🐞 fix: Agent "Resend" Message Attachments + Source Icon Styling (#6408)

* style: Update text file source icon background color for improved visibility in light mode

* style: Update `vectordb` source icon background color for better visibility

* fix: resend files behavior for tool resource message attachments (code interpreter and file search); Rename `getToolFiles` to `getConvoFiles` and simplify file retrieval logic; add `getToolFilesByIds` for fetching tool files by IDs
This commit is contained in:
Danny Avila 2025-03-19 03:27:20 -04:00 committed by GitHub
parent 8f68e8be81
commit 57c3a217c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 46 additions and 38 deletions

View file

@ -61,45 +61,22 @@ const deleteNullOrEmptyConversations = async () => {
}; };
/** /**
* Retrieves files from a conversation that have either embedded=true * Searches for a conversation by conversationId and returns associated file ids.
* or a metadata.fileIdentifier. Simplified and efficient query. * @param {string} conversationId - The conversation's ID.
* * @returns {Promise<string[] | null>}
* @param {string} conversationId - The conversation ID
* @returns {Promise<MongoFile[]>} - Filtered array of matching file objects
*/ */
const getToolFiles = async (conversationId) => { const getConvoFiles = async (conversationId) => {
try { try {
const [result] = await Conversation.aggregate([ return (await Conversation.findOne({ conversationId }, 'files').lean())?.files ?? [];
{ $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 || [];
} catch (error) { } catch (error) {
logger.error('[getConvoEmbeddedFiles] Error fetching embedded files:', error); logger.error('[getConvoFiles] Error getting conversation files', error);
throw new Error('Error fetching embedded files'); throw new Error('Error getting conversation files');
} }
}; };
module.exports = { module.exports = {
Conversation, Conversation,
getToolFiles, getConvoFiles,
searchConversation, searchConversation,
deleteNullOrEmptyConversations, deleteNullOrEmptyConversations,
/** /**

View file

@ -1,5 +1,6 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const { fileSchema } = require('@librechat/data-schemas'); const { fileSchema } = require('@librechat/data-schemas');
const { logger } = require('~/config');
const File = mongoose.model('File', fileSchema); 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(); 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<Array<IMongoFile>>} 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. * Creates a new file with a TTL of 1 hour.
* @param {IMongoFile} data - The file data to be created, must contain file_id. * @param {IMongoFile} data - The file data to be created, must contain file_id.
@ -111,6 +138,7 @@ module.exports = {
File, File,
findFileById, findFileById,
getFiles, getFiles,
getToolFilesByIds,
createFile, createFile,
updateFile, updateFile,
updateFileUsage, updateFileUsage,

View file

@ -19,7 +19,8 @@ const { getCustomEndpointConfig } = require('~/server/services/Config');
const { processFiles } = require('~/server/services/Files/process'); const { processFiles } = require('~/server/services/Files/process');
const { loadAgentTools } = require('~/server/services/ToolService'); const { loadAgentTools } = require('~/server/services/ToolService');
const AgentClient = require('~/server/controllers/agents/client'); 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 { getModelMaxTokens } = require('~/utils');
const { getAgent } = require('~/models/Agent'); const { getAgent } = require('~/models/Agent');
const { getFiles } = require('~/models/File'); const { getFiles } = require('~/models/File');
@ -115,15 +116,17 @@ const initializeAgentOptions = async ({
isInitialAgent = false, isInitialAgent = false,
}) => { }) => {
let currentFiles; let currentFiles;
/** @type {Array<MongoFile>} */
const requestFiles = req.body.files ?? []; const requestFiles = req.body.files ?? [];
if ( if (
isInitialAgent && isInitialAgent &&
req.body.conversationId != null && 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); const fileIds = (await getConvoFiles(req.body.conversationId)) ?? [];
if (requestFiles.length || fileIds.length) { const toolFiles = await getToolFilesByIds(fileIds);
currentFiles = await processFiles(requestFiles, fileIds); if (requestFiles.length || toolFiles.length) {
currentFiles = await processFiles(requestFiles.concat(toolFiles));
} }
} else if (isInitialAgent && requestFiles.length) { } else if (isInitialAgent && requestFiles.length) {
currentFiles = await processFiles(requestFiles); currentFiles = await processFiles(requestFiles);

View file

@ -12,8 +12,8 @@ const sourceToClassname = {
[FileSources.openai]: 'bg-white/75 dark:bg-black/65', [FileSources.openai]: 'bg-white/75 dark:bg-black/65',
[FileSources.azure]: 'azure-bg-color opacity-85', [FileSources.azure]: 'azure-bg-color opacity-85',
[FileSources.execute_code]: 'bg-black text-white 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.text]: 'bg-blue-500 dark:bg-blue-900 opacity-85 text-white',
[FileSources.vectordb]: 'bg-yellow-100 dark:bg-yellow-900 opacity-85 text-white', [FileSources.vectordb]: 'bg-yellow-700 dark:bg-yellow-900 opacity-85 text-white',
}; };
const defaultClassName = const defaultClassName =