diff --git a/api/models/Conversation.js b/api/models/Conversation.js index f1aa7bfe718..53e557dc898 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -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); @@ -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 }; diff --git a/client/src/components/Chat/Landing.tsx b/client/src/components/Chat/Landing.tsx index 1cc5df08e26..4b764ef7f07 100644 --- a/client/src/components/Chat/Landing.tsx +++ b/client/src/components/Chat/Landing.tsx @@ -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 ( @@ -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 (
{Header && Header}
diff --git a/client/src/components/Conversations/Conversations.tsx b/client/src/components/Conversations/Conversations.tsx index 5ce4f1f8b9e..fe81f097b8a 100644 --- a/client/src/components/Conversations/Conversations.tsx +++ b/client/src/components/Conversations/Conversations.tsx @@ -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, @@ -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 ( +
+ {Object.entries(groupedConversations).map(([groupName, convos]) => ( +
+
+ {groupName} +
+ {convos.map((convo, i) => ( - ); - })} - + ))} +
+
+ ))} +
); } diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index adf30e2851d..b53f5602229 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -18,7 +18,13 @@ import store from '~/store'; type KeyEvent = KeyboardEvent; -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); @@ -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 ( clickHandler()} {...aProps} title={title}> {icon} -
+
{renaming === true ? ( + {activeConvo ? ( +
+ ) : ( +
+ )} {activeConvo ? (
@@ -150,7 +160,7 @@ export default function Conversation({ conversation, retainView, toggleNav, i }) />
) : ( -
+
)} ); diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index 86a3e335971..9917bee547d 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -1,7 +1,10 @@ -import { useSearchQuery, useGetConversationsQuery } from 'librechat-data-provider/react-query'; +import { + useSearchInfiniteQuery, + useConversationsInfiniteQuery, +} from 'librechat-data-provider/react-query'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useCallback, useEffect, useRef, useState } from 'react'; -import type { TConversation, TSearchResults } from 'librechat-data-provider'; +import type { ConversationListResponse, TConversation } from 'librechat-data-provider'; import { useAuthContext, useMediaQuery, @@ -10,7 +13,7 @@ import { useLocalStorage, } from '~/hooks'; import { TooltipProvider, Tooltip } from '~/components/ui'; -import { Conversations, Pages } from '../Conversations'; +import { Conversations } from '../Conversations'; import { Spinner } from '~/components/svg'; import SearchBar from './SearchBar'; import NavToggle from './NavToggle'; @@ -28,6 +31,7 @@ export default function Nav({ navVisible, setNavVisible }) { const scrollPositionRef = useRef(null); const isSmallScreen = useMediaQuery('(max-width: 768px)'); const [newUser, setNewUser] = useLocalStorage('newUser', true); + const endpointSelected = useRecoilValue(store.endpointSelected); useEffect(() => { if (isSmallScreen) { @@ -37,18 +41,13 @@ export default function Nav({ navVisible, setNavVisible }) { } }, [isSmallScreen]); - const [conversations, setConversations] = useState([]); + const [, setConversations] = useState([]); // current page const [pageNumber, setPageNumber] = useState(1); // total pages - const [pages, setPages] = useState(1); - - // data provider - const getConversationsQuery = useGetConversationsQuery(pageNumber + '', { - enabled: isAuthenticated, - }); // search + const searchQuery = useRecoilValue(store.searchQuery); const isSearchEnabled = useRecoilValue(store.isSearchEnabled); const isSearching = useRecoilValue(store.isSearching); @@ -56,25 +55,58 @@ export default function Nav({ navVisible, setNavVisible }) { // current conversation const conversation = useRecoilValue(store.conversation); + const { conversationId } = conversation || {}; const setSearchResultMessages = useSetRecoilState(store.searchResultMessages); const refreshConversationsHint = useRecoilValue(store.refreshConversationsHint); const { refreshConversations } = useConversations(); - const [isFetching, setIsFetching] = useState(false); + const queryParameters = searchQuery + ? { pageNumber: pageNumber.toString(), searchQuery } + : { pageNumber: pageNumber.toString() }; + + // Define as opções de configuração do hook + const queryConfig = { + enabled: isAuthenticated, + }; + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useSearchInfiniteQuery( + { pageNumber: pageNumber.toString(), searchQuery: searchQuery }, + { enabled: isAuthenticated }, + ); + + const conversations = data?.pages.flatMap((page) => page.conversations) || []; + + const handleScroll = useCallback(() => { + if (containerRef.current) { + const { scrollTop, clientHeight, scrollHeight } = containerRef.current; + const nearBottomOfList = scrollTop + clientHeight >= scrollHeight * 0.8; // 80% scroll + + if (nearBottomOfList && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); // Adicione outras dependências se necessário + + useEffect(() => { + const container = containerRef.current; + if (container) { + container.addEventListener('scroll', handleScroll); + return () => container.removeEventListener('scroll', handleScroll); + } + }, [handleScroll]); + const useAppropriateInfiniteQuery = searchQuery + ? useSearchInfiniteQuery + : useConversationsInfiniteQuery; - const searchQueryFn = useSearchQuery(searchQuery, pageNumber + '', { - enabled: !!(!!searchQuery && searchQuery.length > 0 && isSearchEnabled && isSearching), - }); + const getConversationsQuery = useAppropriateInfiniteQuery(queryParameters, queryConfig); - const onSearchSuccess = useCallback((data: TSearchResults, expectedPage?: number) => { + const onSearchSuccess = useCallback((data: ConversationListResponse, expectedPage?: number) => { const res = data; setConversations(res.conversations); if (expectedPage) { setPageNumber(expectedPage); } - setPages(Number(res.pages)); - setIsFetching(false); searchPlaceholderConversation(); setSearchResultMessages(res.messages); /* disabled due recoil methods not recognized as state setters */ @@ -83,13 +115,10 @@ export default function Nav({ navVisible, setNavVisible }) { useEffect(() => { //we use isInitialLoading here instead of isLoading because query is disabled by default - if (searchQueryFn.isInitialLoading) { - setIsFetching(true); - } else if (searchQueryFn.data) { - onSearchSuccess(searchQueryFn.data); + if (getConversationsQuery.data) { + onSearchSuccess(getConversationsQuery.data.pages[0]); } - }, [searchQueryFn.data, searchQueryFn.isInitialLoading, onSearchSuccess]); - + }, [getConversationsQuery.data, getConversationsQuery.isInitialLoading, onSearchSuccess]); const clearSearch = () => { setPageNumber(1); refreshConversations(); @@ -105,23 +134,15 @@ export default function Nav({ navVisible, setNavVisible }) { } }, [containerRef, scrollPositionRef]); - const nextPage = async () => { - moveToTop(); - setPageNumber(pageNumber + 1); - }; - - const previousPage = async () => { - moveToTop(); - setPageNumber(pageNumber - 1); - }; - useEffect(() => { - if (getConversationsQuery.data) { + if (data) { if (isSearching) { return; } - let { conversations, pages } = getConversationsQuery.data; + let { conversations } = data.pages[0]; + let { pages } = data.pages[0]; pages = Number(pages); + if (pageNumber > pages) { setPageNumber(pages); } else { @@ -130,18 +151,19 @@ export default function Nav({ navVisible, setNavVisible }) { (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), ); } + setConversations(conversations); - setPages(pages); + // setPages(pages); } } - }, [getConversationsQuery.isSuccess, getConversationsQuery.data, isSearching, pageNumber]); + }, [data, pageNumber, isSearching]); useEffect(() => { if (!isSearching) { getConversationsQuery.refetch(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pageNumber, conversationId, refreshConversationsHint]); + }, [pageNumber, conversationId, refreshConversationsHint, conversation, data]); const toggleNavVisible = () => { setNavVisible((prev: boolean) => !prev); @@ -178,42 +200,36 @@ export default function Nav({ navVisible, setNavVisible }) {
-
diff --git a/client/src/components/Nav/NewChat.tsx b/client/src/components/Nav/NewChat.tsx index b0ebb195dce..03bafecbbc1 100644 --- a/client/src/components/Nav/NewChat.tsx +++ b/client/src/components/Nav/NewChat.tsx @@ -1,6 +1,25 @@ import { useLocalize, useConversation, useNewConvo, useOriginNavigate } from '~/hooks'; +import { MinimalIcon, Icon } from '~/components/Endpoints'; +import { + Plugin, + GPTIcon, + AnthropicIcon, + AzureMinimalIcon, + CustomMinimalIcon, + PaLMIcon, + CodeyIcon, + GoogleIconChat, +} from '~/components/svg'; +import ChatGPT from '../Input/ModelSelect/ChatGPT'; +import BingAI from '../Input/ModelSelect/BingAI'; -export default function NewChat({ toggleNav }: { toggleNav: () => void }) { +export default function NewChat({ + endpoint, + toggleNav, +}: { + endpoint: string; + toggleNav: () => void; +}) { const { newConversation } = useConversation(); const { newConversation: newConvo } = useNewConvo(); const navigate = useOriginNavigate(); @@ -16,29 +35,73 @@ export default function NewChat({ toggleNav }: { toggleNav: () => void }) { } }; + const Icon = ({ endpoint }) => { + let IconComponent; + + if (endpoint === 'openAI' || endpoint === 'azureOpenAI') { + IconComponent = ; + } else if (endpoint === 'anthropic') { + IconComponent = ; + } else if (endpoint === 'chatGPTBrowser') { + IconComponent = ; + } else if (endpoint === 'bingAI') { + IconComponent = ; + } else if (endpoint === 'google') { + IconComponent = ; + } else if (endpoint === 'plugin') { + IconComponent = ; + } else if (endpoint === 'OpenRouter') { + IconComponent = ( + description + ); + } else if (endpoint === 'Mistral') { + IconComponent = ( + description + ); + } + return
{IconComponent}
; + }; + return ( +
+
+
+ +
+
+ {localize('com_ui_new_chat')} +
- - + - {localize('com_ui_new_chat')}
); } diff --git a/client/src/components/svg/GoogleIconChat.tsx b/client/src/components/svg/GoogleIconChat.tsx new file mode 100644 index 00000000000..4c2f4eb8fca --- /dev/null +++ b/client/src/components/svg/GoogleIconChat.tsx @@ -0,0 +1,26 @@ +import { cn } from '~/utils/'; + +export default function Google({ + size = 25, + className = '', +}: { + size?: number; + className?: string; +}) { + const unit = '41'; + const height = size; + const width = size; + return ( + + + + ); +} diff --git a/client/src/components/svg/Spinner.tsx b/client/src/components/svg/Spinner.tsx index 3e60397cd60..eb473fe3e79 100644 --- a/client/src/components/svg/Spinner.tsx +++ b/client/src/components/svg/Spinner.tsx @@ -4,7 +4,7 @@ import { cn } from '~/utils/'; export default function Spinner({ className = 'm-auto' }) { return ( { const convoData = queryClient.getQueryData([ QueryKeys.allConversations, - { pageNumber: '1', active: true }, + { pageNumber: '1' }, ]) ?? { conversations: [] as TConversation[], pageNumber: '1', pages: 1, pageSize: 14 }; - - let { conversations: convos, pageSize = 14 } = convoData; + const queryKey = [QueryKeys.allConversations, { pageNumber: '1' }]; + const currentData = + queryClient.getQueryData>(queryKey); + let convos = convoData.pages[0].conversations; + let pageSize = convoData.pages[0].pageSize || 14; // Default to 14 if pageSize is not set pageSize = Number(pageSize); convos = convos.filter((c) => c.conversationId !== convo.conversationId); convos = convos.length < pageSize ? convos : convos.slice(0, -1); @@ -82,7 +87,7 @@ export default function useChatHelpers(index = 0, paramId: string | undefined) { ]; queryClient.setQueryData( - [QueryKeys.allConversations, { pageNumber: '1', active: true }], + [QueryKeys.allConversations, { pageNumber: '1' }], { ...convoData, conversations, @@ -93,7 +98,7 @@ export default function useChatHelpers(index = 0, paramId: string | undefined) { ); const invalidateConvos = useCallback(() => { - queryClient.invalidateQueries([QueryKeys.allConversations, { active: true }]); + queryClient.invalidateQueries([QueryKeys.allConversations]); }, [queryClient]); const getMessages = useCallback(() => { diff --git a/client/src/store/conversation.ts b/client/src/store/conversation.ts index ee12d714963..adf2e91c98c 100644 --- a/client/src/store/conversation.ts +++ b/client/src/store/conversation.ts @@ -32,10 +32,16 @@ const messagesSiblingIdxFamily = atomFamily({ default: 0, }); +export const endpointSelected = atom({ + key: 'endpointSelected', // unique ID (with respect to other atoms/selectors) + default: 'defaultValue', // default value (aka initial value) +}); + export default { messages, conversation, messagesTree, latestMessage, messagesSiblingIdxFamily, + endpointSelected, }; diff --git a/client/src/style.css b/client/src/style.css index c94212d99a7..4a30683c8ff 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -1051,27 +1051,42 @@ button { } ::-webkit-scrollbar { - height: 0.85em; + height: 0.1em; width: 0.5rem; } +.scrollbar-trigger:hover ::-webkit-scrollbar-thumb { + visibility: hide; +} + ::-webkit-scrollbar-thumb { - --tw-border-opacity: 1; - /* background-color: rgba(217,217,227,.8); Original */ - background-color: rgba(217, 217, 227, 0.26); - border-color: rgba(255, 255, 255, var(--tw-border-opacity)); + background-color: hsla(0,0%,100%,.1); border-radius: 9999px; - border-width: 1px; } .scrollbar-transparent::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0.1); } +.bg-token-surface-secondary { + background-color: #f7f7f8; + background-color: var(--surface-secondary); +} + +.from-token-surface-secondary { + --tw-gradient-from: var(--surface-secondary) var(--tw-gradient-from-position); + --tw-gradient-to: hsla(0,0%,100%,0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from),var(--tw-gradient-to); +} + ::-webkit-scrollbar-track { background-color: transparent; border-radius: 9999px; } +::-webkit-scrollbar-thumb:hover { + background-color: hsla(0,0%,100%,.3); + +} body, html { diff --git a/package-lock.json b/package-lock.json index f8367590737..f4ac6872a7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,9 @@ "client", "packages/*" ], + "dependencies": { + "date-fns": "^3.3.1" + }, "devDependencies": { "@playwright/test": "^1.38.1", "@typescript-eslint/eslint-plugin": "^5.62.0", @@ -12000,6 +12003,15 @@ "node": ">=12" } }, + "node_modules/date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 526a6d289ad..ed9a7ee27e5 100644 --- a/package.json +++ b/package.json @@ -98,5 +98,8 @@ "admin/", "packages/" ] + }, + "dependencies": { + "date-fns": "^3.3.1" } } diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index f6b42214041..c8cc5167bbe 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -1,6 +1,7 @@ import * as f from './types/files'; import * as m from './types/mutations'; import * as a from './types/assistants'; +import * as c from './types/conversations'; import * as t from './types'; import * as s from './schemas'; import request from './request'; @@ -209,3 +210,26 @@ export const deleteFiles = async (files: f.BatchFile[]): Promise => { + // Assuming params has a pageNumber property + const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided + return request.get(endpoints.conversations(pageNumber)); +}; + +export const listConversationsByQuery = ( + params?: c.ConversationListParams & { searchQuery?: string }, +): Promise => { + const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided + const searchQuery = params?.searchQuery || ''; // If no search query is provided, default to an empty string + // Update the endpoint to handle a search query + if (searchQuery !== '') { + return request.get(endpoints.search(searchQuery, pageNumber)); + } else { + return request.get(endpoints.conversations(pageNumber)); + } +}; diff --git a/packages/data-provider/src/index.ts b/packages/data-provider/src/index.ts index 318829adafa..8e72fb7cf4d 100644 --- a/packages/data-provider/src/index.ts +++ b/packages/data-provider/src/index.ts @@ -5,6 +5,7 @@ export * from './parsers'; /* types (exports schemas from `./types` as they contain needed in other defs) */ export * from './types'; export * from './types/assistants'; +export * from './types/conversations'; export * from './types/files'; export * from './types/mutations'; /* query/mutation keys */ diff --git a/packages/data-provider/src/react-query/conversations.ts b/packages/data-provider/src/react-query/conversations.ts new file mode 100644 index 00000000000..ae419dd17c6 --- /dev/null +++ b/packages/data-provider/src/react-query/conversations.ts @@ -0,0 +1,45 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import type { UseInfiniteQueryOptions } from '@tanstack/react-query'; +import { QueryKeys } from '../keys'; +import * as c from '../types/conversations'; +import * as dataService from '../data-service'; + +export const useSearchInfiniteQuery = ( + params?: c.ConversationListParams & { searchQuery?: string }, + config?: UseInfiniteQueryOptions, +) => { + return useInfiniteQuery( + [QueryKeys.allConversations, params], // Include the searchQuery in the query key + ({ pageParam = '1' }) => + dataService.listConversationsByQuery({ ...params, pageNumber: pageParam }), + { + getNextPageParam: (lastPage) => { + const currentPageNumber = Number(lastPage.pageNumber); + const totalPages = Number(lastPage.pages); + return currentPageNumber < totalPages ? currentPageNumber + 1 : undefined; + }, + ...config, + }, + ); +}; + +export const useConversationsInfiniteQuery = ( + params?: c.ConversationListParams, + config?: UseInfiniteQueryOptions, +) => { + return useInfiniteQuery( + [QueryKeys.allConversations, params], + ({ pageParam = '' }) => + dataService.listConversations({ ...params, pageNumber: pageParam.toString() }), + { + getNextPageParam: (lastPage) => { + const currentPageNumber = Number(lastPage.pageNumber); + const totalPages = Number(lastPage.pages); // Convert totalPages to a number + // If the current page number is less than total pages, return the next page number + return currentPageNumber < totalPages ? currentPageNumber + 1 : undefined; + }, + ...config, + }, + ); +}; diff --git a/packages/data-provider/src/react-query/index.ts b/packages/data-provider/src/react-query/index.ts index 54ab962a015..3564a903029 100644 --- a/packages/data-provider/src/react-query/index.ts +++ b/packages/data-provider/src/react-query/index.ts @@ -1,2 +1,3 @@ export * from './react-query-service'; export * from './assistants'; +export * from './conversations'; diff --git a/packages/data-provider/src/types/conversations.ts b/packages/data-provider/src/types/conversations.ts new file mode 100644 index 00000000000..df7a3435f9c --- /dev/null +++ b/packages/data-provider/src/types/conversations.ts @@ -0,0 +1,42 @@ +// Define a type for individual conversation details +import type { TMessage, TConversation } from '../schemas'; +export type Conversation = { + id: string; + createdAt: number; + participants: string[]; // Array of participant identifiers + lastMessage: string; + conversations: TConversation[]; + + // Additional fields as required by your application... +}; + +// Parameters for creating or updating a conversation +export type ConversationCreateParams = { + participants: string[]; + // Other fields as needed... +}; + +export type ConversationUpdateParams = { + // Fields that can be updated... +}; + +// Parameters for listing conversations (e.g., for pagination) +export type ConversationListParams = { + limit?: number; + before?: string | null; + after?: string | null; + order?: 'asc' | 'desc'; + pageNumber: string; // Add this line + conversationId?: string; +}; + +// Type for the response from the conversation list API +export type ConversationListResponse = { + conversations: TConversation[]; + pageNumber: string; + pageSize: string | number; + pages: string | number; + messages: TMessage[]; +}; + +// Additional types as needed...