diff --git a/.env.example b/.env.example index 6303808412..f2ffe103b8 100644 --- a/.env.example +++ b/.env.example @@ -565,9 +565,9 @@ HELP_AND_FAQ_URL=https://librechat.ai # users always get the latest version. Customize # # only if you understand caching implications. # -# INDEX_HTML_CACHE_CONTROL=no-cache, no-store, must-revalidate -# INDEX_HTML_PRAGMA=no-cache -# INDEX_HTML_EXPIRES=0 +# INDEX_CACHE_CONTROL=no-cache, no-store, must-revalidate +# INDEX_PRAGMA=no-cache +# INDEX_EXPIRES=0 # no-cache: Forces validation with server before using cached version # no-store: Prevents storing the response entirely diff --git a/.github/workflows/helmcharts.yml b/.github/workflows/helmcharts.yml index bc715557e4..a8e3ef9b72 100644 --- a/.github/workflows/helmcharts.yml +++ b/.github/workflows/helmcharts.yml @@ -29,5 +29,8 @@ jobs: - name: Run chart-releaser uses: helm/chart-releaser-action@v1.6.0 + with: + charts_dir: helm + skip_existing: true env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/i18n-unused-keys.yml b/.github/workflows/i18n-unused-keys.yml index f720a61783..6bcf824946 100644 --- a/.github/workflows/i18n-unused-keys.yml +++ b/.github/workflows/i18n-unused-keys.yml @@ -22,7 +22,7 @@ jobs: # Define paths I18N_FILE="client/src/locales/en/translation.json" - SOURCE_DIRS=("client/src" "api") + SOURCE_DIRS=("client/src" "api" "packages/data-provider/src") # Check if translation file exists if [[ ! -f "$I18N_FILE" ]]; then diff --git a/.gitignore b/.gitignore index a4d2d8fc7e..0b64a284b5 100644 --- a/.gitignore +++ b/.gitignore @@ -113,4 +113,11 @@ uploads/ # owner release/ + +# Helm +helm/librechat/Chart.lock +helm/**/charts/ +helm/**/.values.yaml + !/client/src/@types/i18next.d.ts + diff --git a/CHANGELOG.md b/CHANGELOG.md index eb4c65c3ab..f39c86cfa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,23 +5,38 @@ All notable changes to this project will be documented in this file. + ## [Unreleased] ### ✨ New Features - ✨ feat: implement search parameter updates by **@mawburn** in [#7151](https://github.com/danny-avila/LibreChat/pull/7151) - 🎏 feat: Add MCP support for Streamable HTTP Transport by **@benverhees** in [#7353](https://github.com/danny-avila/LibreChat/pull/7353) +- 🔒 feat: Add Content Security Policy using Helmet middleware by **@rubentalstra** in [#7377](https://github.com/danny-avila/LibreChat/pull/7377) +- ✨ feat: Add Normalization for MCP Server Names by **@danny-avila** in [#7421](https://github.com/danny-avila/LibreChat/pull/7421) +- 📊 feat: Improve Helm Chart by **@hofq** in [#3638](https://github.com/danny-avila/LibreChat/pull/3638) + +### 🌍 Internationalization + +- 🌍 i18n: Add `Danish` and `Czech` and `Catalan` localization support by **@rubentalstra** in [#7373](https://github.com/danny-avila/LibreChat/pull/7373) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#7375](https://github.com/danny-avila/LibreChat/pull/7375) ### 🔧 Fixes - 💬 fix: update aria-label for accessibility in ConvoLink component by **@berry-13** in [#7320](https://github.com/danny-avila/LibreChat/pull/7320) - 🔑 fix: use `apiKey` instead of `openAIApiKey` in OpenAI-like Config by **@danny-avila** in [#7337](https://github.com/danny-avila/LibreChat/pull/7337) - 🔄 fix: update navigation logic in `useFocusChatEffect` to ensure correct search parameters are used by **@mawburn** in [#7340](https://github.com/danny-avila/LibreChat/pull/7340) +- 🔄 fix: Improve MCP Connection Cleanup by **@danny-avila** in [#7400](https://github.com/danny-avila/LibreChat/pull/7400) +- 🛡️ fix: Preset and Validation Logic for URL Query Params by **@danny-avila** in [#7407](https://github.com/danny-avila/LibreChat/pull/7407) +- 🌘 fix: artifact of preview text is illegible in dark mode by **@nhtruong** in [#7405](https://github.com/danny-avila/LibreChat/pull/7405) +- 🛡️ fix: Temporarily Remove CSP until Configurable by **@danny-avila** in [#7419](https://github.com/danny-avila/LibreChat/pull/7419) +- 💽 fix: Exclude index page `/` from static cache settings by **@sbruel** in [#7382](https://github.com/danny-avila/LibreChat/pull/7382) ### ⚙️ Other Changes - 📜 docs: CHANGELOG for release v0.7.8 by **@github-actions[bot]** in [#7290](https://github.com/danny-avila/LibreChat/pull/7290) - 📦 chore: Update API Package Dependencies by **@danny-avila** in [#7359](https://github.com/danny-avila/LibreChat/pull/7359) +- 📜 docs: Unreleased Changelog by **@github-actions[bot]** in [#7321](https://github.com/danny-avila/LibreChat/pull/7321) @@ -67,7 +82,6 @@ Changes from v0.7.8-rc1 to v0.7.8. --- ## [v0.7.8-rc1] - -## [v0.7.8-rc1] - Changes from v0.7.7 to v0.7.8-rc1. diff --git a/api/app/clients/tools/structured/OpenAIImageTools.js b/api/app/clients/tools/structured/OpenAIImageTools.js index 85941a779a..afea9dfd55 100644 --- a/api/app/clients/tools/structured/OpenAIImageTools.js +++ b/api/app/clients/tools/structured/OpenAIImageTools.js @@ -30,7 +30,7 @@ const DEFAULT_IMAGE_EDIT_DESCRIPTION = When to use \`image_edit_oai\`: - The user wants to modify, extend, or remix one **or more** uploaded images, either: - - Previously generated, or in the current request (both to be included in the \`image_ids\` array). +- Previously generated, or in the current request (both to be included in the \`image_ids\` array). - Always when the user refers to uploaded images for editing, enhancement, remixing, style transfer, or combining elements. - Any current or existing images are to be used as visual guides. - If there are any files in the current request, they are more likely than not expected as references for image edit requests. diff --git a/api/package.json b/api/package.json index bcf94a6cad..1f2e326f7c 100644 --- a/api/package.json +++ b/api/package.json @@ -86,7 +86,7 @@ "mime": "^3.0.0", "module-alias": "^2.2.3", "mongoose": "^8.12.1", - "multer": "^1.4.5-lts.1", + "multer": "^2.0.0", "nanoid": "^3.3.7", "nodemailer": "^6.9.15", "ollama": "^0.5.0", diff --git a/api/server/controllers/assistants/chatV1.js b/api/server/controllers/assistants/chatV1.js index 5fa10e9e37..9129a6a1c1 100644 --- a/api/server/controllers/assistants/chatV1.js +++ b/api/server/controllers/assistants/chatV1.js @@ -326,8 +326,15 @@ const chatV1 = async (req, res) => { file_ids = files.map(({ file_id }) => file_id); if (file_ids.length || thread_file_ids.length) { - userMessage.file_ids = file_ids; attachedFileIds = new Set([...file_ids, ...thread_file_ids]); + if (endpoint === EModelEndpoint.azureAssistants) { + userMessage.attachments = Array.from(attachedFileIds).map((file_id) => ({ + file_id, + tools: [{ type: 'file_search' }], + })); + } else { + userMessage.file_ids = Array.from(attachedFileIds); + } } }; diff --git a/api/server/index.js b/api/server/index.js index cd0bdd3f88..f7548f840b 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -24,10 +24,13 @@ const routes = require('./routes'); const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION, TRUST_PROXY } = process.env ?? {}; -const port = Number(PORT) || 3080; +// Allow PORT=0 to be used for automatic free port assignment +const port = isNaN(Number(PORT)) ? 3080 : Number(PORT); const host = HOST || 'localhost'; const trusted_proxy = Number(TRUST_PROXY) || 1; /* trust first proxy by default */ +const app = express(); + const startServer = async () => { if (typeof Bun !== 'undefined') { axios.defaults.headers.common['Accept-Encoding'] = 'gzip'; @@ -36,8 +39,9 @@ const startServer = async () => { logger.info('Connected to MongoDB'); await indexSync(); - const app = express(); app.disable('x-powered-by'); + app.set('trust proxy', trusted_proxy); + await AppService(app); const indexPath = path.join(app.locals.paths.dist, 'index.html'); @@ -49,23 +53,24 @@ const startServer = async () => { app.use(noIndex); app.use(errorController); app.use(express.json({ limit: '3mb' })); - app.use(mongoSanitize()); app.use(express.urlencoded({ extended: true, limit: '3mb' })); - app.use(staticCache(app.locals.paths.dist)); - app.use(staticCache(app.locals.paths.fonts)); - app.use(staticCache(app.locals.paths.assets)); - app.set('trust proxy', trusted_proxy); + app.use(mongoSanitize()); app.use(cors()); app.use(cookieParser()); if (!isEnabled(DISABLE_COMPRESSION)) { app.use(compression()); + } else { + console.warn('Response compression has been disabled via DISABLE_COMPRESSION.'); } + // Serve static assets with aggressive caching + app.use(staticCache(app.locals.paths.dist)); + app.use(staticCache(app.locals.paths.fonts)); + app.use(staticCache(app.locals.paths.assets)); + if (!ALLOW_SOCIAL_LOGIN) { - console.warn( - 'Social logins are disabled. Set Environment Variable "ALLOW_SOCIAL_LOGIN" to true to enable them.', - ); + console.warn('Social logins are disabled. Set ALLOW_SOCIAL_LOGIN=true to enable them.'); } /* OAUTH */ @@ -128,7 +133,7 @@ const startServer = async () => { }); app.listen(port, host, () => { - if (host == '0.0.0.0') { + if (host === '0.0.0.0') { logger.info( `Server listening on all interfaces at port ${port}. Use http://localhost:${port} to access it`, ); @@ -176,3 +181,6 @@ process.on('uncaughtException', (err) => { process.exit(1); }); + +// export app for easier testing purposes +module.exports = app; diff --git a/api/server/index.spec.js b/api/server/index.spec.js new file mode 100644 index 0000000000..493229c2f4 --- /dev/null +++ b/api/server/index.spec.js @@ -0,0 +1,78 @@ +const fs = require('fs'); +const path = require('path'); +const request = require('supertest'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const mongoose = require('mongoose'); + +describe('Server Configuration', () => { + // Increase the default timeout to allow for Mongo cleanup + jest.setTimeout(30_000); + + let mongoServer; + let app; + + /** Mocked fs.readFileSync for index.html */ + const originalReadFileSync = fs.readFileSync; + beforeAll(() => { + fs.readFileSync = function (filepath, options) { + if (filepath.includes('index.html')) { + return '