mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
🧹 fix: Resolve Unarchive Conversation Bug, Archive Pagination (#4189)
* feat: add cleanup service for 'bugged' conversations (empty/nullish conversationIds) * fix(ArchivedChatsTable): typing and minor styling issues * fix: properly archive conversations * fix: archive convo application crash * chore: remove unused `useEffect` * fix: add basic navigation * chore: typing
This commit is contained in:
parent
2d62eca612
commit
4328a25b6b
10 changed files with 202 additions and 69 deletions
|
|
@ -31,9 +31,39 @@ const getConvo = async (user, conversationId) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteNullOrEmptyConversations = async () => {
|
||||||
|
try {
|
||||||
|
const filter = {
|
||||||
|
$or: [
|
||||||
|
{ conversationId: null },
|
||||||
|
{ conversationId: '' },
|
||||||
|
{ conversationId: { $exists: false } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await Conversation.deleteMany(filter);
|
||||||
|
|
||||||
|
// Delete associated messages
|
||||||
|
const messageDeleteResult = await deleteMessages(filter);
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`[deleteNullOrEmptyConversations] Deleted ${result.deletedCount} conversations and ${messageDeleteResult.deletedCount} messages`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversations: result,
|
||||||
|
messages: messageDeleteResult,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[deleteNullOrEmptyConversations] Error deleting conversations', error);
|
||||||
|
throw new Error('Error deleting conversations with null or empty conversationId');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Conversation,
|
Conversation,
|
||||||
searchConversation,
|
searchConversation,
|
||||||
|
deleteNullOrEmptyConversations,
|
||||||
/**
|
/**
|
||||||
* Saves a conversation to the database.
|
* Saves a conversation to the database.
|
||||||
* @param {Object} req - The request object.
|
* @param {Object} req - The request object.
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,14 @@ router.post('/clear', async (req, res) => {
|
||||||
router.post('/update', async (req, res) => {
|
router.post('/update', async (req, res) => {
|
||||||
const update = req.body.arg;
|
const update = req.body.arg;
|
||||||
|
|
||||||
|
if (!update.conversationId) {
|
||||||
|
return res.status(400).json({ error: 'conversationId is required' });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbResponse = await saveConvo(req, update, { context: 'POST /api/convos/update' });
|
const dbResponse = await saveConvo(req, update, {
|
||||||
|
context: `POST /api/convos/update ${update.conversationId}`,
|
||||||
|
});
|
||||||
res.status(201).json(dbResponse);
|
res.status(201).json(dbResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error updating conversation', error);
|
logger.error('Error updating conversation', error);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const { loadDefaultInterface } = require('./start/interface');
|
||||||
const { azureConfigSetup } = require('./start/azureOpenAI');
|
const { azureConfigSetup } = require('./start/azureOpenAI');
|
||||||
const { loadAndFormatTools } = require('./ToolService');
|
const { loadAndFormatTools } = require('./ToolService');
|
||||||
const { initializeRoles } = require('~/models/Role');
|
const { initializeRoles } = require('~/models/Role');
|
||||||
|
const { cleanup } = require('./cleanup');
|
||||||
const paths = require('~/config/paths');
|
const paths = require('~/config/paths');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -17,6 +18,7 @@ const paths = require('~/config/paths');
|
||||||
* @param {Express.Application} app - The Express application object.
|
* @param {Express.Application} app - The Express application object.
|
||||||
*/
|
*/
|
||||||
const AppService = async (app) => {
|
const AppService = async (app) => {
|
||||||
|
cleanup();
|
||||||
await initializeRoles();
|
await initializeRoles();
|
||||||
/** @type {TCustomConfig}*/
|
/** @type {TCustomConfig}*/
|
||||||
const config = (await loadCustomConfig()) ?? {};
|
const config = (await loadCustomConfig()) ?? {};
|
||||||
|
|
|
||||||
13
api/server/services/cleanup.js
Normal file
13
api/server/services/cleanup.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
const { logger } = require('~/config');
|
||||||
|
const { deleteNullOrEmptyConversations } = require('~/models/Conversation');
|
||||||
|
const cleanup = async () => {
|
||||||
|
try {
|
||||||
|
await deleteNullOrEmptyConversations();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[cleanup] Error during app cleanup', error);
|
||||||
|
} finally {
|
||||||
|
logger.debug('Startup cleanup complete');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { cleanup };
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { OGDialog, OGDialogTrigger, Button } from '~/components';
|
import { OGDialog, OGDialogTrigger, Button } from '~/components';
|
||||||
|
|
||||||
import ArchivedChatsTable from './ArchivedChatsTable';
|
import ArchivedChatsTable from './ArchivedChatsTable';
|
||||||
|
|
||||||
export default function ArchivedChats() {
|
export default function ArchivedChats() {
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,80 @@
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { useConversationsInfiniteQuery } from '~/data-provider';
|
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
ChevronRight,
|
|
||||||
ChevronLeft,
|
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
// ChevronsLeft,
|
||||||
|
// ChevronsRight,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
ArchiveRestore,
|
ArchiveRestore,
|
||||||
ChevronsRight,
|
|
||||||
ChevronsLeft,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { TConversation } from 'librechat-data-provider';
|
import type { TConversation } from 'librechat-data-provider';
|
||||||
import { useAuthContext, useLocalize, useArchiveHandler } from '~/hooks';
|
|
||||||
import { DeleteConversationDialog } from '~/components/Conversations/ConvoOptions';
|
|
||||||
import {
|
import {
|
||||||
TooltipAnchor,
|
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
Input,
|
||||||
|
Button,
|
||||||
|
TableRow,
|
||||||
|
Skeleton,
|
||||||
|
OGDialog,
|
||||||
|
Separator,
|
||||||
TableCell,
|
TableCell,
|
||||||
|
TableBody,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TooltipAnchor,
|
||||||
Separator,
|
|
||||||
Skeleton,
|
|
||||||
Button,
|
|
||||||
Input,
|
|
||||||
OGDialog,
|
|
||||||
OGDialogTrigger,
|
OGDialogTrigger,
|
||||||
} from '~/components';
|
} from '~/components';
|
||||||
|
import { useConversationsInfiniteQuery, useArchiveConvoMutation } from '~/data-provider';
|
||||||
|
import { DeleteConversationDialog } from '~/components/Conversations/ConvoOptions';
|
||||||
|
import { useAuthContext, useLocalize } from '~/hooks';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
export default function ArchivedChatsTable() {
|
export default function ArchivedChatsTable() {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { isAuthenticated } = useAuthContext();
|
const { isAuthenticated } = useAuthContext();
|
||||||
const [conversationId, setConversationId] = useState<string | null>(null);
|
const [isOpened, setIsOpened] = useState(false);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
|
||||||
|
|
||||||
const { data, isLoading, refetch } = useConversationsInfiniteQuery(
|
const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } =
|
||||||
{ pageNumber: currentPage.toString(), limit: 10, isArchived: true },
|
useConversationsInfiniteQuery(
|
||||||
|
{ pageNumber: currentPage.toString(), isArchived: true },
|
||||||
{ enabled: isAuthenticated && isOpened },
|
{ enabled: isAuthenticated && isOpened },
|
||||||
);
|
);
|
||||||
|
const mutation = useArchiveConvoMutation();
|
||||||
|
const handleUnarchive = useCallback(
|
||||||
|
(conversationId: string) => {
|
||||||
|
mutation.mutate({ conversationId, isArchived: false });
|
||||||
|
},
|
||||||
|
[mutation],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const conversations = useMemo(
|
||||||
if (data) {
|
() => data?.pages[currentPage - 1]?.conversations ?? [],
|
||||||
setTotalPages(Math.ceil(Number(data.pages)));
|
[data, currentPage],
|
||||||
|
);
|
||||||
|
const totalPages = useMemo(() => Math.ceil(Number(data?.pages[0].pages ?? 1)) ?? 1, [data]);
|
||||||
|
|
||||||
|
const handleChatClick = useCallback((conversationId: string) => {
|
||||||
|
if (!conversationId) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const archiveHandler = useArchiveHandler(conversationId ?? '', false, () => {
|
|
||||||
refetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleChatClick = useCallback((conversationId) => {
|
|
||||||
window.open(`/c/${conversationId}`, '_blank');
|
window.open(`/c/${conversationId}`, '_blank');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePageChange = useCallback((newPage) => {
|
const handlePageChange = useCallback(
|
||||||
|
(newPage: number) => {
|
||||||
setCurrentPage(newPage);
|
setCurrentPage(newPage);
|
||||||
}, []);
|
if (!(hasNextPage ?? false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchNextPage({ pageParam: newPage });
|
||||||
|
},
|
||||||
|
[fetchNextPage, hasNextPage],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSearch = useCallback((query) => {
|
const handleSearch = useCallback((query: string) => {
|
||||||
setSearchQuery(query);
|
setSearchQuery(query);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -86,16 +98,14 @@ export default function ArchivedChatsTable() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || isFetchingNextPage) {
|
||||||
return <div className="text-gray-300">{skeletons}</div>;
|
return <div className="text-text-secondary">{skeletons}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data || data.pages.length === 0 || data.pages[0].conversations.length === 0) {
|
if (!data || (conversations.length === 0 && totalPages === 0)) {
|
||||||
return <div className="text-gray-300">{localize('com_nav_archived_chats_empty')}</div>;
|
return <div className="text-text-secondary">{localize('com_nav_archived_chats_empty')}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversations = data.pages.flatMap((page) => page.conversations);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -112,7 +122,7 @@ export default function ArchivedChatsTable() {
|
||||||
placeholder={localize('com_nav_search_placeholder')}
|
placeholder={localize('com_nav_search_placeholder')}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
className="w-full border-none"
|
className="w-full border-none placeholder:text-text-secondary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
@ -137,9 +147,16 @@ export default function ArchivedChatsTable() {
|
||||||
<TableRow key={conversation.conversationId} className="hover:bg-transparent">
|
<TableRow key={conversation.conversationId} className="hover:bg-transparent">
|
||||||
<TableCell className="flex items-center py-3 text-text-primary">
|
<TableCell className="flex items-center py-3 text-text-primary">
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="flex"
|
className="flex"
|
||||||
aria-label="Open conversation in a new tab"
|
aria-label="Open conversation in a new tab"
|
||||||
onClick={() => handleChatClick(conversation.conversationId)}
|
onClick={() => {
|
||||||
|
const conversationId = conversation.conversationId ?? '';
|
||||||
|
if (!conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChatClick(conversationId);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MessageCircle className="mr-1 h-5 w-5" />
|
<MessageCircle className="mr-1 h-5 w-5" />
|
||||||
<u>{conversation.title}</u>
|
<u>{conversation.title}</u>
|
||||||
|
|
@ -161,19 +178,23 @@ export default function ArchivedChatsTable() {
|
||||||
description={localize('com_ui_unarchive')}
|
description={localize('com_ui_unarchive')}
|
||||||
render={
|
render={
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
aria-label="Unarchive conversation"
|
aria-label="Unarchive conversation"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="size-8"
|
className="size-8"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setConversationId(conversation.conversationId);
|
const conversationId = conversation.conversationId ?? '';
|
||||||
archiveHandler();
|
if (!conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleUnarchive(conversationId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArchiveRestore className="size-4" />
|
<ArchiveRestore className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
></TooltipAnchor>
|
/>
|
||||||
|
|
||||||
<OGDialog>
|
<OGDialog>
|
||||||
<OGDialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
|
|
@ -181,6 +202,7 @@ export default function ArchivedChatsTable() {
|
||||||
description={localize('com_ui_delete')}
|
description={localize('com_ui_delete')}
|
||||||
render={
|
render={
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
aria-label="Delete archived conversation"
|
aria-label="Delete archived conversation"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -189,13 +211,13 @@ export default function ArchivedChatsTable() {
|
||||||
<TrashIcon className="size-4" />
|
<TrashIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
></TooltipAnchor>
|
/>
|
||||||
</OGDialogTrigger>
|
</OGDialogTrigger>
|
||||||
{DeleteConversationDialog({
|
<DeleteConversationDialog
|
||||||
conversationId: conversation.conversationId ?? '',
|
conversationId={conversation.conversationId ?? ''}
|
||||||
retainView: refetch,
|
retainView={refetch}
|
||||||
title: conversation.title ?? '',
|
title={conversation.title ?? ''}
|
||||||
})}
|
/>
|
||||||
</OGDialog>
|
</OGDialog>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
@ -208,7 +230,7 @@ export default function ArchivedChatsTable() {
|
||||||
Page {currentPage} of {totalPages}
|
Page {currentPage} of {totalPages}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button
|
{/* <Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Go to the previous 10 pages"
|
aria-label="Go to the previous 10 pages"
|
||||||
|
|
@ -216,7 +238,7 @@ export default function ArchivedChatsTable() {
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
>
|
>
|
||||||
<ChevronsLeft className="size-4" />
|
<ChevronsLeft className="size-4" />
|
||||||
</Button>
|
</Button> */}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -235,7 +257,7 @@ export default function ArchivedChatsTable() {
|
||||||
>
|
>
|
||||||
<ChevronRight className="size-4" />
|
<ChevronRight className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Go to the next 10 pages"
|
aria-label="Go to the next 10 pages"
|
||||||
|
|
@ -243,7 +265,7 @@ export default function ArchivedChatsTable() {
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
>
|
>
|
||||||
<ChevronsRight className="size-4" />
|
<ChevronsRight className="size-4" />
|
||||||
</Button>
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ interface TooltipAnchorProps extends Ariakit.TooltipAnchorProps {
|
||||||
description: string;
|
description: string;
|
||||||
side?: 'top' | 'bottom' | 'left' | 'right';
|
side?: 'top' | 'bottom' | 'left' | 'right';
|
||||||
className?: string;
|
className?: string;
|
||||||
|
role?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TooltipAnchor = forwardRef<HTMLDivElement, TooltipAnchorProps>(function TooltipAnchor(
|
export const TooltipAnchor = forwardRef<HTMLDivElement, TooltipAnchorProps>(function TooltipAnchor(
|
||||||
|
|
@ -50,7 +51,7 @@ export const TooltipAnchor = forwardRef<HTMLDivElement, TooltipAnchorProps>(func
|
||||||
className={cn('cursor-pointer', className)}
|
className={cn('cursor-pointer', className)}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{mounted && (
|
{mounted === true && (
|
||||||
<Ariakit.Tooltip
|
<Ariakit.Tooltip
|
||||||
gutter={4}
|
gutter={4}
|
||||||
alwaysVisible
|
alwaysVisible
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,8 @@ export const useArchiveConversationMutation = (
|
||||||
(payload: t.TArchiveConversationRequest) => dataService.archiveConversation(payload),
|
(payload: t.TArchiveConversationRequest) => dataService.archiveConversation(payload),
|
||||||
{
|
{
|
||||||
onSuccess: (_data, vars) => {
|
onSuccess: (_data, vars) => {
|
||||||
if (vars.isArchived) {
|
const isArchived = vars.isArchived === true;
|
||||||
|
if (isArchived) {
|
||||||
queryClient.setQueryData([QueryKeys.conversation, id], null);
|
queryClient.setQueryData([QueryKeys.conversation, id], null);
|
||||||
} else {
|
} else {
|
||||||
queryClient.setQueryData([QueryKeys.conversation, id], _data);
|
queryClient.setQueryData([QueryKeys.conversation, id], _data);
|
||||||
|
|
@ -151,17 +152,17 @@ export const useArchiveConversationMutation = (
|
||||||
const pageSize = convoData.pages[0].pageSize as number;
|
const pageSize = convoData.pages[0].pageSize as number;
|
||||||
|
|
||||||
return normalizeData(
|
return normalizeData(
|
||||||
vars.isArchived ? deleteConversation(convoData, id) : addConversation(convoData, _data),
|
isArchived ? deleteConversation(convoData, id) : addConversation(convoData, _data),
|
||||||
'conversations',
|
'conversations',
|
||||||
pageSize,
|
pageSize,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (vars.isArchived) {
|
if (isArchived) {
|
||||||
const current = queryClient.getQueryData<t.ConversationData>([
|
const current = queryClient.getQueryData<t.ConversationData>([
|
||||||
QueryKeys.allConversations,
|
QueryKeys.allConversations,
|
||||||
]);
|
]);
|
||||||
refetch({ refetchPage: (page, index) => index === (current?.pages.length || 1) - 1 });
|
refetch({ refetchPage: (page, index) => index === (current?.pages.length ?? 1) - 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
queryClient.setQueryData<t.ConversationData>(
|
queryClient.setQueryData<t.ConversationData>(
|
||||||
|
|
@ -172,21 +173,19 @@ export const useArchiveConversationMutation = (
|
||||||
}
|
}
|
||||||
const pageSize = convoData.pages[0].pageSize as number;
|
const pageSize = convoData.pages[0].pageSize as number;
|
||||||
return normalizeData(
|
return normalizeData(
|
||||||
vars.isArchived
|
isArchived ? addConversation(convoData, _data) : deleteConversation(convoData, id),
|
||||||
? addConversation(convoData, _data)
|
|
||||||
: deleteConversation(convoData, id),
|
|
||||||
'conversations',
|
'conversations',
|
||||||
pageSize,
|
pageSize,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!vars.isArchived) {
|
if (!isArchived) {
|
||||||
const currentArchive = queryClient.getQueryData<t.ConversationData>([
|
const currentArchive = queryClient.getQueryData<t.ConversationData>([
|
||||||
QueryKeys.archivedConversations,
|
QueryKeys.archivedConversations,
|
||||||
]);
|
]);
|
||||||
archiveRefetch({
|
archiveRefetch({
|
||||||
refetchPage: (page, index) => index === (currentArchive?.pages.length || 1) - 1,
|
refetchPage: (page, index) => index === (currentArchive?.pages.length ?? 1) - 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -194,6 +193,60 @@ export const useArchiveConversationMutation = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useArchiveConvoMutation = (options?: t.ArchiveConvoOptions) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { onSuccess, ..._options } = options ?? {};
|
||||||
|
|
||||||
|
return useMutation<t.TArchiveConversationResponse, unknown, t.TArchiveConversationRequest>(
|
||||||
|
(payload: t.TArchiveConversationRequest) => dataService.archiveConversation(payload),
|
||||||
|
{
|
||||||
|
onSuccess: (_data, vars) => {
|
||||||
|
const { conversationId } = vars;
|
||||||
|
const isArchived = vars.isArchived === true;
|
||||||
|
if (isArchived) {
|
||||||
|
queryClient.setQueryData([QueryKeys.conversation, conversationId], null);
|
||||||
|
} else {
|
||||||
|
queryClient.setQueryData([QueryKeys.conversation, conversationId], _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
||||||
|
if (!convoData) {
|
||||||
|
return convoData;
|
||||||
|
}
|
||||||
|
const pageSize = convoData.pages[0].pageSize as number;
|
||||||
|
return normalizeData(
|
||||||
|
isArchived
|
||||||
|
? deleteConversation(convoData, conversationId)
|
||||||
|
: addConversation(convoData, _data),
|
||||||
|
'conversations',
|
||||||
|
pageSize,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
queryClient.setQueryData<t.ConversationData>(
|
||||||
|
[QueryKeys.archivedConversations],
|
||||||
|
(convoData) => {
|
||||||
|
if (!convoData) {
|
||||||
|
return convoData;
|
||||||
|
}
|
||||||
|
const pageSize = convoData.pages[0].pageSize as number;
|
||||||
|
return normalizeData(
|
||||||
|
isArchived
|
||||||
|
? addConversation(convoData, _data)
|
||||||
|
: deleteConversation(convoData, conversationId),
|
||||||
|
'conversations',
|
||||||
|
pageSize,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onSuccess?.(_data, vars);
|
||||||
|
},
|
||||||
|
..._options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const useCreateSharedLinkMutation = (
|
export const useCreateSharedLinkMutation = (
|
||||||
options?: t.CreateSharedLinkOptions,
|
options?: t.CreateSharedLinkOptions,
|
||||||
): UseMutationResult<t.TSharedLinkResponse, unknown, t.TSharedLinkRequest, unknown> => {
|
): UseMutationResult<t.TSharedLinkResponse, unknown, t.TSharedLinkRequest, unknown> => {
|
||||||
|
|
|
||||||
|
|
@ -508,6 +508,7 @@ export const tConversationSchema = z.object({
|
||||||
conversationId: z.string().nullable(),
|
conversationId: z.string().nullable(),
|
||||||
endpoint: eModelEndpointSchema.nullable(),
|
endpoint: eModelEndpointSchema.nullable(),
|
||||||
endpointType: eModelEndpointSchema.optional(),
|
endpointType: eModelEndpointSchema.optional(),
|
||||||
|
isArchived: z.boolean().optional(),
|
||||||
title: z.string().nullable().or(z.literal('New Chat')).default('New Chat'),
|
title: z.string().nullable().or(z.literal('New Chat')).default('New Chat'),
|
||||||
user: z.string().optional(),
|
user: z.string().optional(),
|
||||||
messages: z.array(z.string()).optional(),
|
messages: z.array(z.string()).optional(),
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,12 @@ export type UpdateSharedLinkOptions = MutationOptions<
|
||||||
types.TSharedLink,
|
types.TSharedLink,
|
||||||
Partial<types.TSharedLink>
|
Partial<types.TSharedLink>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type ArchiveConvoOptions = MutationOptions<
|
||||||
|
types.TArchiveConversationResponse,
|
||||||
|
types.TArchiveConversationRequest
|
||||||
|
>;
|
||||||
|
|
||||||
export type DeleteSharedLinkOptions = MutationOptions<types.TSharedLink, { shareId: string }>;
|
export type DeleteSharedLinkOptions = MutationOptions<types.TSharedLink, { shareId: string }>;
|
||||||
|
|
||||||
export type TUpdatePromptContext =
|
export type TUpdatePromptContext =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue