Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♾️ style: Infinite Scroll Nav and Sort Convos by Date/Usage #1708

Merged
merged 25 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d68da0a
Style: Infinite Scroll and Group convos by date
walbercardoso Jan 24, 2024
9ce5356
Style: Infinite Scroll and Group convos by date- Redesign NavBar
walbercardoso Jan 24, 2024
13c581c
Style: Infinite Scroll and Group convos by date- Redesign NavBar - Cl…
walbercardoso Jan 24, 2024
2c50d92
Style: Infinite Scroll and Group convos by date- Redesign NavBar - Re…
walbercardoso Jan 25, 2024
ba30666
Style: Infinite Scroll and Group convos by date- Redesign NavBar - Re…
walbercardoso Jan 25, 2024
feed476
Style: Infinite Scroll and Group convos by date- Redesign NavBar - Re…
walbercardoso Jan 25, 2024
0cf9cd6
Including OpenRouter and Mistral icon
walbercardoso Jan 27, 2024
88e8734
Merge branch 'main' of https://github.com/walbercardoso/LibreChat int…
danny-avila Feb 2, 2024
4f725fb
refactor(Conversations): cleanup use of utility functions and typing
danny-avila Feb 2, 2024
8ca10d3
refactor(Nav/NewChat): use localStorage `lastConversationSetup` to de…
danny-avila Feb 2, 2024
eed8e9b
refactor: remove use of `isFirstToday`
danny-avila Feb 2, 2024
20dbba3
refactor(Nav): remove use of `endpointSelected`, consolidate scrollin…
danny-avila Feb 2, 2024
a58b009
refactor: Add spinner to bottom of list, throttle fetching, move quer…
danny-avila Feb 2, 2024
c65b83a
chore: sort by `updatedAt` field
danny-avila Feb 3, 2024
f77d491
refactor: optimize conversation infinite query, use optimistic update…
danny-avila Feb 3, 2024
18d8039
feat: gen_title route for generating the title for the conversation
danny-avila Feb 3, 2024
36ce64c
style(Convo): change hover bg-color
danny-avila Feb 3, 2024
7a9b25c
refactor: memoize groupedConversations and return as array of tuples,…
danny-avila Feb 3, 2024
ba150e9
style: rename Header NewChat Button -> HeaderNewChat, add NewChatIcon…
danny-avila Feb 3, 2024
b1f34f7
style(NewChat): add hover bg color
danny-avila Feb 3, 2024
044556a
style: cleanup comments, match ChatGPT nav styling, redesign search b…
danny-avila Feb 3, 2024
b712203
feat: add tests for conversation helpers and ensure no duplicate conv…
danny-avila Feb 4, 2024
9b8efd5
style: hover bg-color
danny-avila Feb 4, 2024
e6ce7f5
feat: alt-click on convo item to open conversation in new tab
danny-avila Feb 4, 2024
7bf11d7
chore: send error message when `gen_title` fails
danny-avila Feb 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/cache/getLogStores.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const tokenConfig = isEnabled(USE_REDIS) // ttl: 30 minutes
? new Keyv({ store: keyvRedis, ttl: 1800000 })
: new Keyv({ namespace: CacheKeys.TOKEN_CONFIG, ttl: 1800000 });

const genTitle = isEnabled(USE_REDIS) // ttl: 2 minutes
? new Keyv({ store: keyvRedis, ttl: 120000 })
: new Keyv({ namespace: CacheKeys.GEN_TITLE, ttl: 120000 });

const namespaces = {
[CacheKeys.CONFIG_STORE]: config,
pending_req,
Expand All @@ -39,6 +43,7 @@ const namespaces = {
registrations: createViolationInstance('registrations'),
logins: createViolationInstance('logins'),
[CacheKeys.TOKEN_CONFIG]: tokenConfig,
[CacheKeys.GEN_TITLE]: genTitle,
};

/**
Expand Down
6 changes: 3 additions & 3 deletions api/models/Conversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ module.exports = {
return { message: 'Error saving conversation' };
}
},
getConvosByPage: async (user, pageNumber = 1, pageSize = 14) => {
getConvosByPage: async (user, pageNumber = 1, pageSize = 25) => {
try {
const totalConvos = (await Conversation.countDocuments({ user })) || 1;
const totalPages = Math.ceil(totalConvos / pageSize);
const convos = await Conversation.find({ user })
.sort({ createdAt: -1 })
.sort({ updatedAt: -1 })
.skip((pageNumber - 1) * pageSize)
.limit(pageSize)
.lean();
Expand All @@ -45,7 +45,7 @@ module.exports = {
return { message: 'Error getting conversations' };
}
},
getConvosQueried: async (user, convoIds, pageNumber = 1, pageSize = 14) => {
getConvosQueried: async (user, convoIds, pageNumber = 1, pageSize = 25) => {
try {
if (!convoIds || convoIds.length === 0) {
return { conversations: [], pages: 1, pageNumber, pageSize };
Expand Down
26 changes: 26 additions & 0 deletions api/server/routes/convos.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const express = require('express');
const { CacheKeys } = require('librechat-data-provider');
const { getConvosByPage, deleteConvos } = require('~/models/Conversation');
const requireJwtAuth = require('~/server/middleware/requireJwtAuth');
const { sleep } = require('~/server/services/AssistantService');
const getLogStores = require('~/cache/getLogStores');
const { getConvo, saveConvo } = require('~/models');
const { logger } = require('~/config');

Expand Down Expand Up @@ -29,6 +32,29 @@ router.get('/:conversationId', async (req, res) => {
}
});

router.post('/gen_title', async (req, res) => {
const { conversationId } = req.body;
const titleCache = getLogStores(CacheKeys.GEN_TITLE);
const key = `${req.user.id}-${conversationId}`;
let title = await titleCache.get(key);

if (!title) {
await sleep(2500);
title = await titleCache.get(key);
}

if (title) {
await titleCache.delete(key);
res.status(200).json({ title });
} else {
res
.status(404)
.json({
message: 'Title not found or method not implemented for the conversation\'s endpoint',
});
}
});

router.post('/clear', async (req, res) => {
let filter = {};
const { conversationId, source } = req.body.arg;
Expand Down
1 change: 1 addition & 0 deletions api/server/services/AssistantService.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,5 +357,6 @@ module.exports = {
waitForRun,
getResponse,
handleRun,
sleep,
mapMessagesToSteps,
};
8 changes: 7 additions & 1 deletion api/server/services/Endpoints/openAI/addTitle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { saveConvo } = require('~/models');
const { CacheKeys } = require('librechat-data-provider');
const getLogStores = require('~/cache/getLogStores');
const { isEnabled } = require('~/server/utils');
const { saveConvo } = require('~/models');

const addTitle = async (req, { text, response, client }) => {
const { TITLE_CONVO = 'true' } = process.env ?? {};
Expand All @@ -16,7 +18,11 @@ const addTitle = async (req, { text, response, client }) => {
return;
}

const titleCache = getLogStores(CacheKeys.GEN_TITLE);
const key = `${req.user.id}-${response.conversationId}`;

const title = await client.titleConvo({ text, responseText: response?.text });
await titleCache.set(key, title);
await saveConvo(req.user.id, {
conversationId: response.conversationId,
title,
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Chat/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useOutletContext } from 'react-router-dom';
import type { ContextType } from '~/common';
import { EndpointsMenu, PresetsMenu, NewChat } from './Menus';
import { EndpointsMenu, PresetsMenu, HeaderNewChat } from './Menus';
import HeaderOptions from './Input/HeaderOptions';

export default function Header() {
const { navVisible } = useOutletContext<ContextType>();
return (
<div className="sticky top-0 z-10 flex h-14 w-full items-center justify-between bg-white/95 p-2 font-semibold dark:bg-gray-800/90 dark:text-white ">
<div className="hide-scrollbar flex items-center gap-2 overflow-x-auto">
{!navVisible && <NewChat />}
{!navVisible && <HeaderNewChat />}
<EndpointsMenu />
<HeaderOptions />
<PresetsMenu />
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Chat/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { getEndpointField } from '~/utils';
import { useLocalize } from '~/hooks';

export default function Landing({ Header }: { Header?: ReactNode }) {
const { data: endpointsConfig } = useGetEndpointsQuery();
const { conversation } = useChatContext();
const { data: endpointsConfig } = useGetEndpointsQuery();

const localize = useLocalize();
let { endpoint } = conversation ?? {};
if (
Expand Down
23 changes: 23 additions & 0 deletions client/src/components/Chat/Menus/HeaderNewChat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NewChatIcon } from '~/components/svg';
import { useChatContext } from '~/Providers';
import { useMediaQuery } from '~/hooks';

export default function HeaderNewChat() {
const { newConversation } = useChatContext();
const isSmallScreen = useMediaQuery('(max-width: 768px)');
if (isSmallScreen) {
return null;
}
return (
<button
data-testid="wide-header-new-chat-button"
type="button"
className="btn btn-neutral btn-small border-token-border-medium relative ml-2 flex hidden h-9 w-9 items-center justify-center whitespace-nowrap rounded-lg rounded-lg border focus:ring-0 focus:ring-offset-0 md:flex"
onClick={() => newConversation()}
>
<div className="flex w-full items-center justify-center gap-2">
<NewChatIcon />
</div>
</button>
);
}
36 changes: 0 additions & 36 deletions client/src/components/Chat/Menus/NewChat.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion client/src/components/Chat/Menus/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { default as EndpointsMenu } from './EndpointsMenu';
export { default as PresetsMenu } from './PresetsMenu';
export { default as NewChat } from './NewChat';
export { default as HeaderNewChat } from './HeaderNewChat';
22 changes: 22 additions & 0 deletions client/src/components/Chat/SearchView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { memo } from 'react';
import { useRecoilValue } from 'recoil';
import MessagesView from './Messages/MessagesView';
import store from '~/store';

import Header from './Header';

function SearchView() {
const searchResultMessagesTree = useRecoilValue(store.searchResultMessagesTree);

return (
<div className="relative flex w-full grow overflow-hidden bg-white dark:bg-gray-800">
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white pt-0 dark:bg-gray-800">
<div className="flex h-full flex-col" role="presentation" tabIndex={0}>
<MessagesView messagesTree={searchResultMessagesTree} Header={<Header />} />
</div>
</div>
</div>
);
}

export default memo(SearchView);
2 changes: 1 addition & 1 deletion client/src/components/Conversations/Conversation.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useRef } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { useUpdateConversationMutation } from 'librechat-data-provider/react-query';
import { useUpdateConversationMutation } from '~/data-provider';
import { useConversations, useConversation } from '~/hooks';
import { MinimalIcon } from '~/components/Endpoints';
import { NotificationSeverity } from '~/common';
Expand Down
65 changes: 48 additions & 17 deletions client/src/components/Conversations/Conversations.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Convo from './Convo';
import Conversation from './Conversation';
import { useMemo } from 'react';
import { parseISO, isToday } from 'date-fns';
import { useLocation } from 'react-router-dom';
import { TConversation } from 'librechat-data-provider';
import { groupConversationsByDate } from '~/utils';
import Conversation from './Conversation';
import Convo from './Convo';

export default function Conversations({
conversations,
Expand All @@ -15,22 +18,50 @@ export default function Conversations({
const location = useLocation();
const { pathname } = location;
const ConvoItem = pathname.includes('chat') ? Conversation : Convo;
const groupedConversations = useMemo(
() => groupConversationsByDate(conversations),
[conversations],
);
const firstTodayConvoId = conversations.find((convo) =>
isToday(parseISO(convo.updatedAt)),
)?.conversationId;

return (
<>
{conversations &&
conversations.length > 0 &&
conversations.map((convo: TConversation, i) => {
return (
<ConvoItem
key={convo.conversationId}
conversation={convo}
retainView={moveToTop}
toggleNav={toggleNav}
i={i}
/>
);
})}
</>
<div className="text-token-text-primary flex flex-col gap-2 pb-2 text-sm">
<div>
<span>
{groupedConversations.map(([groupName, convos]) => (
<div key={groupName}>
<div
style={{
color: '#aaa',
fontSize: '0.7rem',
marginTop: '20px',
marginBottom: '5px',
paddingLeft: '10px',
}}
>
{groupName}
</div>
{convos.map((convo, i) => (
<ConvoItem
key={`${groupName}-${convo.conversationId}-${i}`}
isLatestConvo={convo.conversationId === firstTodayConvoId}
conversation={convo}
retainView={moveToTop}
toggleNav={toggleNav}
/>
))}
<div
style={{
marginTop: '5px',
marginBottom: '5px',
}}
/>
</div>
))}
</span>
</div>
</div>
);
}
Loading
Loading