diff --git a/api/models/Conversation.js b/api/models/Conversation.js index f609b96c5c..dd6ef9bde1 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -61,45 +61,22 @@ const deleteNullOrEmptyConversations = async () => { }; /** - * Retrieves files from a conversation that have either embedded=true - * or a metadata.fileIdentifier. Simplified and efficient query. - * - * @param {string} conversationId - The conversation ID - * @returns {Promise} - Filtered array of matching file objects + * Searches for a conversation by conversationId and returns associated file ids. + * @param {string} conversationId - The conversation's ID. + * @returns {Promise} */ -const getToolFiles = async (conversationId) => { +const getConvoFiles = async (conversationId) => { try { - const [result] = await Conversation.aggregate([ - { $match: { conversationId } }, - { - $project: { - files: { - $filter: { - input: '$files', - as: 'file', - cond: { - $or: [ - { $eq: ['$$file.embedded', true] }, - { $ifNull: ['$$file.metadata.fileIdentifier', false] }, - ], - }, - }, - }, - _id: 0, - }, - }, - ]).exec(); - - return result?.files || []; + return (await Conversation.findOne({ conversationId }, 'files').lean())?.files ?? []; } catch (error) { - logger.error('[getConvoEmbeddedFiles] Error fetching embedded files:', error); - throw new Error('Error fetching embedded files'); + logger.error('[getConvoFiles] Error getting conversation files', error); + throw new Error('Error getting conversation files'); } }; module.exports = { Conversation, - getToolFiles, + getConvoFiles, searchConversation, deleteNullOrEmptyConversations, /** diff --git a/api/models/File.js b/api/models/File.js index 3d62f27abb..0bde258a54 100644 --- a/api/models/File.js +++ b/api/models/File.js @@ -1,5 +1,6 @@ const mongoose = require('mongoose'); const { fileSchema } = require('@librechat/data-schemas'); +const { logger } = require('~/config'); const File = mongoose.model('File', fileSchema); @@ -26,6 +27,32 @@ const getFiles = async (filter, _sortOptions, selectFields = { text: 0 }) => { return await File.find(filter).select(selectFields).sort(sortOptions).lean(); }; +/** + * Retrieves tool files (files that are embedded or have a fileIdentifier) from an array of file IDs + * @param {string[]} fileIds - Array of file_id strings to search for + * @returns {Promise>} Files that match the criteria + */ +const getToolFilesByIds = async (fileIds) => { + if (!fileIds || !fileIds.length) { + return []; + } + + try { + const filter = { + file_id: { $in: fileIds }, + $or: [{ embedded: true }, { 'metadata.fileIdentifier': { $exists: true } }], + }; + + const selectFields = { text: 0 }; + const sortOptions = { updatedAt: -1 }; + + return await getFiles(filter, sortOptions, selectFields); + } catch (error) { + logger.error('[getToolFilesByIds] Error retrieving tool files:', error); + throw new Error('Error retrieving tool files'); + } +}; + /** * Creates a new file with a TTL of 1 hour. * @param {IMongoFile} data - The file data to be created, must contain file_id. @@ -111,6 +138,7 @@ module.exports = { File, findFileById, getFiles, + getToolFilesByIds, createFile, updateFile, updateFileUsage, diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index dec69d862c..737165e316 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -19,7 +19,8 @@ const { getCustomEndpointConfig } = require('~/server/services/Config'); const { processFiles } = require('~/server/services/Files/process'); const { loadAgentTools } = require('~/server/services/ToolService'); const AgentClient = require('~/server/controllers/agents/client'); -const { getToolFiles } = require('~/models/Conversation'); +const { getConvoFiles } = require('~/models/Conversation'); +const { getToolFilesByIds } = require('~/models/File'); const { getModelMaxTokens } = require('~/utils'); const { getAgent } = require('~/models/Agent'); const { getFiles } = require('~/models/File'); @@ -115,15 +116,17 @@ const initializeAgentOptions = async ({ isInitialAgent = false, }) => { let currentFiles; + /** @type {Array} */ const requestFiles = req.body.files ?? []; if ( isInitialAgent && req.body.conversationId != null && - agent.model_parameters?.resendFiles === true + (agent.model_parameters?.resendFiles ?? true) === true ) { - const fileIds = (await getToolFiles(req.body.conversationId)).map((f) => f.file_id); - if (requestFiles.length || fileIds.length) { - currentFiles = await processFiles(requestFiles, fileIds); + const fileIds = (await getConvoFiles(req.body.conversationId)) ?? []; + const toolFiles = await getToolFilesByIds(fileIds); + if (requestFiles.length || toolFiles.length) { + currentFiles = await processFiles(requestFiles.concat(toolFiles)); } } else if (isInitialAgent && requestFiles.length) { currentFiles = await processFiles(requestFiles); diff --git a/client/src/components/Chat/Input/Files/SourceIcon.tsx b/client/src/components/Chat/Input/Files/SourceIcon.tsx index 3af439bced..c3b2a4423c 100644 --- a/client/src/components/Chat/Input/Files/SourceIcon.tsx +++ b/client/src/components/Chat/Input/Files/SourceIcon.tsx @@ -12,8 +12,8 @@ const sourceToClassname = { [FileSources.openai]: 'bg-white/75 dark:bg-black/65', [FileSources.azure]: 'azure-bg-color opacity-85', [FileSources.execute_code]: 'bg-black text-white opacity-85', - [FileSources.text]: 'bg-blue-100 dark:bg-blue-900 opacity-85 text-white', - [FileSources.vectordb]: 'bg-yellow-100 dark:bg-yellow-900 opacity-85 text-white', + [FileSources.text]: 'bg-blue-500 dark:bg-blue-900 opacity-85 text-white', + [FileSources.vectordb]: 'bg-yellow-700 dark:bg-yellow-900 opacity-85 text-white', }; const defaultClassName =