mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-29 14:48:51 +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
637
client/src/utils/convos.fakeData.ts
Normal file
637
client/src/utils/convos.fakeData.ts
Normal file
|
|
@ -0,0 +1,637 @@
|
|||
import type { ConversationData } from 'librechat-data-provider';
|
||||
|
||||
/* @ts-ignore */
|
||||
export const convoData: ConversationData = {
|
||||
pages: [
|
||||
{
|
||||
conversations: [
|
||||
{
|
||||
_id: '65bd0a2f7cb605e374e93ed1',
|
||||
conversationId: 'bf71b257-3625-440c-b6a6-03f6a3fd6a4d',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-02T15:28:47.123Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bd0a2f7cb605e374e93ea3',
|
||||
'65bd0a2f7cb605e374e94028',
|
||||
'65bec4af7cb605e3741e84d1',
|
||||
'65bec4af7cb605e3741e86aa',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0125',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'A Long Story',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T22:56:46.269Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bec2c27cb605e374189730',
|
||||
conversationId: '544f1c4f-030f-4ea2-997c-35923f5d8ee2',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
_meiliIndex: true,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T22:48:33.144Z',
|
||||
endpoint: 'OpenRouter',
|
||||
endpointType: 'custom',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bec2c27cb605e3741896fd',
|
||||
'65bec2c47cb605e374189c16',
|
||||
'65bec2d97cb605e37418d7dc',
|
||||
'65bec2e67cb605e374190490',
|
||||
'65bec2e77cb605e3741907df',
|
||||
],
|
||||
model: 'meta-llama/llama-2-13b-chat',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'How Are You Doing?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T22:49:21.140Z',
|
||||
},
|
||||
{
|
||||
_id: '65be8c0d7cb605e3747323ad',
|
||||
conversationId: 'e3f19866-190e-43ab-869f-10260f07530f',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T18:55:09.560Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65be8c0d7cb605e37473236c', '65be8c0d7cb605e374732475'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'A Long Story',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:55:17.586Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65be6bd17cb605e37412706b',
|
||||
conversationId: '4d569723-3aff-4f52-9bbf-e127783a06ac',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T16:37:37.600Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65be6bd17cb605e374127036',
|
||||
'65be6bd17cb605e374127156',
|
||||
'65be8c007cb605e37472f7a9',
|
||||
'65be8c007cb605e37472f8b5',
|
||||
'65be8c057cb605e374730c05',
|
||||
'65be8c067cb605e374730dae',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Write Einstein\'s Famous Equation in LaTeX',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:55:02.407Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65be6b7b7cb605e374117546',
|
||||
conversationId: '640db89d-459f-4411-a0b0-26cb1d53bf1a',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T16:36:11.010Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65be6b7a7cb605e374117519',
|
||||
'65be6b7b7cb605e37411766c',
|
||||
'65be6e1c7cb605e374195898',
|
||||
'65be6e1d7cb605e374195985',
|
||||
'65be6e767cb605e3741a5d94',
|
||||
'65be6e767cb605e3741a5e8e',
|
||||
'65be89ee7cb605e3746ccb52',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Fibonacci Solver in Python',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:46:06.636Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde6117cb605e37481d315',
|
||||
conversationId: 'a9b39a05-fdc0-47f4-bd3b-b0aca618f656',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:06:55.573Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bde6117cb605e37481d294',
|
||||
'65bde6117cb605e37481d4eb',
|
||||
'65be6e7b7cb605e3741a6dd4',
|
||||
'65be6e7b7cb605e3741a6ebe',
|
||||
'65be6fa97cb605e3741df0ed',
|
||||
'65be6fa97cb605e3741df249',
|
||||
'65be709a7cb605e37420ca1b',
|
||||
'65be709a7cb605e37420cb24',
|
||||
'65be71ba7cb605e374244131',
|
||||
'65be71bb7cb605e37424423e',
|
||||
'65be79017cb605e37439dddd',
|
||||
'65be79027cb605e37439df49',
|
||||
'65be82e57cb605e37457d6b5',
|
||||
'65be84727cb605e3745c76ff',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'test',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:22:42.524Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bd1b347cb605e3741e29dc',
|
||||
conversationId: '3ce779d7-8535-4a43-9b70-e0d3160f299e',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-02T16:41:24.324Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bd1b347cb605e3741e299d',
|
||||
'65bd1b347cb605e3741e2ba6',
|
||||
'65be82ed7cb605e37457f381',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0125',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:16:13.357Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bdd6d77cb605e37454b694',
|
||||
conversationId: 'c162f906-06fb-405a-b7e6-773a0fc5f8e9',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T06:01:57.968Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bdd6d77cb605e37454b66c',
|
||||
'65bdd6d87cb605e37454b892',
|
||||
'65bddca57cb605e37465ceea',
|
||||
'65bddcab7cb605e37465de2b',
|
||||
'65bddccb7cb605e374663d37',
|
||||
'65bddccc7cb605e374663ea9',
|
||||
'65bddce17cb605e374667f08',
|
||||
'65bddce27cb605e374668096',
|
||||
'65bdeb557cb605e37491787a',
|
||||
'65bdeb567cb605e374917aa2',
|
||||
'65be82dc7cb605e37457b70e',
|
||||
],
|
||||
model: 'gpt-4-0125-preview',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'test',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:15:57.133Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65be82c87cb605e374577820',
|
||||
conversationId: '48bbc7d5-1815-4024-8ac6-6c9f59242426',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T18:15:36.759Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65be82c87cb605e3745777f6',
|
||||
'65be82c97cb605e374577911',
|
||||
'65be82d57cb605e37457a2fc',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:15:49.536Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde8567cb605e37488ac01',
|
||||
conversationId: '97d6e676-b05b-43f9-8f56-1c07e8a1eb4e',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:16:36.407Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bde8557cb605e37488abe2',
|
||||
'65bde8567cb605e37488ad32',
|
||||
'65be6eb97cb605e3741b267b',
|
||||
'65be6eba7cb605e3741b2849',
|
||||
'65be703c7cb605e3741fb06d',
|
||||
'65be703d7cb605e3741fb182',
|
||||
'65be710b7cb605e374221776',
|
||||
'65be710b7cb605e37422193a',
|
||||
'65be72137cb605e37425544c',
|
||||
'65be72137cb605e37425556c',
|
||||
'65be7e2c7cb605e3744975ee',
|
||||
'65be7e6c7cb605e3744a3d29',
|
||||
'65be81147cb605e374525ccb',
|
||||
'65be826b7cb605e374565dcf',
|
||||
'65be827e7cb605e37456986c',
|
||||
'65be82967cb605e37456db94',
|
||||
'65be82c07cb605e374575ef6',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'test',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T18:15:28.531Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde95c7cb605e3748ba8ae',
|
||||
conversationId: '293f230b-ceaa-4802-9611-c4fe7e4b1fd6',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:20:58.933Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bde95c7cb605e3748ba84d',
|
||||
'65bde95c7cb605e3748baa9d',
|
||||
'65be6b3a7cb605e37410ab2d',
|
||||
'65be6b3a7cb605e37410ac16',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello, How Can I Help You?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T16:35:07.134Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65be6a967cb605e3740ebdc4',
|
||||
conversationId: '279db3ad-2219-4229-b99a-e19a2b191dd7',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T16:32:22.480Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65be6a967cb605e3740ebd60', '65be6a967cb605e3740ebf38'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello there! How may I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T16:32:25.066Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bdea947cb605e3748f42c0',
|
||||
conversationId: '3e62a081-055c-4ee5-9e33-7ab8b3d367c9',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:26:10.988Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bdea947cb605e3748f4275', '65bdea947cb605e3748f43af'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How may I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:26:13.177Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde8aa7cb605e37489a27b',
|
||||
conversationId: 'b97836fc-8566-48e2-a28d-99f99528ca20',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:18:01.245Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde8aa7cb605e37489a256', '65bde8ab7cb605e37489a3a1'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:18:04.006Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde8357cb605e3748850a7',
|
||||
conversationId: 'aa52b79d-ebe7-49d1-9fee-5f5b89d56069',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:16:03.728Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde8357cb605e37488508e', '65bde8357cb605e37488520e'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How may I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:16:06.189Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde7887cb605e374864527',
|
||||
conversationId: 'fe50b20f-8465-4866-b5ef-9bc519a00eef',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:13:10.682Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde7887cb605e3748644e0', '65bde7887cb605e37486463b'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:13:12.960Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde6f47cb605e37484824b',
|
||||
conversationId: '2fbb4a34-4d17-4e05-8c0a-949e78572aa3',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:10:42.904Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde6f47cb605e374848207', '65bde6f47cb605e3748483b5'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:10:45.245Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde6a77cb605e37483941b',
|
||||
conversationId: 'c0d587d0-e881-42be-a2cf-5bf01198bdac',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:09:25.506Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde6a77cb605e3748393d7', '65bde6a77cb605e374839506'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:09:27.717Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde65c7cb605e37482b717',
|
||||
conversationId: 'acd7fa14-4165-4fa1-b2a6-637041743a78',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:08:10.607Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde65c7cb605e37482b6f7', '65bde65c7cb605e37482b822'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:08:12.971Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde6467cb605e37482700c',
|
||||
conversationId: '61ba520e-d53b-4816-b8cc-059d89f15ed4',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T07:07:49.166Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde6467cb605e374826fee', '65bde6477cb605e374827125'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T07:07:51.592Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bde4677cb605e3747cd139',
|
||||
conversationId: 'd4f599af-aeae-4a54-b34c-bd85ce8134af',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T06:59:49.834Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bde4677cb605e3747cd0ed', '65bde4677cb605e3747cd26d'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T06:59:52.004Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bddfd37cb605e3746f4328',
|
||||
conversationId: 'e424c98c-8540-428a-ae43-dc314e15849d',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T06:40:18.167Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bddfd37cb605e3746f42c5', '65bddfd47cb605e3746f4471'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T06:40:20.382Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bddeb97cb605e3746bfb8c',
|
||||
conversationId: 'edac9c4d-bb66-4550-acaf-98006b83db4d',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T06:35:35.937Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: ['65bddeb97cb605e3746bfb5e', '65bddeb97cb605e3746bfc8a'],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hello! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T06:35:38.519Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bdd6817cb605e37453b949',
|
||||
conversationId: 'dbeca051-8af8-42cb-a611-70f669c66502',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T06:00:31.691Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bdd6817cb605e37453b904',
|
||||
'65bdd6817cb605e37453ba9b',
|
||||
'65bddd7e7cb605e3746858ff',
|
||||
'65bddd7f7cb605e374685ac6',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'test 2',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T06:30:21.941Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
{
|
||||
_id: '65bdd9ac7cb605e3745cf331',
|
||||
conversationId: '4a69c491-5cfc-4a62-b7d3-6a54d890dfa8',
|
||||
user: 'my-user-id',
|
||||
__v: 0,
|
||||
chatGptLabel: null,
|
||||
createdAt: '2024-02-03T06:14:02.394Z',
|
||||
endpoint: 'openAI',
|
||||
frequency_penalty: 0,
|
||||
imageDetail: 'auto',
|
||||
messages: [
|
||||
'65bdd9ab7cb605e3745cf30b',
|
||||
'65bdd9ac7cb605e3745cf3f6',
|
||||
'65bddc417cb605e37464abc7',
|
||||
'65bddc427cb605e37464ad09',
|
||||
'65bddc4a7cb605e37464c7cc',
|
||||
'65bddc767cb605e374654895',
|
||||
],
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
presence_penalty: 0,
|
||||
promptPrefix: null,
|
||||
resendImages: false,
|
||||
temperature: 1,
|
||||
title: 'Hi there! How can I assist you today?',
|
||||
top_p: 1,
|
||||
updatedAt: '2024-02-03T06:25:59.827Z',
|
||||
_meiliIndex: true,
|
||||
},
|
||||
],
|
||||
pages: 49,
|
||||
pageNumber: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
],
|
||||
pageParams: [null],
|
||||
};
|
||||
219
client/src/utils/convos.spec.ts
Normal file
219
client/src/utils/convos.spec.ts
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
import { convoData } from './convos.fakeData';
|
||||
import {
|
||||
groupConversationsByDate,
|
||||
addConversation,
|
||||
updateConversation,
|
||||
updateConvoFields,
|
||||
deleteConversation,
|
||||
findPageForConversation,
|
||||
} from './convos';
|
||||
import type { TConversation, ConversationData } from 'librechat-data-provider';
|
||||
|
||||
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() },
|
||||
];
|
||||
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
||||
expect(grouped[0][0]).toBe('Today');
|
||||
expect(grouped[0][1]).toHaveLength(1);
|
||||
expect(grouped[1][0]).toBe(' 2023');
|
||||
expect(grouped[1][1]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns an empty array for no conversations', () => {
|
||||
expect(groupConversationsByDate([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('skips conversations with duplicate conversationIds', () => {
|
||||
const conversations = [
|
||||
{ conversationId: '1', updatedAt: '2023-12-01T12:00:00Z' }, // " 2023"
|
||||
{ conversationId: '2', updatedAt: '2023-11-25T12:00:00Z' }, // " 2023"
|
||||
{ conversationId: '1', updatedAt: '2023-11-20T12:00:00Z' }, // Should be skipped because of duplicate ID
|
||||
{ conversationId: '3', updatedAt: '2022-12-01T12:00:00Z' }, // " 2022"
|
||||
];
|
||||
|
||||
const grouped = groupConversationsByDate(conversations as TConversation[]);
|
||||
|
||||
expect(grouped).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([' 2023', expect.arrayContaining(conversations.slice(0, 2))]),
|
||||
expect.arrayContaining([' 2022', expect.arrayContaining([conversations[3]])]),
|
||||
]),
|
||||
);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addConversation', () => {
|
||||
it('adds a new conversation to the top of the list', () => {
|
||||
const data = { pages: [{ conversations: [] }] };
|
||||
const newConversation = { conversationId: 'new', updatedAt: '2023-04-02T12:00:00Z' };
|
||||
const newData = addConversation(
|
||||
data as unknown as ConversationData,
|
||||
newConversation as TConversation,
|
||||
);
|
||||
expect(newData.pages[0].conversations).toHaveLength(1);
|
||||
expect(newData.pages[0].conversations[0].conversationId).toBe('new');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateConversation', () => {
|
||||
it('updates an existing conversation and moves it to the top', () => {
|
||||
const initialData = {
|
||||
pages: [
|
||||
{
|
||||
conversations: [
|
||||
{ conversationId: '1', updatedAt: '2023-04-01T12:00:00Z' },
|
||||
{ conversationId: '2', updatedAt: '2023-04-01T13:00:00Z' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const updatedConversation = { conversationId: '1', updatedAt: '2023-04-02T12:00:00Z' };
|
||||
const newData = updateConversation(
|
||||
initialData as unknown as ConversationData,
|
||||
updatedConversation as TConversation,
|
||||
);
|
||||
expect(newData.pages[0].conversations).toHaveLength(2);
|
||||
expect(newData.pages[0].conversations[0].conversationId).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateConvoFields', () => {
|
||||
it('updates specific fields of a conversation', () => {
|
||||
const initialData = {
|
||||
pages: [
|
||||
{
|
||||
conversations: [
|
||||
{ conversationId: '1', title: 'Old Title', updatedAt: '2023-04-01T12:00:00Z' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const updatedFields = { conversationId: '1', title: 'New Title' };
|
||||
const newData = updateConvoFields(
|
||||
initialData as ConversationData,
|
||||
updatedFields as TConversation,
|
||||
);
|
||||
expect(newData.pages[0].conversations[0].title).toBe('New Title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteConversation', () => {
|
||||
it('removes a conversation by id', () => {
|
||||
const initialData = {
|
||||
pages: [
|
||||
{
|
||||
conversations: [
|
||||
{ conversationId: '1', updatedAt: '2023-04-01T12:00:00Z' },
|
||||
{ conversationId: '2', updatedAt: '2023-04-01T13:00:00Z' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const newData = deleteConversation(initialData as ConversationData, '1');
|
||||
expect(newData.pages[0].conversations).toHaveLength(1);
|
||||
expect(newData.pages[0].conversations[0].conversationId).not.toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPageForConversation', () => {
|
||||
it('finds the correct page and index for a given conversation', () => {
|
||||
const data = {
|
||||
pages: [
|
||||
{
|
||||
conversations: [
|
||||
{ conversationId: '1', updatedAt: '2023-04-01T12:00:00Z' },
|
||||
{ conversationId: '2', updatedAt: '2023-04-02T13:00:00Z' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const { pageIndex, convIndex } = findPageForConversation(data as ConversationData, {
|
||||
conversationId: '2',
|
||||
});
|
||||
expect(pageIndex).toBe(0);
|
||||
expect(convIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Conversation Utilities with Fake Data', () => {
|
||||
describe('groupConversationsByDate', () => {
|
||||
it('correctly groups conversations from fake data by date', () => {
|
||||
const { pages } = convoData;
|
||||
const allConversations = pages.flatMap((p) => p.conversations);
|
||||
const grouped = groupConversationsByDate(allConversations);
|
||||
|
||||
expect(grouped).toHaveLength(1);
|
||||
expect(grouped[0][1]).toBeInstanceOf(Array);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addConversation', () => {
|
||||
it('adds a new conversation to the existing fake data', () => {
|
||||
const newConversation = {
|
||||
conversationId: 'new',
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as TConversation;
|
||||
const initialLength = convoData.pages[0].conversations.length;
|
||||
const newData = addConversation(convoData, newConversation);
|
||||
expect(newData.pages[0].conversations.length).toBe(initialLength + 1);
|
||||
expect(newData.pages[0].conversations[0].conversationId).toBe('new');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateConversation', () => {
|
||||
it('updates an existing conversation within fake data', () => {
|
||||
const updatedConversation = {
|
||||
...convoData.pages[0].conversations[0],
|
||||
title: 'Updated Title',
|
||||
};
|
||||
const newData = updateConversation(convoData, updatedConversation);
|
||||
expect(newData.pages[0].conversations[0].title).toBe('Updated Title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateConvoFields', () => {
|
||||
it('updates specific fields of a conversation in fake data', () => {
|
||||
const updatedFields = {
|
||||
conversationId: convoData.pages[0].conversations[0].conversationId,
|
||||
title: 'Partially Updated Title',
|
||||
};
|
||||
const newData = updateConvoFields(convoData, updatedFields as TConversation);
|
||||
const updatedConversation = newData.pages[0].conversations.find(
|
||||
(c) => c.conversationId === updatedFields.conversationId,
|
||||
);
|
||||
expect(updatedConversation?.title).toBe('Partially Updated Title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteConversation', () => {
|
||||
it('removes a conversation by id from fake data', () => {
|
||||
const conversationIdToDelete = convoData.pages[0].conversations[0].conversationId as string;
|
||||
const newData = deleteConversation(convoData, conversationIdToDelete);
|
||||
const deletedConvoExists = newData.pages[0].conversations.some(
|
||||
(c) => c.conversationId === conversationIdToDelete,
|
||||
);
|
||||
expect(deletedConvoExists).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPageForConversation', () => {
|
||||
it('finds the correct page and index for a given conversation in fake data', () => {
|
||||
const targetConversation = convoData.pages[0].conversations[0];
|
||||
const { pageIndex, convIndex } = findPageForConversation(convoData, {
|
||||
conversationId: targetConversation.conversationId as string,
|
||||
});
|
||||
expect(pageIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(convIndex).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
148
client/src/utils/convos.ts
Normal file
148
client/src/utils/convos.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import { parseISO, isToday, isWithinInterval, subDays, getYear } from 'date-fns';
|
||||
import type {
|
||||
TConversation,
|
||||
ConversationData,
|
||||
ConversationUpdater,
|
||||
GroupedConversations,
|
||||
} from 'librechat-data-provider';
|
||||
|
||||
const getGroupName = (date: Date) => {
|
||||
const now = new Date();
|
||||
if (isToday(date)) {
|
||||
return 'Today';
|
||||
}
|
||||
if (isWithinInterval(date, { start: subDays(now, 7), end: now })) {
|
||||
return 'Last 7 days';
|
||||
}
|
||||
if (isWithinInterval(date, { start: subDays(now, 30), end: now })) {
|
||||
return 'Last 30 days';
|
||||
}
|
||||
return ' ' + getYear(date).toString();
|
||||
};
|
||||
|
||||
export const groupConversationsByDate = (conversations: TConversation[]): GroupedConversations => {
|
||||
if (!Array.isArray(conversations)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const seenConversationIds = new Set();
|
||||
const groups = conversations.reduce((acc, conversation) => {
|
||||
if (seenConversationIds.has(conversation.conversationId)) {
|
||||
return acc;
|
||||
}
|
||||
seenConversationIds.add(conversation.conversationId);
|
||||
|
||||
const date = parseISO(conversation.updatedAt);
|
||||
const groupName = getGroupName(date);
|
||||
if (!acc[groupName]) {
|
||||
acc[groupName] = [];
|
||||
}
|
||||
acc[groupName].push(conversation);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const sortedGroups = {};
|
||||
const dateGroups = ['Today', 'Last 7 days', 'Last 30 days'];
|
||||
dateGroups.forEach((group) => {
|
||||
if (groups[group]) {
|
||||
sortedGroups[group] = groups[group];
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(groups)
|
||||
.filter((group) => !dateGroups.includes(group))
|
||||
.sort()
|
||||
.reverse()
|
||||
.forEach((year) => {
|
||||
sortedGroups[year] = groups[year];
|
||||
});
|
||||
|
||||
return Object.entries(sortedGroups);
|
||||
};
|
||||
|
||||
export const addConversation: ConversationUpdater = (data, newConversation) => {
|
||||
const newData = JSON.parse(JSON.stringify(data)) as ConversationData;
|
||||
const { pageIndex, convIndex } = findPageForConversation(newData, newConversation);
|
||||
|
||||
if (pageIndex !== -1 && convIndex !== -1) {
|
||||
return updateConversation(data, newConversation);
|
||||
}
|
||||
newData.pages[0].conversations.unshift({
|
||||
...newConversation,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
export function findPageForConversation(
|
||||
data: ConversationData,
|
||||
conversation: TConversation | { conversationId: string },
|
||||
) {
|
||||
for (let pageIndex = 0; pageIndex < data.pages.length; pageIndex++) {
|
||||
const page = data.pages[pageIndex];
|
||||
const convIndex = page.conversations.findIndex(
|
||||
(c) => c.conversationId === conversation.conversationId,
|
||||
);
|
||||
if (convIndex !== -1) {
|
||||
return { pageIndex, convIndex };
|
||||
}
|
||||
}
|
||||
return { pageIndex: -1, convIndex: -1 }; // Not found
|
||||
}
|
||||
|
||||
export const updateConversation: ConversationUpdater = (data, updatedConversation) => {
|
||||
const newData = JSON.parse(JSON.stringify(data));
|
||||
const { pageIndex, convIndex } = findPageForConversation(newData, updatedConversation);
|
||||
|
||||
if (pageIndex !== -1 && convIndex !== -1) {
|
||||
// Remove the conversation from its current position
|
||||
newData.pages[pageIndex].conversations.splice(convIndex, 1);
|
||||
// Add the updated conversation to the top of the first page
|
||||
newData.pages[0].conversations.unshift({
|
||||
...updatedConversation,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
export const updateConvoFields: ConversationUpdater = (
|
||||
data: ConversationData,
|
||||
updatedConversation: Partial<TConversation> & Pick<TConversation, 'conversationId'>,
|
||||
): ConversationData => {
|
||||
const newData = JSON.parse(JSON.stringify(data));
|
||||
const { pageIndex, convIndex } = findPageForConversation(
|
||||
newData,
|
||||
updatedConversation as { conversationId: string },
|
||||
);
|
||||
|
||||
if (pageIndex !== -1 && convIndex !== -1) {
|
||||
const deleted = newData.pages[pageIndex].conversations.splice(convIndex, 1);
|
||||
const oldConversation = deleted[0] as TConversation;
|
||||
|
||||
newData.pages[0].conversations.unshift({
|
||||
...oldConversation,
|
||||
...updatedConversation,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
export const deleteConversation = (
|
||||
data: ConversationData,
|
||||
conversationId: string,
|
||||
): ConversationData => {
|
||||
const newData = JSON.parse(JSON.stringify(data));
|
||||
const { pageIndex, convIndex } = findPageForConversation(newData, { conversationId });
|
||||
|
||||
if (pageIndex !== -1 && convIndex !== -1) {
|
||||
// Delete the conversation from its current page
|
||||
newData.pages[pageIndex].conversations.splice(convIndex, 1);
|
||||
}
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
export * from './json';
|
||||
export * from './files';
|
||||
export * from './latex';
|
||||
export * from './convos';
|
||||
export * from './presets';
|
||||
export * from './languages';
|
||||
export * from './endpoints';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue