mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-15 23:15:30 +01:00
♾️ style: Infinite Scroll Nav and Sort Convos by Date/Usage (#1708)
* Style: Infinite Scroll and Group convos by date * Style: Infinite Scroll and Group convos by date- Redesign NavBar * Style: Infinite Scroll and Group convos by date- Redesign NavBar - Clean code * Style: Infinite Scroll and Group convos by date- Redesign NavBar - Redesign NewChat Component * Style: Infinite Scroll and Group convos by date- Redesign NavBar - Redesign NewChat Component * Style: Infinite Scroll and Group convos by date- Redesign NavBar - Redesign NewChat Component * Including OpenRouter and Mistral icon * refactor(Conversations): cleanup use of utility functions and typing * refactor(Nav/NewChat): use localStorage `lastConversationSetup` to determine the endpoint to use, as well as icons -> JSX components, remove use of `endpointSelected` * refactor: remove use of `isFirstToday` * refactor(Nav): remove use of `endpointSelected`, consolidate scrolling logic to its own hook `useNavScrolling`, remove use of recoil `conversation` * refactor: Add spinner to bottom of list, throttle fetching, move query hooks to client workspace * chore: sort by `updatedAt` field * refactor: optimize conversation infinite query, use optimistic updates, add conversation helpers for managing pagination, remove unnecessary operations * feat: gen_title route for generating the title for the conversation * style(Convo): change hover bg-color * refactor: memoize groupedConversations and return as array of tuples, correctly update convos pre/post message stream, only call genTitle if conversation is new, make `addConversation` dynamically either add/update depending if convo exists in pages already, reorganize type definitions * style: rename Header NewChat Button -> HeaderNewChat, add NewChatIcon, closely match main Nav New Chat button to ChatGPT * style(NewChat): add hover bg color * style: cleanup comments, match ChatGPT nav styling, redesign search bar, make part of new chat sticky header, move Nav under same parent as outlet/mobilenav, remove legacy code, search only if searchQuery is not empty * feat: add tests for conversation helpers and ensure no duplicate conversations are ever grouped * style: hover bg-color * feat: alt-click on convo item to open conversation in new tab * chore: send error message when `gen_title` fails --------- Co-authored-by: Walber Cardoso <walbercardoso@gmail.com>
This commit is contained in:
parent
13b2d6e34a
commit
74459d6261
48 changed files with 1788 additions and 391 deletions
|
|
@ -23,6 +23,8 @@ export const conversations = (pageNumber: string) => `/api/convos?pageNumber=${p
|
|||
|
||||
export const conversationById = (id: string) => `/api/convos/${id}`;
|
||||
|
||||
export const genTitle = () => '/api/convos/gen_title';
|
||||
|
||||
export const updateConversation = () => '/api/convos/update';
|
||||
|
||||
export const deleteConversation = () => '/api/convos/clear';
|
||||
|
|
|
|||
|
|
@ -161,6 +161,10 @@ export enum CacheKeys {
|
|||
* Key for the plugins cache.
|
||||
*/
|
||||
PLUGINS = 'plugins',
|
||||
/**
|
||||
* Key for the title generation cache.
|
||||
*/
|
||||
GEN_TITLE = 'genTitle',
|
||||
/**
|
||||
* Key for the model config cache.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as f from './types/files';
|
||||
import * as q from './types/queries';
|
||||
import * as m from './types/mutations';
|
||||
import * as a from './types/assistants';
|
||||
import * as t from './types';
|
||||
|
|
@ -52,6 +53,10 @@ export function updateConversation(
|
|||
return request.post(endpoints.updateConversation(), { arg: payload });
|
||||
}
|
||||
|
||||
export function genTitle(payload: m.TGenTitleRequest): Promise<m.TGenTitleResponse> {
|
||||
return request.post(endpoints.genTitle(), payload);
|
||||
}
|
||||
|
||||
export function updateMessage(payload: t.TUpdateMessageRequest): Promise<unknown> {
|
||||
const { conversationId, messageId, text } = payload;
|
||||
if (!conversationId) {
|
||||
|
|
@ -209,3 +214,26 @@ export const deleteFiles = async (files: f.BatchFile[]): Promise<f.DeleteFilesRe
|
|||
request.deleteWithOptions(endpoints.files(), {
|
||||
data: { files },
|
||||
});
|
||||
|
||||
/* conversations */
|
||||
|
||||
export const listConversations = (
|
||||
params?: q.ConversationListParams,
|
||||
): Promise<q.ConversationListResponse> => {
|
||||
// Assuming params has a pageNumber property
|
||||
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
|
||||
return request.get(endpoints.conversations(pageNumber));
|
||||
};
|
||||
|
||||
export const listConversationsByQuery = (
|
||||
params?: q.ConversationListParams & { searchQuery?: string },
|
||||
): Promise<q.ConversationListResponse> => {
|
||||
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
|
||||
const searchQuery = params?.searchQuery || ''; // If no search query is provided, default to an empty string
|
||||
// Update the endpoint to handle a search query
|
||||
if (searchQuery !== '') {
|
||||
return request.get(endpoints.search(searchQuery, pageNumber));
|
||||
} else {
|
||||
return request.get(endpoints.conversations(pageNumber));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export * from './parsers';
|
|||
/* types (exports schemas from `./types` as they contain needed in other defs) */
|
||||
export * from './types';
|
||||
export * from './types/assistants';
|
||||
export * from './types/queries';
|
||||
export * from './types/files';
|
||||
export * from './types/mutations';
|
||||
/* query/mutation keys */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export enum QueryKeys {
|
||||
messages = 'messages',
|
||||
allConversations = 'allConversations',
|
||||
searchConversations = 'searchConversations',
|
||||
conversation = 'conversation',
|
||||
searchEnabled = 'searchEnabled',
|
||||
user = 'user',
|
||||
|
|
|
|||
|
|
@ -85,40 +85,6 @@ export const useGetConversationByIdQuery = (
|
|||
);
|
||||
};
|
||||
|
||||
/* like above, but first try the convos query data */
|
||||
export const useGetConvoIdQuery = (
|
||||
id: string,
|
||||
config?: UseQueryOptions<s.TConversation>,
|
||||
): QueryObserverResult<s.TConversation> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useQuery<s.TConversation>(
|
||||
[QueryKeys.conversation, id],
|
||||
() => {
|
||||
const defaultQuery = () => dataService.getConversationById(id);
|
||||
|
||||
const convosQueryKey = [QueryKeys.allConversations, { pageNumber: '1', active: true }];
|
||||
const convosQuery = queryClient.getQueryData<t.TGetConversationsResponse>(convosQueryKey);
|
||||
|
||||
if (!convosQuery) {
|
||||
return defaultQuery();
|
||||
}
|
||||
|
||||
const convo = convosQuery.conversations?.find((c) => c.conversationId === id);
|
||||
if (convo) {
|
||||
return convo;
|
||||
}
|
||||
|
||||
return defaultQuery();
|
||||
},
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
//This isn't ideal because its just a query and we're using mutation, but it was the only way
|
||||
//to make it work with how the Chat component is structured
|
||||
export const useGetConversationByIdMutation = (id: string): UseMutationResult<s.TConversation> => {
|
||||
|
|
@ -131,26 +97,6 @@ export const useGetConversationByIdMutation = (id: string): UseMutationResult<s.
|
|||
});
|
||||
};
|
||||
|
||||
export const useUpdateConversationMutation = (
|
||||
id: string,
|
||||
): UseMutationResult<
|
||||
t.TUpdateConversationResponse,
|
||||
unknown,
|
||||
t.TUpdateConversationRequest,
|
||||
unknown
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
(payload: t.TUpdateConversationRequest) => dataService.updateConversation(payload),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.conversation, id]);
|
||||
queryClient.invalidateQueries([QueryKeys.allConversations]);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useUpdateMessageMutation = (
|
||||
id: string,
|
||||
): UseMutationResult<unknown, unknown, t.TUpdateMessageRequest, unknown> => {
|
||||
|
|
@ -176,26 +122,6 @@ export const useUpdateUserKeysMutation = (): UseMutationResult<
|
|||
});
|
||||
};
|
||||
|
||||
export const useDeleteConversationMutation = (
|
||||
id?: string,
|
||||
): UseMutationResult<
|
||||
t.TDeleteConversationResponse,
|
||||
unknown,
|
||||
t.TDeleteConversationRequest,
|
||||
unknown
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
(payload: t.TDeleteConversationRequest) => dataService.deleteConversation(payload),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.conversation, id]);
|
||||
queryClient.invalidateQueries([QueryKeys.allConversations]);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useClearConversationsMutation = (): UseMutationResult<unknown> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(() => dataService.clearAllConversations(), {
|
||||
|
|
@ -228,7 +154,7 @@ export const useGetConversationsQuery = (
|
|||
config?: UseQueryOptions<t.TGetConversationsResponse>,
|
||||
): QueryObserverResult<t.TGetConversationsResponse> => {
|
||||
return useQuery<t.TGetConversationsResponse>(
|
||||
[QueryKeys.allConversations, { pageNumber, active: true }],
|
||||
[QueryKeys.allConversations],
|
||||
() => dataService.getConversations(pageNumber),
|
||||
{
|
||||
refetchOnReconnect: false,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ export type TPluginAction = {
|
|||
auth?: unknown;
|
||||
};
|
||||
|
||||
export type GroupedConversations = [key: string, TConversation[]][];
|
||||
|
||||
export type TUpdateUserPlugins = {
|
||||
pluginKey: string;
|
||||
action: string;
|
||||
|
|
@ -102,9 +104,7 @@ export type TUpdateConversationRequest = {
|
|||
title: string;
|
||||
};
|
||||
|
||||
export type TUpdateConversationResponse = {
|
||||
data: TConversation;
|
||||
};
|
||||
export type TUpdateConversationResponse = TConversation;
|
||||
|
||||
export type TDeleteConversationRequest = {
|
||||
conversationId?: string;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { TPreset } from '../types';
|
||||
|
||||
export type TGenTitleRequest = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
export type TGenTitleResponse = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type PresetDeleteResponse = {
|
||||
acknowledged: boolean;
|
||||
deletedCount: number;
|
||||
|
|
|
|||
34
packages/data-provider/src/types/queries.ts
Normal file
34
packages/data-provider/src/types/queries.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type { TMessage, TConversation } from '../schemas';
|
||||
export type Conversation = {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
participants: string[];
|
||||
lastMessage: string;
|
||||
conversations: TConversation[];
|
||||
};
|
||||
|
||||
// Parameters for listing conversations (e.g., for pagination)
|
||||
export type ConversationListParams = {
|
||||
limit?: number;
|
||||
before?: string | null;
|
||||
after?: string | null;
|
||||
order?: 'asc' | 'desc';
|
||||
pageNumber: string; // Add this line
|
||||
conversationId?: string;
|
||||
};
|
||||
|
||||
// Type for the response from the conversation list API
|
||||
export type ConversationListResponse = {
|
||||
conversations: TConversation[];
|
||||
pageNumber: string;
|
||||
pageSize: string | number;
|
||||
pages: string | number;
|
||||
messages: TMessage[];
|
||||
};
|
||||
|
||||
export type ConversationData = InfiniteData<ConversationListResponse>;
|
||||
export type ConversationUpdater = (
|
||||
data: ConversationData,
|
||||
conversation: TConversation,
|
||||
) => ConversationData;
|
||||
Loading…
Add table
Add a link
Reference in a new issue