mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🏷️ fix: Increment Tag Counters When Forking/Duplicating Conversations (#9737)
* fix: increment tag counters when forking/duplicating conversations - Add bulkIncrementTagCounts to update existing tag counts in bulk - Integrate tag count updates into importBatchBuilder.saveBatch() using Promise.all - Update frontend mutations to directly update cache instead of invalidating queries - Optimize bulkIncrementTagCounts to skip unnecessary database queries Fixes issue where forked/duplicated conversations with bookmarks would not increment tag counters, leading to negative counts when bookmarks were later removed. * chore: reorder import statements for clarity in fork.spec.js
This commit is contained in:
parent
aae3694b11
commit
fcaf55143d
4 changed files with 196 additions and 4 deletions
|
|
@ -10,6 +10,10 @@ jest.mock('~/models/Message', () => ({
|
|||
bulkSaveMessages: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('~/models/ConversationTag', () => ({
|
||||
bulkIncrementTagCounts: jest.fn(),
|
||||
}));
|
||||
|
||||
let mockIdCounter = 0;
|
||||
jest.mock('uuid', () => {
|
||||
return {
|
||||
|
|
@ -22,11 +26,13 @@ jest.mock('uuid', () => {
|
|||
|
||||
const {
|
||||
forkConversation,
|
||||
duplicateConversation,
|
||||
splitAtTargetLevel,
|
||||
getAllMessagesUpToParent,
|
||||
getMessagesUpToTargetLevel,
|
||||
cloneMessagesWithTimestamps,
|
||||
} = require('./fork');
|
||||
const { bulkIncrementTagCounts } = require('~/models/ConversationTag');
|
||||
const { getConvo, bulkSaveConvos } = require('~/models/Conversation');
|
||||
const { getMessages, bulkSaveMessages } = require('~/models/Message');
|
||||
const { createImportBatchBuilder } = require('./importBatchBuilder');
|
||||
|
|
@ -181,6 +187,120 @@ describe('forkConversation', () => {
|
|||
}),
|
||||
).rejects.toThrow('Failed to fetch messages');
|
||||
});
|
||||
|
||||
test('should increment tag counts when forking conversation with tags', async () => {
|
||||
const mockConvoWithTags = {
|
||||
...mockConversation,
|
||||
tags: ['bookmark1', 'bookmark2'],
|
||||
};
|
||||
getConvo.mockResolvedValue(mockConvoWithTags);
|
||||
|
||||
await forkConversation({
|
||||
originalConvoId: 'abc123',
|
||||
targetMessageId: '3',
|
||||
requestUserId: 'user1',
|
||||
option: ForkOptions.DIRECT_PATH,
|
||||
});
|
||||
|
||||
// Verify that bulkIncrementTagCounts was called with correct tags
|
||||
expect(bulkIncrementTagCounts).toHaveBeenCalledWith('user1', ['bookmark1', 'bookmark2']);
|
||||
});
|
||||
|
||||
test('should handle conversation without tags when forking', async () => {
|
||||
const mockConvoWithoutTags = {
|
||||
...mockConversation,
|
||||
// No tags field
|
||||
};
|
||||
getConvo.mockResolvedValue(mockConvoWithoutTags);
|
||||
|
||||
await forkConversation({
|
||||
originalConvoId: 'abc123',
|
||||
targetMessageId: '3',
|
||||
requestUserId: 'user1',
|
||||
option: ForkOptions.DIRECT_PATH,
|
||||
});
|
||||
|
||||
// bulkIncrementTagCounts will be called with array containing undefined
|
||||
expect(bulkIncrementTagCounts).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle empty tags array when forking', async () => {
|
||||
const mockConvoWithEmptyTags = {
|
||||
...mockConversation,
|
||||
tags: [],
|
||||
};
|
||||
getConvo.mockResolvedValue(mockConvoWithEmptyTags);
|
||||
|
||||
await forkConversation({
|
||||
originalConvoId: 'abc123',
|
||||
targetMessageId: '3',
|
||||
requestUserId: 'user1',
|
||||
option: ForkOptions.DIRECT_PATH,
|
||||
});
|
||||
|
||||
// bulkIncrementTagCounts will be called with empty array
|
||||
expect(bulkIncrementTagCounts).toHaveBeenCalledWith('user1', []);
|
||||
});
|
||||
});
|
||||
|
||||
describe('duplicateConversation', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockIdCounter = 0;
|
||||
getConvo.mockResolvedValue(mockConversation);
|
||||
getMessages.mockResolvedValue(mockMessages);
|
||||
bulkSaveConvos.mockResolvedValue(null);
|
||||
bulkSaveMessages.mockResolvedValue(null);
|
||||
bulkIncrementTagCounts.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
test('should duplicate conversation and increment tag counts', async () => {
|
||||
const mockConvoWithTags = {
|
||||
...mockConversation,
|
||||
tags: ['important', 'work', 'project'],
|
||||
};
|
||||
getConvo.mockResolvedValue(mockConvoWithTags);
|
||||
|
||||
await duplicateConversation({
|
||||
userId: 'user1',
|
||||
conversationId: 'abc123',
|
||||
});
|
||||
|
||||
// Verify that bulkIncrementTagCounts was called with correct tags
|
||||
expect(bulkIncrementTagCounts).toHaveBeenCalledWith('user1', ['important', 'work', 'project']);
|
||||
});
|
||||
|
||||
test('should duplicate conversation without tags', async () => {
|
||||
const mockConvoWithoutTags = {
|
||||
...mockConversation,
|
||||
// No tags field
|
||||
};
|
||||
getConvo.mockResolvedValue(mockConvoWithoutTags);
|
||||
|
||||
await duplicateConversation({
|
||||
userId: 'user1',
|
||||
conversationId: 'abc123',
|
||||
});
|
||||
|
||||
// bulkIncrementTagCounts will be called with array containing undefined
|
||||
expect(bulkIncrementTagCounts).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle empty tags array when duplicating', async () => {
|
||||
const mockConvoWithEmptyTags = {
|
||||
...mockConversation,
|
||||
tags: [],
|
||||
};
|
||||
getConvo.mockResolvedValue(mockConvoWithEmptyTags);
|
||||
|
||||
await duplicateConversation({
|
||||
userId: 'user1',
|
||||
conversationId: 'abc123',
|
||||
});
|
||||
|
||||
// bulkIncrementTagCounts will be called with empty array
|
||||
expect(bulkIncrementTagCounts).toHaveBeenCalledWith('user1', []);
|
||||
});
|
||||
});
|
||||
|
||||
const mockMessagesComplex = [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const { v4: uuidv4 } = require('uuid');
|
||||
const { EModelEndpoint, Constants, openAISettings } = require('librechat-data-provider');
|
||||
const { bulkIncrementTagCounts } = require('~/models/ConversationTag');
|
||||
const { bulkSaveConvos } = require('~/models/Conversation');
|
||||
const { bulkSaveMessages } = require('~/models/Message');
|
||||
const { logger } = require('~/config');
|
||||
|
|
@ -93,13 +94,22 @@ class ImportBatchBuilder {
|
|||
|
||||
/**
|
||||
* Saves the batch of conversations and messages to the DB.
|
||||
* Also increments tag counts for any existing tags.
|
||||
* @returns {Promise<void>} A promise that resolves when the batch is saved.
|
||||
* @throws {Error} If there is an error saving the batch.
|
||||
*/
|
||||
async saveBatch() {
|
||||
try {
|
||||
await bulkSaveConvos(this.conversations);
|
||||
await bulkSaveMessages(this.messages, true);
|
||||
const promises = [];
|
||||
promises.push(bulkSaveConvos(this.conversations));
|
||||
promises.push(bulkSaveMessages(this.messages, true));
|
||||
promises.push(
|
||||
bulkIncrementTagCounts(
|
||||
this.requestUserId,
|
||||
this.conversations.flatMap((convo) => convo.tags),
|
||||
),
|
||||
);
|
||||
await Promise.all(promises);
|
||||
logger.debug(
|
||||
`user: ${this.requestUserId} | Added ${this.conversations.length} conversations and ${this.messages.length} messages to the DB.`,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue