mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
📁 feat: Integrate SharePoint File Picker and Download Workflow (#8651)
* feat(sharepoint): integrate SharePoint file picker and download workflow Introduces end‑to‑end SharePoint import support: * Token exchange with Microsoft Graph and scope management (`useSharePointToken`) * Re‑usable hooks: `useSharePointPicker`, `useSharePointDownload`, `useSharePointFileHandling` * FileSearch dropdown now offers **From Local Machine** / **From SharePoint** sources and gracefully falls back when SharePoint is disabled * Agent upload model, `AttachFileMenu`, and `DropdownPopup` extended for SharePoint files and sub‑menus * Blurry overlay with progress indicator and `maxSelectionCount` limit during downloads * Cache‑flush utility (`config/flush-cache.js`) supporting Redis & filesystem, with dry‑run and npm script * Updated `SharePointIcon` (uses `currentColor`) and new i18n keys * Bug fixes: placeholder syntax in progress message, picker event‑listener cleanup * Misc style and performance optimizations * Fix ESLint warnings --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
This commit is contained in:
parent
b6413b06bc
commit
a955097faf
40 changed files with 2500 additions and 123 deletions
264
config/flush-cache.js
Normal file
264
config/flush-cache.js
Normal file
|
@ -0,0 +1,264 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* LibreChat Cache Flush Utility
|
||||
*
|
||||
* This script flushes the cache store used by LibreChat, whether it's
|
||||
* Redis (if configured) or file-based cache.
|
||||
*
|
||||
* Usage:
|
||||
* npm run flush-cache
|
||||
* node config/flush-cache.js
|
||||
* node config/flush-cache.js --help
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// Set up environment
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
|
||||
const { USE_REDIS, REDIS_URI, REDIS_KEY_PREFIX } = process.env;
|
||||
|
||||
// Simple utility function
|
||||
const isEnabled = (value) => value === 'true' || value === true;
|
||||
|
||||
async function showHelp() {
|
||||
console.log(`
|
||||
LibreChat Cache Flush Utility
|
||||
|
||||
DESCRIPTION:
|
||||
Flushes the cache store used by LibreChat. Automatically detects
|
||||
whether Redis or file-based cache is being used and flushes accordingly.
|
||||
|
||||
USAGE:
|
||||
npm run flush-cache
|
||||
node config/flush-cache.js [options]
|
||||
|
||||
OPTIONS:
|
||||
--help, -h Show this help message
|
||||
--dry-run Show what would be flushed without actually doing it
|
||||
--verbose, -v Show detailed output
|
||||
|
||||
CACHE TYPES:
|
||||
• Redis Cache: Flushes all keys with the configured Redis prefix
|
||||
• File Cache: Removes ./data/logs.json and ./data/violations.json
|
||||
|
||||
WHAT GETS FLUSHED:
|
||||
• User sessions and authentication tokens
|
||||
• Configuration cache
|
||||
• Model queries cache
|
||||
• Rate limiting data
|
||||
• Conversation titles cache
|
||||
• File upload progress
|
||||
• SharePoint tokens
|
||||
• And more...
|
||||
|
||||
NOTE: This will log out all users and may require them to re-authenticate.
|
||||
`);
|
||||
}
|
||||
|
||||
async function flushRedisCache(dryRun = false, verbose = false) {
|
||||
try {
|
||||
console.log('🔍 Redis cache detected');
|
||||
|
||||
if (verbose) {
|
||||
console.log(` URI: ${REDIS_URI ? REDIS_URI.replace(/\/\/.*@/, '//***:***@') : 'Not set'}`);
|
||||
console.log(` Prefix: ${REDIS_KEY_PREFIX || 'None'}`);
|
||||
}
|
||||
|
||||
// Create Redis client directly
|
||||
const Redis = require('ioredis');
|
||||
let redis;
|
||||
|
||||
// Handle cluster vs single Redis
|
||||
if (process.env.USE_REDIS_CLUSTER === 'true') {
|
||||
const hosts = REDIS_URI.split(',').map((uri) => {
|
||||
const [host, port] = uri.split(':');
|
||||
return { host, port: parseInt(port) || 6379 };
|
||||
});
|
||||
redis = new Redis.Cluster(hosts);
|
||||
} else {
|
||||
redis = new Redis(REDIS_URI);
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log('🔍 [DRY RUN] Would flush Redis cache');
|
||||
try {
|
||||
const keys = await redis.keys('*');
|
||||
console.log(` Would delete ${keys.length} keys`);
|
||||
if (verbose && keys.length > 0) {
|
||||
console.log(
|
||||
' Sample keys:',
|
||||
keys.slice(0, 10).join(', ') + (keys.length > 10 ? '...' : ''),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' Could not fetch keys for preview:', error.message);
|
||||
}
|
||||
await redis.disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get key count before flushing
|
||||
let keyCount = 0;
|
||||
try {
|
||||
const keys = await redis.keys('*');
|
||||
keyCount = keys.length;
|
||||
} catch (error) {
|
||||
// Continue with flush even if we can't count keys
|
||||
}
|
||||
|
||||
// Flush the Redis cache
|
||||
await redis.flushdb();
|
||||
console.log('✅ Redis cache flushed successfully');
|
||||
|
||||
if (keyCount > 0) {
|
||||
console.log(` Deleted ${keyCount} keys`);
|
||||
}
|
||||
|
||||
await redis.disconnect();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Error flushing Redis cache:', error.message);
|
||||
if (verbose) {
|
||||
console.error(' Full error:', error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function flushFileCache(dryRun = false, verbose = false) {
|
||||
const dataDir = path.join(__dirname, '..', 'data');
|
||||
const filesToClear = [path.join(dataDir, 'logs.json'), path.join(dataDir, 'violations.json')];
|
||||
|
||||
console.log('🔍 Checking file-based cache');
|
||||
|
||||
if (dryRun) {
|
||||
console.log('🔍 [DRY RUN] Would flush file cache');
|
||||
for (const filePath of filesToClear) {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const stats = fs.statSync(filePath);
|
||||
console.log(
|
||||
` Would delete: ${path.basename(filePath)} (${(stats.size / 1024).toFixed(1)} KB)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let deletedCount = 0;
|
||||
let totalSize = 0;
|
||||
|
||||
for (const filePath of filesToClear) {
|
||||
try {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const stats = fs.statSync(filePath);
|
||||
totalSize += stats.size;
|
||||
fs.unlinkSync(filePath);
|
||||
deletedCount++;
|
||||
if (verbose) {
|
||||
console.log(
|
||||
` ✅ Deleted ${path.basename(filePath)} (${(stats.size / 1024).toFixed(1)} KB)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (verbose) {
|
||||
console.log(` ❌ Failed to delete ${path.basename(filePath)}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedCount > 0) {
|
||||
console.log('✅ File cache flushed successfully');
|
||||
console.log(` Deleted ${deletedCount} cache files (${(totalSize / 1024).toFixed(1)} KB)`);
|
||||
} else {
|
||||
console.log('ℹ️ No file cache to flush');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function restartRecommendation() {
|
||||
console.log('\n💡 RECOMMENDATION:');
|
||||
console.log(' For complete cache clearing, especially for in-memory caches,');
|
||||
console.log(' consider restarting the LibreChat backend:');
|
||||
console.log('');
|
||||
console.log(' npm run backend:stop');
|
||||
console.log(' npm run backend:dev');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const dryRun = args.includes('--dry-run');
|
||||
const verbose = args.includes('--verbose') || args.includes('-v');
|
||||
const help = args.includes('--help') || args.includes('-h');
|
||||
|
||||
if (help) {
|
||||
await showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🧹 LibreChat Cache Flush Utility');
|
||||
console.log('================================');
|
||||
|
||||
if (dryRun) {
|
||||
console.log('🔍 DRY RUN MODE - No actual changes will be made\n');
|
||||
}
|
||||
|
||||
let success = true;
|
||||
const isRedisEnabled = isEnabled(USE_REDIS) && REDIS_URI;
|
||||
|
||||
// Flush the appropriate cache type
|
||||
if (isRedisEnabled) {
|
||||
success = (await flushRedisCache(dryRun, verbose)) && success;
|
||||
} else {
|
||||
console.log('ℹ️ Redis not configured, using file-based cache only');
|
||||
}
|
||||
|
||||
// Always check file cache
|
||||
success = (await flushFileCache(dryRun, verbose)) && success;
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
|
||||
if (success) {
|
||||
if (dryRun) {
|
||||
console.log('✅ Cache flush preview completed');
|
||||
console.log(' Run without --dry-run to actually flush the cache');
|
||||
} else {
|
||||
console.log('✅ Cache flush completed successfully');
|
||||
console.log('⚠️ Note: All users will need to re-authenticate');
|
||||
}
|
||||
|
||||
if (!isRedisEnabled) {
|
||||
await restartRecommendation();
|
||||
}
|
||||
} else {
|
||||
console.log('❌ Cache flush completed with errors');
|
||||
console.log(' Check the output above for details');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle errors gracefully
|
||||
process.on('unhandledRejection', (error) => {
|
||||
console.error('❌ Unhandled error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('❌ Uncaught exception:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Run the main function
|
||||
if (require.main === module) {
|
||||
main().catch((error) => {
|
||||
console.error('❌ Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { flushRedisCache, flushFileCache };
|
Loading…
Add table
Add a link
Reference in a new issue