mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00

* 🔧chore: add internationalization labels for archive feature * ✨ feat: Add function to useArchiveConversationMutation() This commit adds a new mutation function `useArchiveConversationMutation()` for archiving conversations. This function takes the ID string of the conversation to be archived and returns a mutation result object. Upon successful archiving, it removes and refreshes the conversation from the query data cache. While ChatGPT PATCHes the archived status by sending `{is_archived: true}` to the URL `/backend-api/conversation/$conversation_id`, this implementation uses the `dataService.updateConversation(payload)` with a POST method, aligning with the existing code conventions. * ✨ feat(api): add is_archived field to Conversation schema and update getConvosByPage method This commit adds a new field `is_archived` with a default value of false to the Conversation schema. It also modifies the `getConvosByPage` method within the Conversation API to adjust the query to only target conversations where `is_archived` is set to false or where the `is_archived` field does not exist. The function `getConvosQueried`, which returns conversations for a specified Conversation ID, was determined not to require consideration of whether `is_archived` is true or false, and thus was not modified. * ♻️ refactor: add className prop to DotsIcon component To enhance the versatility of the DotsIcon component, this commit introduces the ability to specify a className prop, allowing for greater customization. * ✨ feat(ui): add Edit Button to group Title change and Conversation delete buttons Added a new Edit Button to the conversations, similar to the ChatGPT UI, which groups options for editing the conversation title and deleting conversations. This grouping is accessible through a dialogue that appears when the three-dot icon is clicked. * ♻️ refactor(ui): enhance Delete Button to accept className and label options Enhanced the Delete Button component to accept a `className` for customization and an optional `appendLabel`. The DeleteButton component is used by both `Convo.tsx` and `Conversation.tsx`, but currently only `Convo.tsx` is active and `Conversation.tsx `is apparently not used; removing `Conversation.tsx` may eliminate the need for the `appendLabel` property in the future. * ♻️ refactor(ui): enhance RenameButton to accept label options Added the ability to optionally display labels; the Rename Button component is used by both `Convo.tsx` and `Conversation.tsx`, but currently only `Convo.tsx` is active and `Conversation.tsx `is apparently not used; removing `Conversation.tsx` may eliminate the need for the `appendLabel` property in the future. * 🔧 chors: additional localization labels * ♻️ refactor: change is_archived property of conversation to camelCase * Refactor the is_archived property of conversation to camelCase (isArchived) to adhere to the existing code conventions * Modify the function that retrieves conversations to accept the isArchived parameter * ♻️ refactor: add archiveConversation mutation I thought I could divert dataService.updateConversation, but added a new archiveConversation because the request types are different. It might be better to make them common, but to avoid side effects, I added a new function this time. Added process to deleteConversationMutation to delete archived conversations * ✨ feat: Add the function to hide a cancel button in DialogTemplate component The Cancel button is not needed when displaying the archive list, so I made the Cancel button optional. * ♻️ refactor: Add support for filtering archived conversations in Nav component This commit modifies the Nav component to add the ability to filter out archived conversations when fetching data. This is done by adding `isArchived: false` to the query parameters for both the `useConversationsInfiniteQuery()` and `useSearchInfiniteQuery()` hooks, effectively excluding any archived conversations from the results returned. * ♻️ refactor: add Tooltip to DeleteButton * Add Tooltip to DeleteButton component * Display Tooltip when DeleteButton only shows an Icon without text * ✨ feat(ui): add ArchiveButton component for archiving conversations To be compatible with the ChatGPT UI, no confirmation dialog is displayed when ArchiveButton is clicked. The basic behavior conforms to DeleteButton and RenameButton. * ✨ feat(ui): add Archive button to list of conversations Modify the Nav of the conversation list to include a dropdown that contains the Rename and Delete options, similar to the ChatGPT UI. Additionally, an Archive button has been added adjacent to the dropdown menu. * ✨ feat: Add ArchivedChatsTable component Adds the `ArchivedChatsTable` component, which displays a table of archived chats. It has been implemented to be as compatible with the ChatGPT UI as possible. * 🚑 fix(tooltip): increase z-index to ensure visibility over Dialog Resolve an issue where tooltips were not visible when displayed over a Dialog. The z-index of `DialogPrimitive.Portal` in `Dialog.tsx` is set to 999. Since the rationale for this value is unclear, the z-index of the tooltip has been increased to 1000 to guarantee its visibility above the Dialog component. * 🔧 chors: add internationalization labels
165 lines
5.8 KiB
JavaScript
165 lines
5.8 KiB
JavaScript
const Conversation = require('./schema/convoSchema');
|
|
const { getMessages, deleteMessages } = require('./Message');
|
|
const logger = require('~/config/winston');
|
|
|
|
/**
|
|
* Retrieves a single conversation for a given user and conversation ID.
|
|
* @param {string} user - The user's ID.
|
|
* @param {string} conversationId - The conversation's ID.
|
|
* @returns {Promise<TConversation>} The conversation object.
|
|
*/
|
|
const getConvo = async (user, conversationId) => {
|
|
try {
|
|
return await Conversation.findOne({ user, conversationId }).lean();
|
|
} catch (error) {
|
|
logger.error('[getConvo] Error getting single conversation', error);
|
|
return { message: 'Error getting single conversation' };
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
Conversation,
|
|
saveConvo: async (user, { conversationId, newConversationId, ...convo }) => {
|
|
try {
|
|
const messages = await getMessages({ conversationId });
|
|
const update = { ...convo, messages, user };
|
|
if (newConversationId) {
|
|
update.conversationId = newConversationId;
|
|
}
|
|
|
|
return await Conversation.findOneAndUpdate({ conversationId: conversationId, user }, update, {
|
|
new: true,
|
|
upsert: true,
|
|
});
|
|
} catch (error) {
|
|
logger.error('[saveConvo] Error saving conversation', error);
|
|
return { message: 'Error saving conversation' };
|
|
}
|
|
},
|
|
bulkSaveConvos: async (conversations) => {
|
|
try {
|
|
const bulkOps = conversations.map((convo) => ({
|
|
updateOne: {
|
|
filter: { conversationId: convo.conversationId, user: convo.user },
|
|
update: convo,
|
|
upsert: true,
|
|
timestamps: false,
|
|
},
|
|
}));
|
|
|
|
const result = await Conversation.bulkWrite(bulkOps);
|
|
return result;
|
|
} catch (error) {
|
|
logger.error('[saveBulkConversations] Error saving conversations in bulk', error);
|
|
throw new Error('Failed to save conversations in bulk.');
|
|
}
|
|
},
|
|
getConvosByPage: async (user, pageNumber = 1, pageSize = 25, isArchived = false) => {
|
|
const query = { user };
|
|
if (isArchived) {
|
|
query.isArchived = true;
|
|
} else {
|
|
query.$or = [{ isArchived: false }, { isArchived: { $exists: false } }];
|
|
}
|
|
try {
|
|
const totalConvos = (await Conversation.countDocuments(query)) || 1;
|
|
const totalPages = Math.ceil(totalConvos / pageSize);
|
|
const convos = await Conversation.find(query)
|
|
.sort({ updatedAt: -1 })
|
|
.skip((pageNumber - 1) * pageSize)
|
|
.limit(pageSize)
|
|
.lean();
|
|
return { conversations: convos, pages: totalPages, pageNumber, pageSize };
|
|
} catch (error) {
|
|
logger.error('[getConvosByPage] Error getting conversations', error);
|
|
return { message: 'Error getting conversations' };
|
|
}
|
|
},
|
|
getConvosQueried: async (user, convoIds, pageNumber = 1, pageSize = 25) => {
|
|
try {
|
|
if (!convoIds || convoIds.length === 0) {
|
|
return { conversations: [], pages: 1, pageNumber, pageSize };
|
|
}
|
|
|
|
const cache = {};
|
|
const convoMap = {};
|
|
const promises = [];
|
|
|
|
convoIds.forEach((convo) =>
|
|
promises.push(
|
|
Conversation.findOne({
|
|
user,
|
|
conversationId: convo.conversationId,
|
|
}).lean(),
|
|
),
|
|
);
|
|
|
|
const results = (await Promise.all(promises)).filter(Boolean);
|
|
|
|
results.forEach((convo, i) => {
|
|
const page = Math.floor(i / pageSize) + 1;
|
|
if (!cache[page]) {
|
|
cache[page] = [];
|
|
}
|
|
cache[page].push(convo);
|
|
convoMap[convo.conversationId] = convo;
|
|
});
|
|
|
|
const totalPages = Math.ceil(results.length / pageSize);
|
|
cache.pages = totalPages;
|
|
cache.pageSize = pageSize;
|
|
return {
|
|
cache,
|
|
conversations: cache[pageNumber] || [],
|
|
pages: totalPages || 1,
|
|
pageNumber,
|
|
pageSize,
|
|
convoMap,
|
|
};
|
|
} catch (error) {
|
|
logger.error('[getConvosQueried] Error getting conversations', error);
|
|
return { message: 'Error fetching conversations' };
|
|
}
|
|
},
|
|
getConvo,
|
|
/* chore: this method is not properly error handled */
|
|
getConvoTitle: async (user, conversationId) => {
|
|
try {
|
|
const convo = await getConvo(user, conversationId);
|
|
/* ChatGPT Browser was triggering error here due to convo being saved later */
|
|
if (convo && !convo.title) {
|
|
return null;
|
|
} else {
|
|
// TypeError: Cannot read properties of null (reading 'title')
|
|
return convo?.title || 'New Chat';
|
|
}
|
|
} catch (error) {
|
|
logger.error('[getConvoTitle] Error getting conversation title', error);
|
|
return { message: 'Error getting conversation title' };
|
|
}
|
|
},
|
|
/**
|
|
* Asynchronously deletes conversations and associated messages for a given user and filter.
|
|
*
|
|
* @async
|
|
* @function
|
|
* @param {string|ObjectId} user - The user's ID.
|
|
* @param {Object} filter - Additional filter criteria for the conversations to be deleted.
|
|
* @returns {Promise<{ n: number, ok: number, deletedCount: number, messages: { n: number, ok: number, deletedCount: number } }>}
|
|
* An object containing the count of deleted conversations and associated messages.
|
|
* @throws {Error} Throws an error if there's an issue with the database operations.
|
|
*
|
|
* @example
|
|
* const user = 'someUserId';
|
|
* const filter = { someField: 'someValue' };
|
|
* const result = await deleteConvos(user, filter);
|
|
* logger.error(result); // { n: 5, ok: 1, deletedCount: 5, messages: { n: 10, ok: 1, deletedCount: 10 } }
|
|
*/
|
|
deleteConvos: async (user, filter) => {
|
|
let toRemove = await Conversation.find({ ...filter, user }).select('conversationId');
|
|
const ids = toRemove.map((instance) => instance.conversationId);
|
|
let deleteCount = await Conversation.deleteMany({ ...filter, user });
|
|
deleteCount.messages = await deleteMessages({ conversationId: { $in: ids } });
|
|
return deleteCount;
|
|
},
|
|
};
|