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 design: Nav bar with Infinite Scroll and Group Convo by Date #1565

Closed
wants to merge 7 commits into from
4 changes: 2 additions & 2 deletions api/models/Conversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 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);
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
5 changes: 5 additions & 0 deletions client/src/components/Chat/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { icons } from './Menus/Endpoints/Icons';
import { useChatContext } from '~/Providers';
import { getEndpointField } from '~/utils';
import { useLocalize } from '~/hooks';
import { useSetRecoilState, useRecoilValue } from 'recoil';
import store from '~/store';

export default function Landing({ Header }: { Header?: ReactNode }) {
const { data: endpointsConfig } = useGetEndpointsQuery();
const { conversation } = useChatContext();
const setEndpointSelected = useSetRecoilState(store.endpointSelected);
const localize = useLocalize();
let { endpoint } = conversation ?? {};
if (
Expand All @@ -25,6 +28,8 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
const iconKey = endpointType ? 'unknown' : endpoint ?? 'unknown';
const Icon = icons[iconKey];

setEndpointSelected(endpoint?.toString() ?? '');

return (
<div className="relative h-full">
<div className="absolute left-0 right-0">{Header && Header}</div>
Expand Down
90 changes: 82 additions & 8 deletions client/src/components/Conversations/Conversations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,58 @@ import Convo from './Convo';
import Conversation from './Conversation';
import { useLocation } from 'react-router-dom';
import { TConversation } from 'librechat-data-provider';
import { parseISO, isToday, isWithinInterval, subDays, getYear } from 'date-fns';

const getGroupName = (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(); // Returns the year for anything older than 30 days
};

// Function to group conversations
const groupConversationsByDate = (conversations) => {
if (!Array.isArray(conversations)) {
// Handle the case where conversations is not an array
return {};
}
const groups = conversations.reduce((acc, conversation) => {
const date = parseISO(conversation.updatedAt);
const groupName = getGroupName(date);
if (!acc[groupName]) {
acc[groupName] = [];
}
acc[groupName].push(conversation);
return acc;
}, {});

// Ensures groups are ordered correctly

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 sortedGroups;
};

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

return (
<>
{conversations &&
conversations.length > 0 &&
conversations.map((convo: TConversation, i) => {
return (
<div className="flex-1 flex-col overflow-y-auto">
{Object.entries(groupedConversations).map(([groupName, convos]) => (
<div key={groupName}>
<div
style={{
color: '#aaa', // Cor do texto
fontSize: '0.7rem', // Tamanho da fonte
marginTop: '20px', // Espaço acima do cabeçalho
marginBottom: '5px', // Espaço abaixo do cabeçalho
paddingLeft: '10px', // Espaçamento à esquerda para alinhamento com as conversas
}}
>
{groupName}
</div>
{convos.map((convo, i) => (
<ConvoItem
key={convo.conversationId}
isFirstTodayConvo={convo.conversationId === firstTodayConvoId}
conversation={convo}
retainView={moveToTop}
toggleNav={toggleNav}
i={i}
/>
);
})}
</>
))}
<div
style={{
marginTop: '5px',
marginBottom: '5px',
}}
></div>
</div>
))}
</div>
);
}
24 changes: 17 additions & 7 deletions client/src/components/Conversations/Convo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ import store from '~/store';

type KeyEvent = KeyboardEvent<HTMLInputElement>;

export default function Conversation({ conversation, retainView, toggleNav, i }) {
export default function Conversation({
conversation,
retainView,
toggleNav,
i,
isFirstTodayConvo,
}) {
const { conversationId: currentConvoId } = useParams();
const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? '');
const activeConvos = useRecoilValue(store.allConversationsSelector);
Expand Down Expand Up @@ -109,22 +115,21 @@ export default function Conversation({ conversation, retainView, toggleNav, i })

const aProps = {
className:
'animate-flash group relative flex cursor-pointer items-center gap-3 break-all rounded-md bg-gray-900 py-3 px-3 pr-14 hover:bg-gray-900',
'group relative rounded-lg active:opacity-50 flex cursor-pointer items-center mt-2 gap-3 break-all rounded-lg bg-gray-800 py-2 px-2',
};

const activeConvo =
currentConvoId === conversationId ||
(i === 0 && currentConvoId === 'new' && activeConvos[0] && activeConvos[0] !== 'new');
currentConvoId === conversationId || (isFirstTodayConvo && currentConvoId === 'new');

if (!activeConvo) {
aProps.className =
'group relative flex cursor-pointer items-center gap-3 break-all rounded-md py-3 px-3 hover:bg-gray-900 hover:pr-4';
'group relative rounded-lg active:opacity-50 flex cursor-pointer items-center mt-2 gap-3 break-all rounded-lg py-2 px-2 hover:bg-gray-800';
}

return (
<a data-testid="convo-item" onClick={() => clickHandler()} {...aProps} title={title}>
{icon}
<div className="relative line-clamp-1 max-h-5 flex-1 text-ellipsis break-all">
<div className="relative line-clamp-1 max-h-5 flex-1 grow overflow-hidden">
{renaming === true ? (
<input
ref={inputRef}
Expand All @@ -139,6 +144,11 @@ export default function Conversation({ conversation, retainView, toggleNav, i })
title
)}
</div>
{activeConvo ? (
<div className="absolute bottom-0 right-1 top-0 w-20 bg-gradient-to-l from-gray-800 from-60% to-transparent"></div>
) : (
<div className="from--gray-800 absolute bottom-0 right-0 top-0 w-2 bg-gradient-to-l from-0% to-transparent group-hover:w-1 group-hover:from-60%"></div>
)}
{activeConvo ? (
<div className="visible absolute right-1 z-10 flex text-gray-400">
<RenameButton renaming={renaming} onRename={onRename} renameHandler={renameHandler} />
Expand All @@ -150,7 +160,7 @@ export default function Conversation({ conversation, retainView, toggleNav, i })
/>
</div>
) : (
<div className="absolute inset-y-0 right-0 z-10 w-8 rounded-r-md bg-gradient-to-l from-black group-hover:from-gray-900" />
<div className="absolute bottom-0 right-0 top-0 w-20 rounded-lg bg-gradient-to-l from-black from-0% to-transparent group-hover:from-gray-800" />
)}
</a>
);
Expand Down
Loading
Loading