2025-04-15 10:04:00 +02:00
|
|
|
import { QueryClient } from '@tanstack/react-query';
|
|
|
|
|
import type { TConversation, InfiniteData } from 'librechat-data-provider';
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
import {
|
2024-05-14 11:00:01 -04:00
|
|
|
dateKeys,
|
2025-04-15 10:04:00 +02:00
|
|
|
storeEndpointSettings,
|
|
|
|
|
addConversationToInfinitePages,
|
|
|
|
|
updateInfiniteConvoPage,
|
|
|
|
|
findConversationInInfinite,
|
|
|
|
|
removeConvoFromInfinitePages,
|
2024-05-14 11:00:01 -04:00
|
|
|
groupConversationsByDate,
|
2025-04-15 10:04:00 +02:00
|
|
|
updateConvoFieldsInfinite,
|
|
|
|
|
addConvoToAllQueries,
|
|
|
|
|
updateConvoInAllQueries,
|
|
|
|
|
removeConvoFromAllQueries,
|
|
|
|
|
addConversationToAllConversationsQueries,
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
} from './convos';
|
2024-09-21 10:20:30 -04:00
|
|
|
import { convoData } from './convos.fakeData';
|
2024-05-24 09:38:38 -07:00
|
|
|
import { normalizeData } from './collection';
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
|
2024-09-21 18:50:04 -04:00
|
|
|
jest.mock('date-fns', () => {
|
|
|
|
|
const actual = jest.requireActual('date-fns');
|
|
|
|
|
return {
|
|
|
|
|
...actual,
|
|
|
|
|
startOfToday: jest.fn(() => new Date('2023-07-15T00:00:00Z')),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
describe('Conversation Utilities', () => {
|
|
|
|
|
describe('groupConversationsByDate', () => {
|
|
|
|
|
it('groups conversations by date correctly', () => {
|
|
|
|
|
const conversations = [
|
|
|
|
|
{ conversationId: '1', updatedAt: '2023-04-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '2', updatedAt: new Date().toISOString() },
|
2025-04-15 10:04:00 +02:00
|
|
|
{ conversationId: '3', updatedAt: new Date(Date.now() - 86400000).toISOString() },
|
|
|
|
|
{ conversationId: '4', updatedAt: new Date(Date.now() - 86400000 * 2).toISOString() },
|
|
|
|
|
{ conversationId: '5', updatedAt: new Date(Date.now() - 86400000 * 8).toISOString() },
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
];
|
|
|
|
|
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
2024-05-14 11:00:01 -04:00
|
|
|
expect(grouped[0][0]).toBe(dateKeys.today);
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
expect(grouped[0][1]).toHaveLength(1);
|
2024-05-14 11:00:01 -04:00
|
|
|
expect(grouped[1][0]).toBe(dateKeys.yesterday);
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
expect(grouped[1][1]).toHaveLength(1);
|
2024-05-14 11:00:01 -04:00
|
|
|
expect(grouped[2][0]).toBe(dateKeys.previous7Days);
|
2024-02-11 08:53:44 -05:00
|
|
|
expect(grouped[2][1]).toHaveLength(1);
|
2024-05-14 11:00:01 -04:00
|
|
|
expect(grouped[3][0]).toBe(dateKeys.previous30Days);
|
2024-02-11 08:53:44 -05:00
|
|
|
expect(grouped[3][1]).toHaveLength(1);
|
|
|
|
|
expect(grouped[4][0]).toBe(' 2023');
|
|
|
|
|
expect(grouped[4][1]).toHaveLength(1);
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('skips conversations with duplicate conversationIds', () => {
|
|
|
|
|
const conversations = [
|
2025-04-15 10:04:00 +02:00
|
|
|
{ conversationId: '1', updatedAt: '2023-12-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '2', updatedAt: '2023-11-25T12:00:00Z' },
|
|
|
|
|
{ conversationId: '1', updatedAt: '2023-11-20T12:00:00Z' },
|
|
|
|
|
{ conversationId: '3', updatedAt: '2022-12-01T12:00:00Z' },
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
|
|
|
|
|
|
|
|
|
expect(grouped).toEqual(
|
|
|
|
|
expect.arrayContaining([
|
2025-04-15 10:04:00 +02:00
|
|
|
[' 2023', expect.arrayContaining([conversations[0], conversations[1]])],
|
|
|
|
|
[' 2022', expect.arrayContaining([conversations[3]])],
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
]),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// No duplicate IDs are present
|
|
|
|
|
const allGroupedIds = grouped.flatMap(([, convs]) => convs.map((c) => c.conversationId));
|
|
|
|
|
const uniqueIds = [...new Set(allGroupedIds)];
|
|
|
|
|
expect(allGroupedIds.length).toBe(uniqueIds.length);
|
|
|
|
|
});
|
2024-09-21 10:20:30 -04:00
|
|
|
|
|
|
|
|
it('sorts conversations by month correctly', () => {
|
|
|
|
|
const conversations = [
|
2025-04-15 10:04:00 +02:00
|
|
|
{ conversationId: '1', updatedAt: '2023-01-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '2', updatedAt: '2023-12-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '3', updatedAt: '2023-02-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '4', updatedAt: '2023-11-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '5', updatedAt: '2022-12-01T12:00:00Z' },
|
2024-09-21 10:20:30 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
|
|
|
|
|
2025-04-15 10:04:00 +02:00
|
|
|
// Now expect grouping by year for 2023 and 2022
|
|
|
|
|
const expectedGroups = [' 2023', ' 2022'];
|
|
|
|
|
expect(grouped.map(([key]) => key)).toEqual(expectedGroups);
|
2024-09-21 10:20:30 -04:00
|
|
|
|
2025-04-15 10:04:00 +02:00
|
|
|
// Check if conversations within 2023 are sorted correctly by updatedAt descending
|
2024-09-21 10:20:30 -04:00
|
|
|
const conversationsIn2023 = grouped[0][1];
|
2025-04-15 10:04:00 +02:00
|
|
|
const sorted = [...conversationsIn2023].sort(
|
|
|
|
|
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
|
|
|
|
|
);
|
|
|
|
|
expect(conversationsIn2023).toEqual(sorted);
|
2024-09-21 10:20:30 -04:00
|
|
|
|
|
|
|
|
// Check if the conversation from 2022 is in its own group
|
|
|
|
|
expect(grouped[1][1].length).toBe(1);
|
|
|
|
|
expect(new Date(grouped[1][1][0].updatedAt).getFullYear()).toBe(2022);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles conversations from multiple years correctly', () => {
|
|
|
|
|
const conversations = [
|
2025-04-15 10:04:00 +02:00
|
|
|
{ conversationId: '1', updatedAt: '2023-01-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '2', updatedAt: '2022-12-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '3', updatedAt: '2021-06-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '4', updatedAt: '2023-06-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '5', updatedAt: '2021-12-01T12:00:00Z' },
|
2024-09-21 10:20:30 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
|
|
|
|
|
|
|
|
|
expect(grouped.map(([key]) => key)).toEqual([' 2023', ' 2022', ' 2021']);
|
2025-04-15 10:04:00 +02:00
|
|
|
expect(grouped[0][1].map((c) => new Date(c.updatedAt).getFullYear())).toEqual([2023, 2023]);
|
|
|
|
|
expect(grouped[1][1].map((c) => new Date(c.updatedAt).getFullYear())).toEqual([2022]);
|
|
|
|
|
expect(grouped[2][1].map((c) => new Date(c.updatedAt).getFullYear())).toEqual([2021, 2021]);
|
2024-09-21 10:20:30 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles conversations from the same month correctly', () => {
|
|
|
|
|
const conversations = [
|
|
|
|
|
{ conversationId: '1', updatedAt: '2023-06-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '2', updatedAt: '2023-06-15T12:00:00Z' },
|
|
|
|
|
{ conversationId: '3', updatedAt: '2023-06-30T12:00:00Z' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
|
|
|
|
|
|
|
|
|
expect(grouped.length).toBe(1);
|
|
|
|
|
expect(grouped[0][0]).toBe(' 2023');
|
|
|
|
|
expect(grouped[0][1].map((c) => c.conversationId)).toEqual(['3', '2', '1']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles conversations with null or undefined updatedAt correctly', () => {
|
|
|
|
|
const conversations = [
|
|
|
|
|
{ conversationId: '1', updatedAt: '2023-06-01T12:00:00Z' },
|
|
|
|
|
{ conversationId: '2', updatedAt: null },
|
|
|
|
|
{ conversationId: '3', updatedAt: undefined },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
|
|
|
|
|
2025-04-15 10:04:00 +02:00
|
|
|
expect(grouped.length).toBe(2);
|
2024-09-21 10:20:30 -04:00
|
|
|
expect(grouped[0][0]).toBe(dateKeys.today);
|
2025-04-15 10:04:00 +02:00
|
|
|
expect(grouped[0][1].length).toBe(2);
|
2024-09-21 10:20:30 -04:00
|
|
|
expect(grouped[1][0]).toBe(' 2023');
|
2025-04-15 10:04:00 +02:00
|
|
|
expect(grouped[1][1].length).toBe(1);
|
2024-09-21 10:20:30 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('correctly groups and sorts conversations for every month of the year', () => {
|
|
|
|
|
const months = [
|
|
|
|
|
'january',
|
|
|
|
|
'february',
|
|
|
|
|
'march',
|
|
|
|
|
'april',
|
|
|
|
|
'may',
|
|
|
|
|
'june',
|
|
|
|
|
'july',
|
|
|
|
|
'august',
|
|
|
|
|
'september',
|
|
|
|
|
'october',
|
|
|
|
|
'november',
|
|
|
|
|
'december',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Create conversations for each month in both 2023 and 2022
|
|
|
|
|
const conversations = months.flatMap((month, index) => [
|
|
|
|
|
{
|
|
|
|
|
conversationId: `2023-${month}`,
|
|
|
|
|
updatedAt: `2023-${String(index + 1).padStart(2, '0')}-15T12:00:00Z`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
conversationId: `2022-${month}`,
|
|
|
|
|
updatedAt: `2022-${String(index + 1).padStart(2, '0')}-15T12:00:00Z`,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
|
|
|
|
|
2025-04-15 10:04:00 +02:00
|
|
|
// All 2023 conversations should be in a single group
|
|
|
|
|
const group2023 = grouped.find(([key]) => key === ' 2023');
|
2024-09-21 10:20:30 -04:00
|
|
|
expect(group2023).toBeDefined();
|
2025-04-15 10:04:00 +02:00
|
|
|
expect(group2023![1].length).toBe(12);
|
2024-09-21 10:20:30 -04:00
|
|
|
|
2025-04-15 10:04:00 +02:00
|
|
|
// All 2022 conversations should be in a single group
|
|
|
|
|
const group2022 = grouped.find(([key]) => key === ' 2022');
|
2024-09-21 10:20:30 -04:00
|
|
|
expect(group2022).toBeDefined();
|
2025-04-15 10:04:00 +02:00
|
|
|
expect(group2022![1].length).toBe(12);
|
2024-09-21 10:20:30 -04:00
|
|
|
|
|
|
|
|
// Check that all conversations are accounted for
|
2025-04-15 10:04:00 +02:00
|
|
|
const totalGroupedConversations = grouped.reduce(
|
|
|
|
|
(total, [_, convos]) => total + convos.length,
|
|
|
|
|
0,
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
);
|
2025-04-15 10:04:00 +02:00
|
|
|
expect(totalGroupedConversations).toBe(conversations.length);
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
});
|
|
|
|
|
});
|
2024-05-24 09:38:38 -07:00
|
|
|
|
|
|
|
|
describe('normalizeConversationData', () => {
|
|
|
|
|
it('normalizes the number of items on each page after data removal', () => {
|
|
|
|
|
// Create test data:
|
|
|
|
|
// Generates 15 conversation items, each with a unique conversationId and an updatedAt timestamp set to a different day.
|
|
|
|
|
// { conversationId: '1', updatedAt: new Date(Date.now() - 86400000 * i).toISOString() }
|
|
|
|
|
const conversations = Array.from({ length: 15 }, (_, i) => ({
|
|
|
|
|
conversationId: (i + 1).toString(),
|
|
|
|
|
updatedAt: new Date(Date.now() - 86400000 * i).toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Paginate Data:
|
|
|
|
|
// Divides the 15 conversation items into pages, with each page containing up to 5 items (pageSize is set to 5). This results in 3 pages.
|
|
|
|
|
const pageSize = 5;
|
|
|
|
|
const totalPageNumber = Math.ceil(conversations.length / pageSize);
|
|
|
|
|
|
|
|
|
|
const paginatedData = Array.from({ length: totalPageNumber }, (_, index) => ({
|
|
|
|
|
conversations: conversations.slice(index * pageSize, (index + 1) * pageSize),
|
|
|
|
|
pages: totalPageNumber,
|
|
|
|
|
pageNumber: index + 1,
|
|
|
|
|
pageSize,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const testData = { pages: paginatedData, pageParams: [null, 2, 3] };
|
|
|
|
|
|
|
|
|
|
// Removes one item from the first page, resulting in the first page having 4 items, while the second and third pages still have 5 items each.
|
|
|
|
|
testData.pages[0].conversations.splice(1, 1);
|
|
|
|
|
expect(testData.pages[0].conversations).toHaveLength(4);
|
|
|
|
|
expect(testData.pages[1].conversations).toHaveLength(5);
|
|
|
|
|
expect(testData.pages[2].conversations).toHaveLength(5);
|
|
|
|
|
|
|
|
|
|
// Normalize Data:
|
|
|
|
|
// Calls the normalizeData function to ensure that each page contains exactly 5 items, redistributing the items across the pages as needed.
|
|
|
|
|
const normalizedData = normalizeData(testData, 'conversations', pageSize);
|
|
|
|
|
|
|
|
|
|
// Verify Results:
|
|
|
|
|
// Asserts that the number of conversation data is 5 except for the last page,
|
|
|
|
|
// that the number of conversation data is 4 only for the last page,
|
|
|
|
|
// and that the conversation ids are in the expected order.
|
|
|
|
|
expect(normalizedData.pages[0].conversations).toHaveLength(5);
|
|
|
|
|
expect(normalizedData.pages[0].conversations[0].conversationId).toBe('1');
|
|
|
|
|
expect(normalizedData.pages[0].conversations[4].conversationId).toBe('6');
|
|
|
|
|
|
|
|
|
|
expect(normalizedData.pages[1].conversations).toHaveLength(5);
|
|
|
|
|
expect(normalizedData.pages[1].conversations[0].conversationId).toBe('7');
|
|
|
|
|
expect(normalizedData.pages[1].conversations[4].conversationId).toBe('11');
|
|
|
|
|
|
|
|
|
|
expect(normalizedData.pages[2].conversations).toHaveLength(4);
|
|
|
|
|
expect(normalizedData.pages[2].conversations[0].conversationId).toBe('12');
|
|
|
|
|
expect(normalizedData.pages[2].conversations[3].conversationId).toBe('15');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('normalizes the number of items on each page after data addition', () => {
|
|
|
|
|
// Create test data:
|
|
|
|
|
// Generates 15 conversation items, each with a unique conversationId and an updatedAt timestamp set to a different day.
|
|
|
|
|
// { conversationId: '1', updatedAt: new Date(Date.now() - 86400000 * i).toISOString() }
|
|
|
|
|
const conversations = Array.from({ length: 15 }, (_, i) => ({
|
|
|
|
|
conversationId: (i + 1).toString(),
|
|
|
|
|
updatedAt: new Date(Date.now() - 86400000 * i).toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Paginate Data:
|
|
|
|
|
// Divides the 15 conversation items into pages,
|
|
|
|
|
// with each page containing up to 5 items (pageSize is set to 5). This results in 3 pages.
|
|
|
|
|
const pageSize = 5;
|
|
|
|
|
const totalPageNumber = Math.ceil(conversations.length / pageSize);
|
|
|
|
|
|
|
|
|
|
const paginatedData = Array.from({ length: totalPageNumber }, (_, index) => ({
|
|
|
|
|
conversations: conversations.slice(index * pageSize, (index + 1) * pageSize),
|
|
|
|
|
pages: totalPageNumber,
|
|
|
|
|
pageNumber: index + 1,
|
|
|
|
|
pageSize,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const testData = { pages: paginatedData, pageParams: [null, 2, 3] };
|
|
|
|
|
|
|
|
|
|
// Inserts a new conversation item at the beginning of the first page,
|
|
|
|
|
testData.pages[0].conversations.unshift({
|
|
|
|
|
conversationId: '16',
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// resulting in the first page having 6 items,
|
|
|
|
|
// while the second and third pages still have 5 items each.
|
|
|
|
|
expect(testData.pages[0].conversations).toHaveLength(6);
|
|
|
|
|
expect(testData.pages[1].conversations).toHaveLength(5);
|
|
|
|
|
expect(testData.pages[2].conversations).toHaveLength(5);
|
|
|
|
|
expect(testData.pages[2].conversations[4].conversationId).toBe('15');
|
|
|
|
|
expect(testData.pages).toHaveLength(3);
|
|
|
|
|
|
|
|
|
|
const normalizedData = normalizeData(testData, 'conversations', pageSize);
|
|
|
|
|
|
|
|
|
|
// Verify Results:
|
|
|
|
|
// Asserts that after normalization, each page contains the correct number of items (5 items per page).
|
|
|
|
|
expect(normalizedData.pages[0].conversations).toHaveLength(5);
|
|
|
|
|
expect(normalizedData.pages[1].conversations).toHaveLength(5);
|
|
|
|
|
expect(normalizedData.pages[2].conversations).toHaveLength(5);
|
|
|
|
|
expect(normalizedData.pages).toHaveLength(3);
|
|
|
|
|
|
|
|
|
|
// Checks that the items are in the expected order, ensuring that the conversationId values are correctly distributed across the pages.
|
|
|
|
|
|
|
|
|
|
expect(normalizedData.pages[0].conversations[0].conversationId).toBe('16');
|
|
|
|
|
expect(normalizedData.pages[0].conversations[4].conversationId).toBe('4');
|
|
|
|
|
|
|
|
|
|
expect(normalizedData.pages[1].conversations[0].conversationId).toBe('5');
|
|
|
|
|
expect(normalizedData.pages[1].conversations[4].conversationId).toBe('9');
|
|
|
|
|
|
|
|
|
|
expect(normalizedData.pages[2].conversations[0].conversationId).toBe('10');
|
|
|
|
|
expect(normalizedData.pages[2].conversations[4].conversationId).toBe('14');
|
|
|
|
|
expect(normalizedData.pageParams).toHaveLength(3);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns empty data when there is no data', () => {
|
|
|
|
|
const normalizedData = normalizeData(
|
|
|
|
|
{ pages: [{ conversations: [], pageNumber: 1, pageSize: 5, pages: 1 }], pageParams: [] },
|
|
|
|
|
'conversations',
|
|
|
|
|
5,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(normalizedData.pages[0].conversations).toHaveLength(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not normalize data when not needed', () => {
|
|
|
|
|
const normalizedData = normalizeData(
|
|
|
|
|
{ pages: [{ conversations: ['1'], pageNumber: 1, pageSize: 5, pages: 1 }], pageParams: [] },
|
|
|
|
|
'conversations',
|
|
|
|
|
5,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(normalizedData.pages[0].conversations).toHaveLength(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('deletes pages that have no data as a result of normalization', () => {
|
|
|
|
|
const conversations = Array.from({ length: 15 }, (_, i) => ({
|
|
|
|
|
conversationId: (i + 1).toString(),
|
|
|
|
|
updatedAt: new Date(Date.now() - 86400000 * i).toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const pageSize = 5;
|
|
|
|
|
const totalPageNumber = Math.ceil(conversations.length / pageSize);
|
|
|
|
|
|
|
|
|
|
const paginatedData = Array.from({ length: totalPageNumber }, (_, index) => ({
|
|
|
|
|
conversations: conversations.slice(index * pageSize, (index + 1) * pageSize),
|
|
|
|
|
pages: totalPageNumber,
|
|
|
|
|
pageNumber: index + 1,
|
|
|
|
|
pageSize,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const testData = { pages: paginatedData, pageParams: [null, 2, 3] };
|
|
|
|
|
|
|
|
|
|
// Removes all data from the last page, resulting in the last page having 0 items.
|
|
|
|
|
testData.pages[2].conversations = [];
|
|
|
|
|
expect(testData.pages[0].conversations).toHaveLength(5);
|
|
|
|
|
expect(testData.pages[1].conversations).toHaveLength(5);
|
|
|
|
|
expect(testData.pages[2].conversations).toHaveLength(0);
|
|
|
|
|
expect(testData.pageParams).toHaveLength(3);
|
|
|
|
|
|
|
|
|
|
const normalizedData = normalizeData(testData, 'conversations', pageSize);
|
|
|
|
|
|
|
|
|
|
// Verify Results:
|
|
|
|
|
// Asserts that the last page is removed after normalization, leaving only the first and second pages.
|
|
|
|
|
expect(normalizedData.pages).toHaveLength(2);
|
|
|
|
|
expect(normalizedData.pages[0].conversations).toHaveLength(5);
|
|
|
|
|
expect(normalizedData.pages[1].conversations).toHaveLength(5);
|
|
|
|
|
expect(normalizedData.pageParams).toHaveLength(2);
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-04-15 10:04:00 +02:00
|
|
|
|
|
|
|
|
describe('InfiniteData helpers', () => {
|
|
|
|
|
const makeConversation = (id: string, updatedAt?: string) => ({
|
|
|
|
|
conversationId: id,
|
|
|
|
|
updatedAt: updatedAt || new Date().toISOString(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const makePage = (conversations: any[], nextCursor: string | null = null) => ({
|
|
|
|
|
conversations,
|
|
|
|
|
nextCursor,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('findConversationInInfinite', () => {
|
|
|
|
|
it('finds a conversation by id in InfiniteData', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [
|
|
|
|
|
makePage([makeConversation('1'), makeConversation('2')]),
|
|
|
|
|
makePage([makeConversation('3')]),
|
|
|
|
|
],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const found = findConversationInInfinite(data, '2');
|
|
|
|
|
expect(found).toBeDefined();
|
|
|
|
|
expect(found?.conversationId).toBe('2');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns undefined if conversation not found', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [makePage([makeConversation('1')])],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
expect(findConversationInInfinite(data, 'notfound')).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns undefined if data is undefined', () => {
|
|
|
|
|
expect(findConversationInInfinite(undefined, '1')).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('updateInfiniteConvoPage', () => {
|
|
|
|
|
it('updates a conversation in InfiniteData', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [makePage([makeConversation('1', '2023-01-01T00:00:00Z'), makeConversation('2')])],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const updater = (c: any) => ({ ...c, updatedAt: '2024-01-01T00:00:00Z' });
|
|
|
|
|
const updated = updateInfiniteConvoPage(data, '1', updater);
|
|
|
|
|
expect(updated?.pages[0].conversations[0].updatedAt).toBe('2024-01-01T00:00:00Z');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns original data if conversation not found', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [makePage([makeConversation('1')])],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const updater = (c: any) => ({ ...c, foo: 'bar' });
|
|
|
|
|
const updated = updateInfiniteConvoPage(data, 'notfound', updater);
|
|
|
|
|
expect(updated).toEqual(data);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns undefined if data is undefined', () => {
|
|
|
|
|
expect(updateInfiniteConvoPage(undefined, '1', (c) => c)).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('addConversationToInfinitePages', () => {
|
|
|
|
|
it('adds a conversation to the first page', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [makePage([makeConversation('1')])],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const newConvo = makeConversation('new');
|
|
|
|
|
const updated = addConversationToInfinitePages(data, newConvo);
|
|
|
|
|
expect(updated.pages[0].conversations[0].conversationId).toBe('new');
|
|
|
|
|
expect(updated.pages[0].conversations[1].conversationId).toBe('1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('creates new InfiniteData if data is undefined', () => {
|
|
|
|
|
const newConvo = makeConversation('new');
|
|
|
|
|
const updated = addConversationToInfinitePages(undefined, newConvo);
|
|
|
|
|
expect(updated.pages[0].conversations[0].conversationId).toBe('new');
|
|
|
|
|
expect(updated.pageParams).toEqual([undefined]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('removeConvoFromInfinitePages', () => {
|
|
|
|
|
it('removes a conversation by id', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [
|
|
|
|
|
makePage([makeConversation('1'), makeConversation('2')]),
|
|
|
|
|
makePage([makeConversation('3')]),
|
|
|
|
|
],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const updated = removeConvoFromInfinitePages(data, '2');
|
|
|
|
|
expect(updated?.pages[0].conversations.map((c) => c.conversationId)).toEqual(['1']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('removes empty pages after deletion', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [makePage([makeConversation('1')]), makePage([makeConversation('2')])],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const updated = removeConvoFromInfinitePages(data, '2');
|
|
|
|
|
expect(updated?.pages.length).toBe(1);
|
|
|
|
|
expect(updated?.pages[0].conversations[0].conversationId).toBe('1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns original data if data is undefined', () => {
|
|
|
|
|
expect(removeConvoFromInfinitePages(undefined, '1')).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('updateConvoFieldsInfinite', () => {
|
|
|
|
|
it('updates fields and bumps to front if keepPosition is false', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [
|
|
|
|
|
makePage([makeConversation('1'), makeConversation('2')]),
|
|
|
|
|
makePage([makeConversation('3')]),
|
|
|
|
|
],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const updated = updateConvoFieldsInfinite(
|
|
|
|
|
data,
|
|
|
|
|
{ conversationId: '2', title: 'new' },
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
expect(updated?.pages[0].conversations[0].conversationId).toBe('2');
|
|
|
|
|
expect(updated?.pages[0].conversations[0].title).toBe('new');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('updates fields and keeps position if keepPosition is true', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [makePage([makeConversation('1'), makeConversation('2')])],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const updated = updateConvoFieldsInfinite(
|
|
|
|
|
data,
|
|
|
|
|
{ conversationId: '2', title: 'stay' },
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
expect(updated?.pages[0].conversations[1].title).toBe('stay');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns original data if conversation not found', () => {
|
|
|
|
|
const data = {
|
|
|
|
|
pages: [makePage([makeConversation('1')])],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
};
|
|
|
|
|
const updated = updateConvoFieldsInfinite(
|
|
|
|
|
data,
|
|
|
|
|
{ conversationId: 'notfound', title: 'x' },
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
expect(updated).toEqual(data);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns original data if data is undefined', () => {
|
|
|
|
|
expect(
|
|
|
|
|
updateConvoFieldsInfinite(undefined, { conversationId: '1', title: 'x' }, false),
|
|
|
|
|
).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('storeEndpointSettings', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
localStorage.clear();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('stores model for endpoint', () => {
|
|
|
|
|
const conversation = {
|
|
|
|
|
conversationId: '1',
|
|
|
|
|
endpoint: 'openai',
|
|
|
|
|
model: 'gpt-3',
|
|
|
|
|
};
|
|
|
|
|
storeEndpointSettings(conversation as any);
|
|
|
|
|
const stored = JSON.parse(localStorage.getItem('lastModel') || '{}');
|
|
|
|
|
expect([undefined, 'gpt-3']).toContain(stored.openai);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('stores secondaryModel for gptPlugins endpoint', () => {
|
|
|
|
|
const conversation = {
|
|
|
|
|
conversationId: '1',
|
|
|
|
|
endpoint: 'gptPlugins',
|
|
|
|
|
model: 'gpt-4',
|
|
|
|
|
agentOptions: { model: 'plugin-model' },
|
|
|
|
|
};
|
|
|
|
|
storeEndpointSettings(conversation as any);
|
|
|
|
|
const stored = JSON.parse(localStorage.getItem('lastModel') || '{}');
|
|
|
|
|
expect([undefined, 'gpt-4']).toContain(stored.gptPlugins);
|
|
|
|
|
expect([undefined, 'plugin-model']).toContain(stored.secondaryModel);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does nothing if conversation is null', () => {
|
|
|
|
|
storeEndpointSettings(null);
|
|
|
|
|
expect(localStorage.getItem('lastModel')).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does nothing if endpoint is missing', () => {
|
|
|
|
|
storeEndpointSettings({ conversationId: '1', model: 'x' } as any);
|
|
|
|
|
expect(localStorage.getItem('lastModel')).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('QueryClient helpers', () => {
|
|
|
|
|
let queryClient: QueryClient;
|
|
|
|
|
let convoA: TConversation;
|
|
|
|
|
let convoB: TConversation;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
queryClient = new QueryClient();
|
|
|
|
|
convoA = {
|
|
|
|
|
conversationId: 'a',
|
|
|
|
|
updatedAt: '2024-01-01T12:00:00Z',
|
|
|
|
|
createdAt: '2024-01-01T10:00:00Z',
|
|
|
|
|
endpoint: 'openai',
|
|
|
|
|
model: 'gpt-3',
|
|
|
|
|
title: 'Conversation A',
|
|
|
|
|
} as TConversation;
|
|
|
|
|
convoB = {
|
|
|
|
|
conversationId: 'b',
|
|
|
|
|
updatedAt: '2024-01-02T12:00:00Z',
|
|
|
|
|
endpoint: 'openai',
|
|
|
|
|
model: 'gpt-3',
|
|
|
|
|
} as TConversation;
|
|
|
|
|
queryClient.setQueryData(['allConversations'], {
|
|
|
|
|
pages: [{ conversations: [convoA], nextCursor: null }],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('addConvoToAllQueries adds new on top if not present', () => {
|
|
|
|
|
addConvoToAllQueries(queryClient, convoB);
|
|
|
|
|
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
|
|
|
|
|
expect(data!.pages[0].conversations[0].conversationId).toBe('b');
|
|
|
|
|
expect(data!.pages[0].conversations.length).toBe(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('addConvoToAllQueries does not duplicate', () => {
|
|
|
|
|
addConvoToAllQueries(queryClient, convoA);
|
|
|
|
|
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
|
|
|
|
|
expect(data!.pages[0].conversations.filter((c) => c.conversationId === 'a').length).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('updateConvoInAllQueries updates correct convo', () => {
|
|
|
|
|
updateConvoInAllQueries(queryClient, 'a', (c) => ({ ...c, model: 'gpt-4' }));
|
|
|
|
|
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
|
|
|
|
|
expect(data!.pages[0].conversations[0].model).toBe('gpt-4');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('removeConvoFromAllQueries deletes conversation', () => {
|
|
|
|
|
removeConvoFromAllQueries(queryClient, 'a');
|
|
|
|
|
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
|
|
|
|
|
expect(data!.pages.length).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('addConversationToAllConversationsQueries works with multiple pages', () => {
|
|
|
|
|
queryClient.setQueryData(['allConversations', 'other'], {
|
|
|
|
|
pages: [{ conversations: [], nextCursor: null }],
|
|
|
|
|
pageParams: [],
|
|
|
|
|
});
|
|
|
|
|
addConversationToAllConversationsQueries(queryClient, convoB);
|
|
|
|
|
|
|
|
|
|
const mainData = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
|
|
|
|
|
const otherData = queryClient.getQueryData<InfiniteData<any>>([
|
|
|
|
|
'allConversations',
|
|
|
|
|
'other',
|
|
|
|
|
]);
|
|
|
|
|
expect(mainData!.pages[0].conversations[0].conversationId).toBe('b');
|
|
|
|
|
expect(otherData!.pages[0].conversations[0].conversationId).toBe('b');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
♾️ 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>
2024-02-03 20:25:35 -05:00
|
|
|
});
|