From b49f963a6c3b888b38c2c03e60532ad66eefbc2a Mon Sep 17 00:00:00 2001 From: paychex-ssmithrand <108530706+paychex-ssmithrand@users.noreply.github.com> Date: Tue, 2 Jan 2024 11:25:18 -0500 Subject: [PATCH] Merge upstream (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * documentation update: mongodb (#1347) * update dotenv.md add details about the MONGO_URI connection string format * update mongodb.md add details about the MONGO_URI connection string format * chore: Get the latest of all github actions (#1335) * refactor(api): Central Logging 📜 (#1348) * WIP: initial logging changes add several transports in ~/config/winston omit messages in logs, truncate long strings add short blurb in dotenv for debug logging GoogleClient: using logger OpenAIClient: using logger, handleOpenAIErrors Adding typedef for payload message bumped winston and using winston-daily-rotate-file moved config for server paths to ~/config dir Added `DEBUG_LOGGING=true` to .env.example * WIP: Refactor logging statements in code * WIP: Refactor logging statements and import configurations * WIP: Refactor logging statements and import configurations * refactor: broadcast Redis initialization message with `info` not `debug` * refactor: complete Refactor logging statements and import configurations * chore: delete unused tools * fix: circular dependencies due to accessing logger * refactor(handleText): handle booleans and write tests * refactor: redact sensitive values, better formatting * chore: improve log formatting, avoid passing strings to 2nd arg * fix(ci): fix jest tests due to logger changes * refactor(getAvailablePluginsController): cache plugins as they are static and avoids async addOpenAPISpecs call every time * chore: update docs * chore: update docs * chore: create separate meiliSync logger, clean up logs to avoid being unnecessarily verbose * chore: spread objects where they are commonly logged to allow string truncation * chore: improve error log formatting * fix: Add error handling for missing role in OpenAIClient.js (#1352) * feat: Google Gemini ❇️ (#1355) * refactor: add gemini-pro to google Models list; use defaultModels for central model listing * refactor(SetKeyDialog): create useMultipleKeys hook to use for Azure, export `isJson` from utils, use EModelEndpoint * refactor(useUserKey): change variable names to make keyName setting more clear * refactor(FileUpload): allow passing container className string * feat(GoogleClient): Gemini support * refactor(GoogleClient): alternate stream speed for Gemini models * feat(Gemini): styling/settings configuration for Gemini * refactor(GoogleClient): substract max response tokens from max context tokens if context is above 32k (I/O max is combined between the two) * refactor(tokens): correct google max token counts and subtract max response tokens when input/output count are combined towards max context count * feat(google/initializeClient): handle both local and user_provided credentials and write tests * fix(GoogleClient): catch if credentials are undefined, handle if serviceKey is string or object correctly, handle no examples passed, throw error if not a Generative Language model and no service account JSON key is provided, throw error if it is a Generative m odel, but not google API key was provided * refactor(loadAsyncEndpoints/google): activate Google endpoint if either the service key JSON file is provided in /api/data, or a GOOGLE_KEY is defined. * docs: updated Google configuration * fix(ci): Mock import of Service Account Key JSON file (auth.json) * Update apis_and_tokens.md * feat: increase max output tokens slider for gemini pro * refactor(GoogleSettings): handle max and default maxOutputTokens on model change * chore: add sensitive redact regex * docs: add warning about data privacy * Update apis_and_tokens.md * fix: Avoid Throwing Errors for Unsupported Token Count Endpoints 🪙 (#1356) * update: docker-compose.yaml 🐋 (#1341) * fix: Enhance Test Coverage and Fix Compatibility Issues 👷‍♂️ (#1363) * refactor: only remove conversation states from localStorage on login/logout but not on refresh * chore: add debugging log for azure completion url * chore: add api-key to redact regex * fix: do not show endpoint selector if endpoint is falsy * chore: remove logger from genAzureChatCompletion * feat(ci): mock fetchEventSource * refactor(ci): mock all model methods in BaseClient.test, as well as mock the implementation for getCompletion in FakeClient * fix(OpenAIClient): consider chatCompletion if model name includes `gpt` as opposed to `gpt-` * fix(ChatGPTClient/azureOpenAI): Remove 'model' option for Azure compatibility (cannot be sent in payload body) * feat(ci): write new test suite that significantly increase test coverage for OpenAIClient and BaseClient by covering most of the real implementation of the `sendMessage` method - test for the azure edge case where model option is appended to modelOptions, ensuring removal before sent to the azure endpoint - test for expected azure url being passed to SSE POST request - test for AZURE_OPENAI_DEFAULT_MODEL being set, but is not included in the URL deployment name as expected - test getCompletion method to have correct payload fix(ci/OpenAIClient.test.js): correctly mock hanging/async methods * refactor(addTitle): allow azure to title as it aborts signal on completion * 🔄 refactor: Consolidate Ask/Edit Controllers (#1365) * refactor(Ask/Edit): consolidate ask/edit controllers between the main modules and openAI controllers to reduce repetition of code and increase reusability * fix(winston/logger): circular dependency issue * fix(config/scripts): fix script imports * refactor(indexSync): make not configured message an info log message * chore: create a rollup script for api/server/index.js to check circular dependencies * chore: bump @keyv/redis * Update package.json * 🍏Update mac_install.md (#1373) The manual installation guide for mac was very outdated. This brings it up to date with the current method * 📸 feat: Gemini vision, Improved Logs and Multi-modal Handling (#1368) * feat: add GOOGLE_MODELS env var * feat: add gemini vision support * refactor(GoogleClient): adjust clientOptions handling depending on model * fix(logger): fix redact logic and redact errors only * fix(GoogleClient): do not allow non-multiModal messages when gemini-pro-vision is selected * refactor(OpenAIClient): use `isVisionModel` client property to avoid calling validateVisionModel multiple times * refactor: better debug logging by correctly traversing, redacting sensitive info, and logging condensed versions of long values * refactor(GoogleClient): allow response errors to be thrown/caught above client handling so user receives meaningful error message debug orderedMessages, parentMessageId, and buildMessages result * refactor(AskController): use model from client.modelOptions.model when saving intermediate messages, which requires for the progress callback to be initialized after the client is initialized * feat(useSSE): revert to previous model if the model was auto-switched by backend due to message attachments * docs: update with google updates, notes about Gemini Pro Vision * fix: redis should not be initialized without USE_REDIS and increase max listeners to 20 * fix: revert `fonts` resolution path in vite.config.ts * 🚩 fix: Initialize Conversation Only when Necessary Data is Fetched (#1379) * fix(ChatRoute): only initialize conversation after all data is fetched (models, endpoints, initialConversationQuery if not `new`) * chore: remove unnecessary packages for rolling up api * chore: bump data-provider package.json * ✨ feat: Azure Vision Support & Docs Update (#1389) * feat(AzureOpenAI): Vision Support * chore(ci/OpenAIClient.test): update test to reflect Azure now uses chatCompletion method as opposed to getCompletion, while still testing the latter method * docs: update documentation mainly revolving around Azure setup, but also reformatting the 'Tokens and API' section completely * docs: add images and links to ai_setup.md * docs: ai setup reference * ✨ Release v0.6.5 (#1391) * ✨ Release v0.6.5 * fix(ci): use dynamic currentDateString * Update pull_request_template.md (#1417) * 🧹📚 docs: refactor and clean up (#1392) * 📑 update mkdocs * rename docker override file and add to gitignore * update .env.example - GOOGLE_MODELS * update index.md * doc refactor: split installation and configuration in two sub-folders * doc update: installation guides * doc update: configuration guides * doc: new docker override guide * doc: new beginner's guide for contributions - Thanks @Berry-13 * doc: update documentation_guidelines.md * doc: update testing.md * doc: update deployment guides * doc: update /dev readme * doc: update general_info * doc: add 0 value to doc weight * doc: add index.md to every doc folders * doc: add weight to index.md and move openrouter from free_ai_apis.md to ai_setup.md * doc: update toc so they display properly on the right had side in mkdocs * doc: update pandoranext.md * doc: index logging_system.md * doc: update readme.md * doc: update litellm.md * doc: update ./dev/readme.md * doc:🔖 new presets.md * doc: minor corrections * doc update: user_auth_system.md and presets.md, doc feat: add mermaid support to mkdocs * doc update: add screenshots to presets.md * doc update: add screenshots to - OpenID with AWS Cognito * doc update: BingAI cookie instruction * doc update: discord auth * doc update: facebook auth * doc: corrections to user_auth_system.md * doc update: github auth * doc update: google auth * doc update: auth clean up * doc organization: installation * doc organization: configuration * doc organization: features+plugins & update:plugins screenshots * doc organization: deploymend + general_info & update: tech_stack.md * doc organization: contributions * doc: minor fixes * doc: minor fixes * Update French Translation (#1444) * 🪪mkdocs: social cards (#1428) * mkdocs plugins: add plugin for social cards and plugin that allow to exclude a folder * docs: fix hyperlinks * mkdocs: social cards (descriptions) for 'contributions' and 'deployment' guides * mkdocs: social cards (descriptions) for all 'index.md' * mkdocs: social cards (descriptions) for 'features' and 'plugins' * mkdocs: social cards (descriptions) for 'general_info' * mkdocs: social cards (descriptions) for 'configuration' * mkdocs: social cards (descriptions) for 'installation' * mkdocs: minor fixes * update librechat.svg * update how_to_contribute.md add reference to the official GitHub documentation * 🌎: Italian translation update & refactor: translations (#1414) * italian translation update * fix: removed some translations * refactor(Translation) * feat: allow FWA (#1440) * 🌎: Update Russian translations (#1413) * Update Russian localization * Update Ru.tsx * fix: russian translation typing errors * 🐛 fix: Prevent Unnecessary Cloning of Symbols in Log Object (#1455) fix(api/config/parsers): prevent cloning of unnecessary symbols within log object by using `klona` instead of `klona/full`, handle symbols edge case, log parsing errors, and use spaces instead of tab for cleaner logs * 🚫🔍 feat: disallow search indexing (#1409) * feat: disallow search indexing * Update index.js * Update .env.example * added middleware * 🔥🚀 feat: CDN (Firebase) & feat: account section (#1438) * localization + api-endpoint * docs: added firebase documentation * chore: icons * chore: SettingsTabs * feat: account pannel; fix: gear icons * docs: position update * feat: firebase * feat: plugin support * route * fixed bugs with firebase and moved a lot of files * chore(DALLE3): using UUID v4 * feat: support for social strategies; moved '/images' path * fix: data ignored * gitignore update * docs: update firebase guide * refactor: Firebase - use singleton pattern for firebase initialization, initially on server start - reorganize imports, move firebase specific files to own service under Files - rename modules to remove 'avatar' redundancy - fix imports based on changes * ci(DALLE/DALLE3): fix tests to use logger and new expected outputs, add firebase tests * refactor(loadToolWithAuth): pass userId to tool as field * feat(images/parse): feat: Add URL Image Basename Extraction Implement a new module to extract the basename of an image from a given URL. This addition includes the function, which parses the URL and retrieves the basename using the Node.js 'url' and 'path' modules. The function is documented with JSDoc comments for better maintainability and understanding. This feature enhances the application's ability to handle and process image URLs efficiently. * refactor(addImages): function to use a more specific regular expression for observedImagePath based on the generated image markdown standard across the app * refactor(DALLE/DALLE3): utilize `getImageBasename` and `this.userId`; fix: pass correct image path to firebase url helper * fix(addImages): make more general to match any image markdown descriptor * fix(parse/getImageBasename): test result of this function for an actual image basename * ci(DALLE3): mock getImageBasename * refactor(AuthContext): use Recoil atom state for user * feat: useUploadAvatarMutation, react-query hook for avatar upload * fix(Toast): stack z-order of Toast over all components (1000) * refactor(showToast): add optional status field to avoid importing NotificationSeverity on each use of the function * refactor(routes/avatar): remove unnecessary get route, get userId from req.user.id, require auth on POST request * chore(uploadAvatar): TODO: remove direct use of Model, `User` * fix(client): fix Spinner imports * refactor(Avatar): use react-query hook, Toast, remove unnecessary states, add optimistic UI to upload * fix(avatar/localStrategy): correctly save local profile picture and cache bust for immediate rendering; fix: firebase init info message (only show once) * fix: use `includes` instead of `endsWith` for checking manual query of avatar image path in case more queries are appended (as is done in avatar/localStrategy) --------- Co-authored-by: Danny Avila * 🧹fix: Handle Abort Message Edge Cases (#1462) * chore: bump langchain to v0.0.213 from v0.0.186 * fix: handle abort edge cases: - abort message server-side if response experienced error mid-generation - attempt to recover message if aborting resulted in error - if abortKey is not provided, use conversationId if it exists - if headers were already sent, send an Event stream message - issue warning for possible Google censor/filter refactor(streamResponse): for `sendError`, allow passing overrides so that error can include partial generation, improve typing for `sendMessage` * chore(MessageContent): remove eslint warning for unused `i`, rephrase unfinished message text * fix(useSSE): avoid invoking cancelHandler if the abort response was 404 * chore(TMessage): remove unnecessary, unused legacy message property `submitting` * chore(TMessage): remove unnecessary legacy message property `cancelled` * chore(abortMiddleware): remove unused `errorText` property to avoid confusion * doc update: firebase.md (#1456) * 📃 feat: add `list-balances`, `remove-user`, and improve User scripts (#1418) * Refactoring opening of DB to config/helpers.js * Adding two user scripts: - 'delete-user' to remove a user definitely - 'list-balances' to show the balances of all the users * Add Russian translation for the new functionality in the settings (#1457) * 🛠️ fix: Minor Fixes in `Message`, `Ask/EditController`, `OpenAIClient`, and `countTokens` (#1463) * fix(Message): avoid overwriting unprovided properties * fix(OpenAIClient): return intermediateReply on user abort * fix(AskController): do not send/save final message if abort was triggered * fix(countTokens): avoid fetching remote registry and exclusively use cl100k_base or p50k_base weights for token counting * refactor(Message/messageSchema): rely on messageSchema for default values when saving messages * fix(EditController): do not send/save final message if abort was triggered * fix(config/helpers): fix module resolution error * update pull_request_template.md (#1466) add "Translation update" the the PR type choices * 🌎: Update French Translation (#1472) * 🌎: Update French Translation * 🌎: Update French Translation * 🌎: Update French Translation * 🌎: Update French Translation * 🌎: Update Portuguese Translation (#1461) * 👮feat: moderation text (#1388) * fixed some bugs and handling errors better * feat: plugins support * fix: prettier error message * moved circular-json-es6 in /api * docs: added openai moderation text * fix(gptPlugins): incorrect merge * discarding changes * removed circular-json-es6 * fix(OpenAIClient): do not invoke abortCompletion on completion error (#1473) * Update dev-images.yml * Update backend-review.yml --------- Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com> Co-authored-by: Ed Burnette Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com> Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com> Co-authored-by: Nikita Dybov Co-authored-by: Danny Avila Co-authored-by: Linus Gasser Co-authored-by: MACHINSOFT <110278369+machinsoft@users.noreply.github.com> Co-authored-by: Raí Santos <140329135+itzraiss@users.noreply.github.com> --- .env.example | 25 + .github/playwright.yml | 4 +- .github/pull_request_template.md | 5 +- .github/workflows/backend-review.yml | 40 + .github/workflows/container.yml | 52 + .github/workflows/data-provider.yml | 34 + .github/workflows/deploy.yml | 38 + .github/workflows/dev-images.yml | 45 + .github/workflows/frontend-review.yml | 38 + .github/workflows/latest-images-main.yml | 40 + .github/workflows/mkdocs.yaml | 27 + .gitignore | 8 +- README.md | 104 +- api/app/bingai.js | 5 +- api/app/clients/AnthropicClient.js | 38 +- api/app/clients/BaseClient.js | 91 +- api/app/clients/ChatGPTClient.js | 6 + api/app/clients/GoogleClient.js | 196 +- api/app/clients/OpenAIClient.js | 124 +- api/app/clients/PluginsClient.js | 74 +- api/app/clients/TextStream.js | 5 +- .../agents/CustomAgent/outputParser.js | 50 +- .../agents/Functions/FunctionsAgent.js | 4 +- .../clients/callbacks/createStartHandler.js | 27 +- api/app/clients/chains/runTitleChain.js | 4 +- api/app/clients/llm/RunManager.js | 25 +- api/app/clients/memory/summaryBuffer.js | 12 +- api/app/clients/output_parsers/addImages.js | 10 +- .../clients/prompts/formatMessages.spec.js | 1 - api/app/clients/specs/BaseClient.test.js | 40 +- api/app/clients/specs/FakeClient.js | 14 +- api/app/clients/specs/OpenAIClient.test.js | 239 +- api/app/clients/tools/AIPluginTool.js | 238 - api/app/clients/tools/AzureAiSearch.js | 5 +- api/app/clients/tools/DALL-E.js | 71 +- api/app/clients/tools/GoogleSearch.js | 7 +- api/app/clients/tools/HttpRequestTool.js | 108 - api/app/clients/tools/StableDiffusion.js | 5 +- api/app/clients/tools/Wolfram.js | 8 +- .../clients/tools/dynamic/OpenAPIPlugin.js | 34 +- api/app/clients/tools/index.js | 4 - api/app/clients/tools/saveImageFromUrl.js | 5 +- .../clients/tools/structured/AzureAISearch.js | 5 +- .../clients/tools/structured/CodeSherpa.js | 4 +- .../tools/structured/CodeSherpaTools.js | 4 +- api/app/clients/tools/structured/DALLE3.js | 58 +- api/app/clients/tools/structured/E2BTools.js | 23 +- .../tools/structured/StableDiffusion.js | 5 +- api/app/clients/tools/structured/Wolfram.js | 10 +- .../tools/structured/specs/DALLE3.spec.js | 83 +- .../clients/tools/util/handleOpenAIErrors.js | 9 +- api/app/clients/tools/util/handleTools.js | 29 +- api/app/clients/tools/util/loadSpecs.js | 37 +- api/app/titleConvoBing.js | 8 +- api/cache/banViolation.js | 8 +- api/cache/keyvMongo.js | 4 +- api/cache/keyvRedis.js | 13 +- api/common/enums.js | 2 + api/config.js | 6 - api/config/index.js | 5 + api/config/meiliLogger.js | 78 + api/config/parsers.js | 182 + api/config/paths.js | 6 + api/config/winston.js | 127 + api/jest.config.js | 8 +- api/lib/db/indexSync.js | 34 +- api/models/Balance.js | 29 +- api/models/Config.js | 6 +- api/models/Conversation.js | 14 +- api/models/File.js | 6 +- api/models/Message.js | 27 +- api/models/Preset.js | 7 +- api/models/Prompt.js | 7 +- api/models/Session.js | 16 +- api/models/Transaction.js | 2 +- api/models/checkBalance.js | 1 - api/models/plugins/mongoMeili.js | 42 +- api/models/schema/messageSchema.js | 7 +- api/models/spendTokens.js | 7 +- api/package.json | 13 +- api/server/controllers/AskController.js | 131 +- api/server/controllers/AuthController.js | 16 +- api/server/controllers/EditController.js | 78 +- api/server/controllers/ErrorController.js | 8 +- api/server/controllers/PluginController.js | 14 +- api/server/controllers/UserController.js | 13 +- .../controllers/auth/LoginController.js | 7 +- .../controllers/auth/LogoutController.js | 5 +- api/server/index.js | 27 +- api/server/middleware/abortMiddleware.js | 47 +- api/server/middleware/index.js | 4 + api/server/middleware/moderateText.js | 39 + api/server/middleware/noIndex.js | 11 + api/server/routes/ask/addToCache.js | 3 +- api/server/routes/ask/askChatGPTBrowser.js | 21 +- api/server/routes/ask/bingAI.js | 24 +- api/server/routes/ask/gptPlugins.js | 11 +- api/server/routes/ask/openAI.js | 150 +- api/server/routes/assistants/assistants.js | 4 +- api/server/routes/assistants/chat.js | 5 +- api/server/routes/config.js | 6 +- api/server/routes/convos.js | 13 +- api/server/routes/edit/gptPlugins.js | 23 +- api/server/routes/edit/openAI.js | 134 +- api/server/routes/files/avatar.js | 34 + api/server/routes/files/files.js | 5 +- api/server/routes/files/images.js | 7 +- api/server/routes/files/index.js | 1 + api/server/routes/oauth.js | 8 +- api/server/routes/presets.js | 14 +- api/server/routes/search.js | 31 +- api/server/routes/tokenizer.js | 7 +- api/server/services/AuthService.js | 20 +- .../services/Config/loadAsyncEndpoints.js | 6 +- .../services/Config/loadDefaultModels.js | 27 +- .../Endpoints/google/initializeClient.js | 22 +- .../Endpoints/google/initializeClient.spec.js | 84 + .../services/Endpoints/openAI/addTitle.js | 4 +- api/server/services/Files/Firebase/images.js | 45 + api/server/services/Files/Firebase/index.js | 7 + .../services/Files/Firebase/initialize.js | 42 + .../Files/images/avatar/firebaseStrategy.js | 29 + .../Files/images/avatar/localStrategy.js | 32 + .../Files/images/avatar/uploadAvatar.js | 63 + api/server/services/Files/images/encode.js | 31 +- api/server/services/Files/images/index.js | 4 + api/server/services/Files/images/parse.js | 27 + api/server/services/Files/save.js | 3 +- api/server/services/ModelService.js | 44 +- api/server/services/PluginService.js | 13 +- api/server/services/Runs/RunMananger.js | 10 +- api/server/services/UserService.js | 7 +- api/server/utils/countTokens.js | 10 +- api/server/utils/crypto.js | 2 + api/server/utils/handleText.js | 20 +- api/server/utils/handleText.spec.js | 51 + api/server/utils/math.js | 3 +- api/server/utils/sendEmail.js | 11 +- api/server/utils/streamResponse.js | 30 +- api/strategies/discordStrategy.js | 68 +- api/strategies/facebookStrategy.js | 62 +- api/strategies/githubStrategy.js | 58 +- api/strategies/googleStrategy.js | 58 +- api/strategies/joseStrategy.js | 16 +- api/strategies/jwtStrategy.js | 7 +- api/strategies/openidStrategy.js | 18 +- api/test/__mocks__/auth.mock.json | 13 + api/test/__mocks__/fetchEventSource.js | 27 + api/test/__mocks__/logger.js | 10 + api/typedefs.js | 26 + api/utils/azureUtils.js | 10 +- api/utils/findMessageContent.js | 4 +- api/utils/tokens.js | 11 +- api/utils/tokens.spec.js | 3 + client/index.html | 1 + client/package.json | 2 +- client/src/App.jsx | 2 +- client/src/common/types.ts | 1 + client/src/components/Chat/ChatView.tsx | 2 +- client/src/components/Chat/Footer.tsx | 2 +- .../components/Chat/Input/HeaderOptions.tsx | 2 +- .../components/Chat/Input/PopoverButtons.tsx | 28 +- .../components/Chat/Menus/EndpointsMenu.tsx | 9 +- .../Chat/Messages/Content/MessageContent.tsx | 4 +- client/src/components/Endpoints/Icon.tsx | 11 +- .../components/Endpoints/Settings/Google.tsx | 44 +- .../Settings/MultiView/GoogleSettings.tsx | 9 +- .../Input/EndpointMenu/FileUpload.tsx | 3 + client/src/components/Input/Footer.tsx | 2 +- .../Input/SetKeyDialog/GoogleConfig.tsx | 54 +- .../Input/SetKeyDialog/HelpText.tsx | 61 +- .../Input/SetKeyDialog/InputWithLabel.tsx | 16 +- .../Input/SetKeyDialog/OpenAIConfig.tsx | 46 +- .../src/components/Messages/Content/Error.tsx | 13 +- .../Nav/ExportConversation/ExportModal.jsx | 10 - client/src/components/Nav/Nav.tsx | 2 +- client/src/components/Nav/NavLinks.tsx | 2 +- client/src/components/Nav/Settings.tsx | 21 +- .../Nav/SettingsTabs/Account/Account.tsx | 18 + .../Nav/SettingsTabs/Account/Avatar.tsx | 145 + .../Nav/SettingsTabs/{ => Data}/Data.tsx | 2 +- .../{ => General}/AutoScrollSwitch.spec.tsx | 0 .../{ => General}/AutoScrollSwitch.tsx | 0 .../{ => General}/ClearChatsButton.spec.tsx | 0 .../SettingsTabs/{ => General}/General.tsx | 2 +- .../{ => General}/LangSelector.spec.tsx | 0 .../{ => General}/ThemeSelector.spec.tsx | 0 .../src/components/Nav/SettingsTabs/index.ts | 9 +- client/src/components/svg/GearIcon.tsx | 16 +- client/src/components/svg/GeminiIcon.tsx | 36 + client/src/components/svg/UserIcon.tsx | 15 +- client/src/components/svg/index.ts | 2 + client/src/data-provider/mutations.ts | 17 + client/src/hooks/AuthContext.tsx | 10 +- client/src/hooks/Input/index.ts | 1 + client/src/hooks/Input/useMultipleKeys.ts | 24 + client/src/hooks/Input/useUserKey.ts | 19 +- client/src/hooks/useChatHelpers.ts | 1 - client/src/hooks/useSSE.ts | 20 +- client/src/hooks/useToast.ts | 8 +- client/src/localization/Translation.tsx | 3 +- client/src/localization/languages/Ar.tsx | 1 - client/src/localization/languages/Br.tsx | 164 +- client/src/localization/languages/De.tsx | 1 - client/src/localization/languages/Eng.tsx | 15 +- client/src/localization/languages/Fr.tsx | 58 +- client/src/localization/languages/It.tsx | 233 +- client/src/localization/languages/Ru.tsx | 215 +- client/src/localization/languages/Zh.tsx | 1 - client/src/mobile.css | 9 + client/src/routes/ChatRoute.tsx | 23 +- client/src/store/models.ts | 15 +- client/src/store/user.ts | 6 +- client/src/utils/json.ts | 9 + client/vite.config.ts | 2 +- config/add-balance.js | 35 +- config/ban-user.js | 35 +- config/create-user.js | 35 +- config/delete-user.js | 48 + config/helpers.js | 30 + config/list-balances.js | 39 + config/loader.js | 4 +- config/packages.js | 4 +- config/stop-backend.js | 4 +- config/update.js | 2 +- config/upgrade.js | 2 +- docker-compose.override.yml.example | 67 + docker-compose.yml | 36 +- docs/assets/LibreChat.svg | 48 +- docs/contributions/coding_conventions.md | 7 +- .../contributions/documentation_guidelines.md | 30 +- docs/contributions/how_to_contribute.md | 69 + docs/contributions/index.md | 15 + docs/contributions/testing.md | 57 +- .../contributions/translation_contribution.md | 5 + docs/deployment/azure-terraform.md | 5 + docs/deployment/cloudflare.md | 13 +- docs/deployment/digitalocean.md | 15 +- docs/deployment/heroku.md | 15 +- docs/deployment/hetzner_ubuntu.md | 31 +- docs/deployment/huggingface.md | 28 +- docs/deployment/index.md | 18 + docs/deployment/linode.md | 9 +- docs/deployment/meilisearch_in_render.md | 7 +- docs/deployment/ngrok.md | 30 +- docs/deployment/render.md | 7 +- docs/dev/README.md | 29 +- docs/features/bing_jailbreak.md | 10 +- docs/features/firebase.md | 103 + docs/features/index.md | 33 + docs/features/logging_system.md | 43 + docs/features/manage_your_database.md | 6 + docs/features/mod_system.md | 34 +- docs/features/pandoranext.md | 277 +- docs/features/plugins/azure_ai_search.md | 11 +- .../plugins/chatgpt_plugins_openapi.md | 55 +- docs/features/plugins/google_search.md | 10 +- docs/features/plugins/index.md | 14 + docs/features/plugins/introduction.md | 28 +- docs/features/plugins/make_your_own.md | 13 +- docs/features/plugins/stable_diffusion.md | 14 +- docs/features/plugins/wolfram.md | 31 +- docs/features/presets.md | 92 + docs/features/proxy.md | 32 - docs/features/third_party.md | 6 + docs/features/token_usage.md | 13 + docs/general_info/breaking_changes.md | 11 +- docs/general_info/index.md | 13 + docs/general_info/multilingual_information.md | 5 + docs/general_info/project_origin.md | 5 + docs/general_info/tech_stack.md | 36 +- docs/index.md | 25 +- docs/install/apis_and_tokens.md | 162 - docs/install/configuration/ai_setup.md | 401 ++ .../{ => configuration}/default_language.md | 8 +- docs/install/configuration/docker_override.md | 77 + docs/install/{ => configuration}/dotenv.md | 171 +- .../{ => configuration}/free_ai_apis.md | 36 +- docs/install/configuration/index.md | 19 + docs/install/{ => configuration}/litellm.md | 10 +- docs/install/{ => configuration}/misc.md | 9 +- docs/install/{ => configuration}/mongodb.md | 12 +- .../install/configuration/user_auth_system.md | 578 ++ docs/install/index.md | 27 + .../{ => installation}/container_install.md | 14 +- .../docker_compose_install.md | 81 +- docs/install/installation/index.md | 12 + .../{ => installation}/linux_install.md | 52 +- docs/install/installation/mac_install.md | 143 + .../{ => installation}/windows_install.md | 66 +- docs/install/mac_install.md | 164 - docs/install/user_auth_system.md | 262 - docs/stylesheets/extra.css | 6 - index.html | 1 + mkdocs.yml | 116 +- package-lock.json | 5646 ++++++++++------- package.json | 7 +- packages/data-provider/.gitignore | 3 +- packages/data-provider/package.json | 5 +- .../data-provider/server-rollup.config.js | 40 + packages/data-provider/src/api-endpoints.ts | 2 + packages/data-provider/src/data-service.ts | 4 + packages/data-provider/src/keys.ts | 1 + .../src/react-query/react-query-service.ts | 10 +- packages/data-provider/src/schemas.ts | 113 +- packages/data-provider/src/types/files.ts | 10 + 306 files changed, 10481 insertions(+), 5714 deletions(-) create mode 100644 .github/workflows/backend-review.yml create mode 100644 .github/workflows/container.yml create mode 100644 .github/workflows/data-provider.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/dev-images.yml create mode 100644 .github/workflows/frontend-review.yml create mode 100644 .github/workflows/latest-images-main.yml create mode 100644 .github/workflows/mkdocs.yaml delete mode 100644 api/app/clients/tools/AIPluginTool.js delete mode 100644 api/app/clients/tools/HttpRequestTool.js delete mode 100644 api/config.js create mode 100644 api/config/index.js create mode 100644 api/config/meiliLogger.js create mode 100644 api/config/parsers.js create mode 100644 api/config/paths.js create mode 100644 api/config/winston.js create mode 100644 api/server/middleware/moderateText.js create mode 100644 api/server/middleware/noIndex.js create mode 100644 api/server/routes/files/avatar.js create mode 100644 api/server/services/Endpoints/google/initializeClient.spec.js create mode 100644 api/server/services/Files/Firebase/images.js create mode 100644 api/server/services/Files/Firebase/index.js create mode 100644 api/server/services/Files/Firebase/initialize.js create mode 100644 api/server/services/Files/images/avatar/firebaseStrategy.js create mode 100644 api/server/services/Files/images/avatar/localStrategy.js create mode 100644 api/server/services/Files/images/avatar/uploadAvatar.js create mode 100644 api/server/services/Files/images/parse.js create mode 100644 api/server/utils/handleText.spec.js create mode 100644 api/test/__mocks__/auth.mock.json create mode 100644 api/test/__mocks__/fetchEventSource.js create mode 100644 api/test/__mocks__/logger.js create mode 100644 client/src/components/Nav/SettingsTabs/Account/Account.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/Avatar.tsx rename client/src/components/Nav/SettingsTabs/{ => Data}/Data.tsx (98%) rename client/src/components/Nav/SettingsTabs/{ => General}/AutoScrollSwitch.spec.tsx (100%) rename client/src/components/Nav/SettingsTabs/{ => General}/AutoScrollSwitch.tsx (100%) rename client/src/components/Nav/SettingsTabs/{ => General}/ClearChatsButton.spec.tsx (100%) rename client/src/components/Nav/SettingsTabs/{ => General}/General.tsx (99%) rename client/src/components/Nav/SettingsTabs/{ => General}/LangSelector.spec.tsx (100%) rename client/src/components/Nav/SettingsTabs/{ => General}/ThemeSelector.spec.tsx (100%) create mode 100644 client/src/components/svg/GeminiIcon.tsx create mode 100644 client/src/hooks/Input/useMultipleKeys.ts create mode 100644 config/delete-user.js create mode 100644 config/list-balances.js create mode 100644 docker-compose.override.yml.example create mode 100644 docs/contributions/how_to_contribute.md create mode 100644 docs/contributions/index.md create mode 100644 docs/deployment/index.md create mode 100644 docs/features/firebase.md create mode 100644 docs/features/index.md create mode 100644 docs/features/logging_system.md create mode 100644 docs/features/plugins/index.md create mode 100644 docs/features/presets.md delete mode 100644 docs/features/proxy.md create mode 100644 docs/general_info/index.md delete mode 100644 docs/install/apis_and_tokens.md create mode 100644 docs/install/configuration/ai_setup.md rename docs/install/{ => configuration}/default_language.md (80%) create mode 100644 docs/install/configuration/docker_override.md rename docs/install/{ => configuration}/dotenv.md (69%) rename docs/install/{ => configuration}/free_ai_apis.md (56%) create mode 100644 docs/install/configuration/index.md rename docs/install/{ => configuration}/litellm.md (92%) rename docs/install/{ => configuration}/misc.md (92%) rename docs/install/{ => configuration}/mongodb.md (75%) create mode 100644 docs/install/configuration/user_auth_system.md create mode 100644 docs/install/index.md rename docs/install/{ => installation}/container_install.md (91%) rename docs/install/{ => installation}/docker_compose_install.md (56%) create mode 100644 docs/install/installation/index.md rename docs/install/{ => installation}/linux_install.md (82%) create mode 100644 docs/install/installation/mac_install.md rename docs/install/{ => installation}/windows_install.md (73%) delete mode 100644 docs/install/mac_install.md delete mode 100644 docs/install/user_auth_system.md delete mode 100644 docs/stylesheets/extra.css create mode 100644 packages/data-provider/server-rollup.config.js diff --git a/.env.example b/.env.example index 2c97892d616..22b7743e0d0 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,15 @@ MONGO_URI=mongodb://127.0.0.1:27017/LibreChat DOMAIN_CLIENT=http://localhost:3080 DOMAIN_SERVER=http://localhost:3080 +NO_INDEX=true + +#===============# +# Debug Logging # +#===============# + +DEBUG_LOGGING=true +DEBUG_CONSOLE=false + #=============# # Permissions # #=============# @@ -84,6 +93,7 @@ CHATGPT_MODELS=text-davinci-002-render-sha #============# GOOGLE_KEY=user_provided +# GOOGLE_MODELS=gemini-pro,gemini-pro-vision,chat-bison,chat-bison-32k,codechat-bison,codechat-bison-32k,text-bison,text-bison-32k,text-unicorn,code-gecko,code-bison,code-bison-32k # GOOGLE_REVERSE_PROXY= #============# @@ -178,6 +188,10 @@ MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt # Moderation # #========================# +OPENAI_MODERATION=false +OPENAI_MODERATION_API_KEY= +# OPENAI_MODERATION_REVERSE_PROXY=not working with some reverse proxys + BAN_VIOLATIONS=true BAN_DURATION=1000 * 60 * 60 * 2 BAN_INTERVAL=20 @@ -271,6 +285,17 @@ EMAIL_PASSWORD= EMAIL_FROM_NAME= EMAIL_FROM=noreply@librechat.ai +#========================# +# Firebase CDN # +#========================# + +FIREBASE_API_KEY= +FIREBASE_AUTH_DOMAIN= +FIREBASE_PROJECT_ID= +FIREBASE_STORAGE_BUCKET= +FIREBASE_MESSAGING_SENDER_ID= +FIREBASE_APP_ID= + #==================================================# # Others # #==================================================# diff --git a/.github/playwright.yml b/.github/playwright.yml index 87f35fe101c..28eca14d581 100644 --- a/.github/playwright.yml +++ b/.github/playwright.yml @@ -36,8 +36,8 @@ # PLAYWRIGHT_BROWSERS_PATH: 0 # Places binaries to node_modules/@playwright/test # TITLE_CONVO: false # steps: -# - uses: actions/checkout@v3 -# - uses: actions/setup-node@v3 +# - uses: actions/checkout@v4 +# - uses: actions/setup-node@v4 # with: # node-version: 18 # cache: 'npm' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 94ac8790298..06d2656bd64 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ # Pull Request Template -### ⚠️ Before Submitting a PR, read the [Contributing Docs](./CONTRIBUTING.md) in full! +### ⚠️ Before Submitting a PR, read the [Contributing Docs](https://github.com/danny-avila/LibreChat/blob/main/.github/CONTRIBUTING.md) in full! ## Summary @@ -15,7 +15,8 @@ Please delete any irrelevant options. - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update -- [ ] Documentation update +- [ ] Documentation update +- [ ] Translation update ## Testing diff --git a/.github/workflows/backend-review.yml b/.github/workflows/backend-review.yml new file mode 100644 index 00000000000..fddb6cdac63 --- /dev/null +++ b/.github/workflows/backend-review.yml @@ -0,0 +1,40 @@ +name: Backend Unit Tests +on: + workflow_dispatch: + +jobs: + tests_Backend: + name: Run Backend unit tests + timeout-minutes: 60 + runs-on: ubuntu-latest + env: + MONGO_URI: ${{ secrets.MONGO_URI }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} + CREDS_KEY: ${{ secrets.CREDS_KEY }} + CREDS_IV: ${{ secrets.CREDS_IV }} + BAN_VIOLATIONS: ${{ secrets.BAN_VIOLATIONS }} + BAN_DURATION: ${{ secrets.BAN_DURATION }} + BAN_INTERVAL: ${{ secrets.BAN_INTERVAL }} + NODE_ENV: CI + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install Data Provider + run: npm run build:data-provider + + - name: Run unit tests + run: cd api && npm run test:ci + + - name: Run linters + uses: wearerequired/lint-action@v2 + with: + eslint: true diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 00000000000..949d39cfe04 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,52 @@ +name: Docker Compose Build on Tag + +# The workflow is triggered when a tag is pushed +on: + push: + tags: + - "*" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + # Check out the repository + - name: Checkout + uses: actions/checkout@v4 + + # Set up Docker + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + + # Log in to GitHub Container Registry + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Run docker-compose build + - name: Build Docker images + run: | + cp .env.example .env + docker-compose build + docker build -f Dockerfile.multi --target api-build -t librechat-api . + + # Get Tag Name + - name: Get Tag Name + id: tag_name + run: echo "TAG_NAME=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + + # Tag it properly before push to github + - name: tag image and push + run: | + docker tag librechat:latest ghcr.io/${{ github.repository_owner }}/librechat:${{ env.TAG_NAME }} + docker push ghcr.io/${{ github.repository_owner }}/librechat:${{ env.TAG_NAME }} + docker tag librechat:latest ghcr.io/${{ github.repository_owner }}/librechat:latest + docker push ghcr.io/${{ github.repository_owner }}/librechat:latest + docker tag librechat-api:latest ghcr.io/${{ github.repository_owner }}/librechat-api:${{ env.TAG_NAME }} + docker push ghcr.io/${{ github.repository_owner }}/librechat-api:${{ env.TAG_NAME }} + docker tag librechat-api:latest ghcr.io/${{ github.repository_owner }}/librechat-api:latest + docker push ghcr.io/${{ github.repository_owner }}/librechat-api:latest diff --git a/.github/workflows/data-provider.yml b/.github/workflows/data-provider.yml new file mode 100644 index 00000000000..21b8a4e991b --- /dev/null +++ b/.github/workflows/data-provider.yml @@ -0,0 +1,34 @@ +name: Node.js Package + +on: + push: + branches: + - main + paths: + - 'packages/data-provider/package.json' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 16 + - run: cd packages/data-provider && npm ci + - run: cd packages/data-provider && npm run build + + publish-npm: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 16 + registry-url: 'https://registry.npmjs.org' + - run: cd packages/data-provider && npm ci + - run: cd packages/data-provider && npm run build + - run: cd packages/data-provider && npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000000..5c143b45318 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,38 @@ +name: Deploy_GHRunner_Linux_ACI + +on: + workflow_dispatch: + +env: + RUNNER_VERSION: 2.293.0 + ACI_RESOURCE_GROUP: 'Demo-ACI-GitHub-Runners-RG' + ACI_NAME: 'gh-runner-linux-01' + DNS_NAME_LABEL: 'gh-lin-01' + GH_OWNER: ${{ github.repository_owner }} + GH_REPOSITORY: 'LibreChat' #Change here to deploy self hosted runner ACI to another repo. + +jobs: + deploy-gh-runner-aci: + runs-on: ubuntu-latest + steps: + # checkout the repo + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: 'Login via Azure CLI' + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: 'Deploy to Azure Container Instances' + uses: 'azure/aci-deploy@v1' + with: + resource-group: ${{ env.ACI_RESOURCE_GROUP }} + image: ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }} + registry-login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }} + registry-username: ${{ secrets.REGISTRY_USERNAME }} + registry-password: ${{ secrets.REGISTRY_PASSWORD }} + name: ${{ env.ACI_NAME }} + dns-name-label: ${{ env.DNS_NAME_LABEL }} + environment-variables: GH_TOKEN=${{ secrets.PAT_TOKEN }} GH_OWNER=${{ env.GH_OWNER }} GH_REPOSITORY=${{ env.GH_REPOSITORY }} + location: 'eastus' diff --git a/.github/workflows/dev-images.yml b/.github/workflows/dev-images.yml new file mode 100644 index 00000000000..e0149e05e9c --- /dev/null +++ b/.github/workflows/dev-images.yml @@ -0,0 +1,45 @@ +name: Docker Dev Images Build + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + # Check out the repository + - name: Checkout + uses: actions/checkout@v4 + + # Set up Docker + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + + # Log in to GitHub Container Registry + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Build Docker images + - name: Build Docker images + run: | + cp .env.example .env + docker build -f Dockerfile.multi --target api-build -t librechat-dev-api . + docker build -f Dockerfile -t librechat-dev . + + # Tag and push the images to GitHub Container Registry + - name: Tag and push images + run: | + docker tag librechat-dev-api:latest ghcr.io/${{ github.repository_owner }}/librechat-dev-api:${{ github.sha }} + docker push ghcr.io/${{ github.repository_owner }}/librechat-dev-api:${{ github.sha }} + docker tag librechat-dev-api:latest ghcr.io/${{ github.repository_owner }}/librechat-dev-api:latest + docker push ghcr.io/${{ github.repository_owner }}/librechat-dev-api:latest + + docker tag librechat-dev:latest ghcr.io/${{ github.repository_owner }}/librechat-dev:${{ github.sha }} + docker push ghcr.io/${{ github.repository_owner }}/librechat-dev:${{ github.sha }} + docker tag librechat-dev:latest ghcr.io/${{ github.repository_owner }}/librechat-dev:latest + docker push ghcr.io/${{ github.repository_owner }}/librechat-dev:latest diff --git a/.github/workflows/frontend-review.yml b/.github/workflows/frontend-review.yml new file mode 100644 index 00000000000..9f479e1b7a0 --- /dev/null +++ b/.github/workflows/frontend-review.yml @@ -0,0 +1,38 @@ +#github action to run unit tests for frontend with jest +name: Frontend Unit Tests +on: + # push: + # branches: + # - main + # - dev + # - release/* + pull_request: + branches: + - main + - dev + - release/* + paths: + - 'client/**' + - 'packages/**' +jobs: + tests_frontend: + name: Run frontend unit tests + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build Client + run: npm run frontend:ci + + - name: Run unit tests + run: npm run test:ci --verbose + working-directory: client \ No newline at end of file diff --git a/.github/workflows/latest-images-main.yml b/.github/workflows/latest-images-main.yml new file mode 100644 index 00000000000..5149cecb0e6 --- /dev/null +++ b/.github/workflows/latest-images-main.yml @@ -0,0 +1,40 @@ +name: Docker Compose Build on Main Branch + +on: + workflow_dispatch: # This line allows manual triggering + +jobs: + build: + runs-on: ubuntu-latest + + steps: + # Check out the repository + - name: Checkout + uses: actions/checkout@v4 + + # Set up Docker + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + + # Log in to GitHub Container Registry + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Run docker-compose build + - name: Build Docker images + run: | + cp .env.example .env + docker-compose build + docker build -f Dockerfile.multi --target api-build -t librechat-api . + + # Tag and push the images with the 'latest' tag + - name: Tag image and push + run: | + docker tag librechat:latest ghcr.io/${{ github.repository_owner }}/librechat:latest + docker push ghcr.io/${{ github.repository_owner }}/librechat:latest + docker tag librechat-api:latest ghcr.io/${{ github.repository_owner }}/librechat-api:latest + docker push ghcr.io/${{ github.repository_owner }}/librechat-api:latest diff --git a/.github/workflows/mkdocs.yaml b/.github/workflows/mkdocs.yaml new file mode 100644 index 00000000000..3b2878fa2a7 --- /dev/null +++ b/.github/workflows/mkdocs.yaml @@ -0,0 +1,27 @@ +name: mkdocs +on: + push: + branches: + - main +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v3 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material + - run: pip install mkdocs-nav-weight + - run: pip install mkdocs-publisher + - run: pip install mkdocs-exclude + - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index 14bca1d7584..f360cbba0ac 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,10 @@ config.local.ts **/storageState.json junit.xml +# docker override file +docker-compose.override.yaml +docker-compose.override.yml + # meilisearch meilisearch meilisearch.exe @@ -78,4 +82,6 @@ data.ms/* auth.json /packages/ux-shared/ -/images \ No newline at end of file +/images + +!client/src/components/Nav/SettingsTabs/Data/ \ No newline at end of file diff --git a/README.md b/README.md index 1b223f2fd7f..cfd0d8ffd45 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@

-# Features +# 📃 Features - 🖥️ UI matching ChatGPT, including Dark mode, Streaming, and 11-2023 updates - 💬 Multimodal Chat: - - Upload and analyze images with GPT-4-Vision 📸 + - Upload and analyze images with GPT-4 and Gemini Vision 📸 - More filetypes and Assistants API integration in Active Development 🚧 - 🌎 Multilingual UI: - English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro, @@ -46,7 +46,7 @@ [For a thorough review of our features, see our docs here](https://docs.librechat.ai/features/plugins/introduction.html) 📚 -## All-In-One AI Conversations with LibreChat +## 🪶 All-In-One AI Conversations with LibreChat LibreChat brings together the future of assistant AIs with the revolutionary technology of OpenAI's ChatGPT. Celebrating the original styling, LibreChat gives you the ability to integrate multiple AI models. It also integrates and enhances original client features such as conversation and message search, prompt templates and plugins. With LibreChat, you no longer need to opt for ChatGPT Plus and can instead use free or pay-per-call APIs. We welcome contributions, cloning, and forking to enhance the capabilities of this advanced chatbot platform. @@ -58,97 +58,20 @@ Click on the thumbnail to open the video☝️ --- -## ⚠️ [Breaking Changes](docs/general_info/breaking_changes.md) ⚠️ - -**Please read this before updating from a previous version** +## 📚 Documentation +For more information on how to use our advanced features, install and configure our software, and access our guidelines and tutorials, please check out our documentation at [docs.librechat.ai](https://docs.librechat.ai) --- -## Changelog +## 📝 Changelog Keep up with the latest updates by visiting the releases page - [Releases](https://github.com/danny-avila/LibreChat/releases) ---- - -

Table of Contents

- -
- Getting Started - - * Installation - * [Docker Compose Install🐳](docs/install/docker_compose_install.md) - * [Linux Install🐧](docs/install/linux_install.md) - * [Mac Install🍎](docs/install/mac_install.md) - * [Windows Install💙](docs/install/windows_install.md) - * Configuration - * [.env Configuration](./docs/install/dotenv.md) - * [APIs and Tokens](docs/install/apis_and_tokens.md) - * [User Auth System](docs/install/user_auth_system.md) - * [Online MongoDB Database](docs/install/mongodb.md) - * [Default Language](docs/install/default_language.md) - * [LiteLLM Proxy: Load Balance LLMs + Spend Tracking](docs/install/litellm.md) -
- -
- General Information - - * [Code of Conduct](.github/CODE_OF_CONDUCT.md) - * [Project Origin](docs/general_info/project_origin.md) - * [Multilingual Information](docs/general_info/multilingual_information.md) - * [Tech Stack](docs/general_info/tech_stack.md) -
- -
- Features - - * **Plugins** - * [Introduction](docs/features/plugins/introduction.md) - * [Google](docs/features/plugins/google_search.md) - * [Stable Diffusion](docs/features/plugins/stable_diffusion.md) - * [Wolfram](docs/features/plugins/wolfram.md) - * [Make Your Own Plugin](docs/features/plugins/make_your_own.md) - * [Using official ChatGPT Plugins](docs/features/plugins/chatgpt_plugins_openapi.md) - - - * [Automated Moderation](docs/features/mod_system.md) - * [Token Usage](docs/features/token_usage.md) - * [Manage Your Database](docs/features/manage_your_database.md) - * [PandoraNext Deployment Guide](docs/features/pandoranext.md) - * [Third-Party Tools](docs/features/third_party.md) - * [Proxy](docs/features/proxy.md) - * [Bing Jailbreak](docs/features/bing_jailbreak.md) - -
- -
- Cloud Deployment - - * [DigitalOcean](docs/deployment/digitalocean.md) - * [Azure](docs/deployment/azure-terraform.md) - * [Linode](docs/deployment/linode.md) - * [Cloudflare](docs/deployment/cloudflare.md) - * [Ngrok](docs/deployment/ngrok.md) - * [HuggingFace](docs/deployment/huggingface.md) - * [Render](docs/deployment/render.md) - * [Meilisearch in Render](docs/deployment/meilisearch_in_render.md) - * [Hetzner](docs/deployment/hetzner_ubuntu.md) - * [Heroku](docs/deployment/heroku.md) -
- -
- Contributions - - * [Contributor Guidelines](.github/CONTRIBUTING.md) - * [Documentation Guidelines](docs/contributions/documentation_guidelines.md) - * [Contribute a Translation](docs/contributions/translation_contribution.md) - * [Code Standards and Conventions](docs/contributions/coding_conventions.md) - * [Testing](docs/contributions/testing.md) - * [Security](.github/SECURITY.md) - * [Project Roadmap](https://github.com/users/danny-avila/projects/2) -
+**⚠️ [Breaking Changes](docs/general_info/breaking_changes.md)** +Please consult the breaking changes before updating. --- -## Star History +## ⭐ Star History Star History Chart @@ -156,15 +79,14 @@ Keep up with the latest updates by visiting the releases page - [Releases](https --- -## Contributors -Contributions and suggestions bug reports and fixes are welcome! -Please read the documentation before you do! +## ✨ Contributions +Contributions, suggestions, bug reports and fixes are welcome! For new features, components, or extensions, please open an issue and discuss before sending a PR. -- Join the [Discord community](https://discord.gg/uDyZ5Tzhct) +--- -This project exists in its current state thanks to all the people who contribute +💖 This project exists in its current state thanks to all the people who contribute --- diff --git a/api/app/bingai.js b/api/app/bingai.js index 56a488541c1..f7ecf4462d4 100644 --- a/api/app/bingai.js +++ b/api/app/bingai.js @@ -1,6 +1,7 @@ require('dotenv').config(); const { KeyvFile } = require('keyv-file'); -const { getUserKey, checkUserKeyExpiry } = require('../server/services/UserService'); +const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); +const { logger } = require('~/config'); const askBing = async ({ text, @@ -100,7 +101,7 @@ const askBing = async ({ } } - console.log('bing options', options); + logger.debug('bing options', options); const res = await bingAIClient.sendMessage(text, options); diff --git a/api/app/clients/AnthropicClient.js b/api/app/clients/AnthropicClient.js index 5174a1d1971..0441a49334e 100644 --- a/api/app/clients/AnthropicClient.js +++ b/api/app/clients/AnthropicClient.js @@ -3,6 +3,7 @@ const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = requ const { getResponseSender, EModelEndpoint } = require('librechat-data-provider'); const { getModelMaxTokens } = require('~/utils'); const BaseClient = require('./BaseClient'); +const { logger } = require('~/config'); const HUMAN_PROMPT = '\n\nHuman:'; const AI_PROMPT = '\n\nAssistant:'; @@ -103,9 +104,8 @@ class AnthropicClient extends BaseClient { messages, parentMessageId, }); - if (this.options.debug) { - console.debug('AnthropicClient: orderedMessages', orderedMessages, parentMessageId); - } + + logger.debug('[AnthropicClient] orderedMessages', { orderedMessages, parentMessageId }); const formattedMessages = orderedMessages.map((message) => ({ author: message.isCreatedByUser ? this.userLabel : this.assistantLabel, @@ -247,7 +247,7 @@ class AnthropicClient extends BaseClient { } getCompletion() { - console.log('AnthropicClient doesn\'t use getCompletion (all handled in sendCompletion)'); + logger.debug('AnthropicClient doesn\'t use getCompletion (all handled in sendCompletion)'); } async sendCompletion(payload, { onProgress, abortController }) { @@ -262,12 +262,7 @@ class AnthropicClient extends BaseClient { modelOptions.stream = true; } - const { debug } = this.options; - if (debug) { - console.debug(); - console.debug(modelOptions); - console.debug(); - } + logger.debug('modelOptions', { modelOptions }); const client = this.getClient(); const metadata = { @@ -295,32 +290,23 @@ class AnthropicClient extends BaseClient { top_p, top_k, }; - if (this.options.debug) { - console.log('AnthropicClient: requestOptions'); - console.dir(requestOptions, { depth: null }); - } + logger.debug('[AnthropicClient]', { ...requestOptions }); const response = await client.completions.create(requestOptions); signal.addEventListener('abort', () => { - if (this.options.debug) { - console.log('AnthropicClient: message aborted!'); - } + logger.debug('[AnthropicClient] message aborted!'); response.controller.abort(); }); for await (const completion of response) { - if (this.options.debug) { - // Uncomment to debug message stream - // console.debug(completion); - } + // Uncomment to debug message stream + // logger.debug(completion); text += completion.completion; onProgress(completion.completion); } signal.removeEventListener('abort', () => { - if (this.options.debug) { - console.log('AnthropicClient: message aborted!'); - } + logger.debug('[AnthropicClient] message aborted!'); response.controller.abort(); }); @@ -336,9 +322,7 @@ class AnthropicClient extends BaseClient { } getBuildMessagesOptions() { - if (this.options.debug) { - console.log('AnthropicClient doesn\'t use getBuildMessagesOptions'); - } + logger.debug('AnthropicClient doesn\'t use getBuildMessagesOptions'); } static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) { diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index 626a98888d3..ea63a3ce904 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -1,8 +1,10 @@ const crypto = require('crypto'); -const TextStream = require('./TextStream'); +const { supportsBalanceCheck } = require('librechat-data-provider'); const { getConvo, getMessages, saveMessage, updateMessage, saveConvo } = require('~/models'); const { addSpaceIfNeeded, isEnabled } = require('~/server/utils'); const checkBalance = require('~/models/checkBalance'); +const TextStream = require('./TextStream'); +const { logger } = require('~/config'); class BaseClient { constructor(apiKey, options = {}) { @@ -41,15 +43,14 @@ class BaseClient { } async getTokenCountForResponse(response) { - if (this.options.debug) { - console.debug('`recordTokenUsage` not implemented.', response); - } + logger.debug('`[BaseClient] recordTokenUsage` not implemented.', response); } async recordTokenUsage({ promptTokens, completionTokens }) { - if (this.options.debug) { - console.debug('`recordTokenUsage` not implemented.', { promptTokens, completionTokens }); - } + logger.debug('`[BaseClient] recordTokenUsage` not implemented.', { + promptTokens, + completionTokens, + }); } getBuildMessagesOptions() { @@ -194,14 +195,14 @@ class BaseClient { const update = {}; if (messageId === tokenCountMap.summaryMessage?.messageId) { - this.options.debug && console.debug(`Adding summary props to ${messageId}.`); + logger.debug(`[BaseClient] Adding summary props to ${messageId}.`); update.summary = tokenCountMap.summaryMessage.content; update.summaryTokenCount = tokenCountMap.summaryMessage.tokenCount; } if (message.tokenCount && !update.summaryTokenCount) { - this.options.debug && console.debug(`Skipping ${messageId}: already had a token count.`); + logger.debug(`[BaseClient] Skipping ${messageId}: already had a token count.`); continue; } @@ -278,19 +279,17 @@ class BaseClient { if (instructions) { ({ tokenCount, ..._instructions } = instructions); } - this.options.debug && _instructions && console.debug('instructions tokenCount', tokenCount); + _instructions && logger.debug('[BaseClient] instructions tokenCount: ' + tokenCount); let payload = this.addInstructions(formattedMessages, _instructions); let orderedWithInstructions = this.addInstructions(orderedMessages, instructions); let { context, remainingContextTokens, messagesToRefine, summaryIndex } = await this.getMessagesWithinTokenLimit(orderedWithInstructions); - this.options.debug && - console.debug( - 'remainingContextTokens, this.maxContextTokens (1/2)', - remainingContextTokens, - this.maxContextTokens, - ); + logger.debug('[BaseClient] Context Count (1/2)', { + remainingContextTokens, + maxContextTokens: this.maxContextTokens, + }); let summaryMessage; let summaryTokenCount; @@ -308,10 +307,9 @@ class BaseClient { if (diff > 0) { payload = payload.slice(diff); - this.options.debug && - console.debug( - `Difference between original payload (${length}) and context (${context.length}): ${diff}`, - ); + logger.debug( + `[BaseClient] Difference between original payload (${length}) and context (${context.length}): ${diff}`, + ); } const latestMessage = orderedWithInstructions[orderedWithInstructions.length - 1]; @@ -338,12 +336,10 @@ class BaseClient { // Make sure to only continue summarization logic if the summary message was generated shouldSummarize = summaryMessage && shouldSummarize; - this.options.debug && - console.debug( - 'remainingContextTokens, this.maxContextTokens (2/2)', - remainingContextTokens, - this.maxContextTokens, - ); + logger.debug('[BaseClient] Context Count (2/2)', { + remainingContextTokens, + maxContextTokens: this.maxContextTokens, + }); let tokenCountMap = orderedWithInstructions.reduce((map, message, index) => { const { messageId } = message; @@ -361,19 +357,13 @@ class BaseClient { const promptTokens = this.maxContextTokens - remainingContextTokens; - if (this.options.debug) { - console.debug('<-------------------------PAYLOAD/TOKEN COUNT MAP------------------------->'); - console.debug('Payload:', payload); - console.debug('Token Count Map:', tokenCountMap); - console.debug( - 'Prompt Tokens', - promptTokens, - 'remainingContextTokens', - remainingContextTokens, - 'this.maxContextTokens', - this.maxContextTokens, - ); - } + logger.debug('[BaseClient] tokenCountMap:', tokenCountMap); + logger.debug('[BaseClient]', { + promptTokens, + remainingContextTokens, + payloadSize: payload.length, + maxContextTokens: this.maxContextTokens, + }); return { payload, tokenCountMap, promptTokens, messages: orderedWithInstructions }; } @@ -421,11 +411,10 @@ class BaseClient { ); if (tokenCountMap) { - console.dir(tokenCountMap, { depth: null }); + logger.debug('[BaseClient] tokenCountMap', tokenCountMap); if (tokenCountMap[userMessage.messageId]) { userMessage.tokenCount = tokenCountMap[userMessage.messageId]; - console.log('userMessage.tokenCount', userMessage.tokenCount); - console.log('userMessage', userMessage); + logger.debug('[BaseClient] userMessage', userMessage); } this.handleTokenCountMap(tokenCountMap); @@ -435,7 +424,7 @@ class BaseClient { await this.saveMessageToDatabase(userMessage, saveOptions, user); } - if (isEnabled(process.env.CHECK_BALANCE)) { + if (isEnabled(process.env.CHECK_BALANCE) && supportsBalanceCheck[this.options.endpoint]) { await checkBalance({ req: this.options.req, res: this.options.res, @@ -443,7 +432,6 @@ class BaseClient { user: this.user, tokenType: 'prompt', amount: promptTokens, - debug: this.options.debug, model: this.modelOptions.model, endpoint: this.options.endpoint, }, @@ -483,9 +471,7 @@ class BaseClient { } async loadHistory(conversationId, parentMessageId = null) { - if (this.options.debug) { - console.debug('Loading history for conversation', conversationId, parentMessageId); - } + logger.debug('[BaseClient] Loading history:', { conversationId, parentMessageId }); const messages = (await getMessages({ conversationId })) ?? []; @@ -516,16 +502,21 @@ class BaseClient { } } - if (this.options.debug && this.previous_summary) { + if (this.previous_summary) { const { messageId, summary, tokenCount, summaryTokenCount } = this.previous_summary; - console.debug('Previous summary:', { messageId, summary, tokenCount, summaryTokenCount }); + logger.debug('[BaseClient] Previous summary:', { + messageId, + summary, + tokenCount, + summaryTokenCount, + }); } return orderedMessages; } async saveMessageToDatabase(message, endpointOptions, user = null) { - await saveMessage({ ...message, user, unfinished: false, cancelled: false }); + await saveMessage({ ...message, user, unfinished: false }); await saveConvo(user, { conversationId: message.conversationId, endpoint: this.options.endpoint, diff --git a/api/app/clients/ChatGPTClient.js b/api/app/clients/ChatGPTClient.js index 58483bb7f7e..c1ae54fdf08 100644 --- a/api/app/clients/ChatGPTClient.js +++ b/api/app/clients/ChatGPTClient.js @@ -166,6 +166,12 @@ class ChatGPTClient extends BaseClient { console.debug(modelOptions); console.debug(); } + + if (this.azure || this.options.azure) { + // Azure does not accept `model` in the body, so we need to remove it. + delete modelOptions.model; + } + const opts = { method: 'POST', headers: { diff --git a/api/app/clients/GoogleClient.js b/api/app/clients/GoogleClient.js index 4d5ee00211a..950cc8d1116 100644 --- a/api/app/clients/GoogleClient.js +++ b/api/app/clients/GoogleClient.js @@ -1,13 +1,21 @@ const { google } = require('googleapis'); const { Agent, ProxyAgent } = require('undici'); const { GoogleVertexAI } = require('langchain/llms/googlevertexai'); +const { ChatGoogleGenerativeAI } = require('@langchain/google-genai'); const { ChatGoogleVertexAI } = require('langchain/chat_models/googlevertexai'); const { AIMessage, HumanMessage, SystemMessage } = require('langchain/schema'); +const { encodeAndFormat, validateVisionModel } = require('~/server/services/Files/images'); const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken'); -const { getResponseSender, EModelEndpoint, endpointSettings } = require('librechat-data-provider'); +const { + getResponseSender, + EModelEndpoint, + endpointSettings, + AuthKeys, +} = require('librechat-data-provider'); const { getModelMaxTokens } = require('~/utils'); const { formatMessage } = require('./prompts'); const BaseClient = require('./BaseClient'); +const { logger } = require('~/config'); const loc = 'us-central1'; const publisher = 'google'; @@ -20,11 +28,24 @@ const settings = endpointSettings[EModelEndpoint.google]; class GoogleClient extends BaseClient { constructor(credentials, options = {}) { super('apiKey', options); - this.credentials = credentials; - this.client_email = credentials.client_email; - this.project_id = credentials.project_id; - this.private_key = credentials.private_key; + let creds = {}; + + if (typeof credentials === 'string') { + creds = JSON.parse(credentials); + } else if (credentials) { + creds = credentials; + } + + const serviceKey = creds[AuthKeys.GOOGLE_SERVICE_KEY] ?? {}; + this.serviceKey = + serviceKey && typeof serviceKey === 'string' ? JSON.parse(serviceKey) : serviceKey ?? {}; + this.client_email = this.serviceKey.client_email; + this.private_key = this.serviceKey.private_key; + this.project_id = this.serviceKey.project_id; this.access_token = null; + + this.apiKey = creds[AuthKeys.GOOGLE_API_KEY]; + if (options.skipSetOptions) { return; } @@ -42,8 +63,7 @@ class GoogleClient extends BaseClient { jwtClient.authorize((err) => { if (err) { - console.error('Error: jwtClient failed to authorize'); - console.error(err.message); + logger.error('jwtClient failed to authorize', err); throw err; } }); @@ -58,11 +78,9 @@ class GoogleClient extends BaseClient { return new Promise((resolve, reject) => { jwtClient.authorize((err, tokens) => { if (err) { - console.error('Error: jwtClient failed to authorize'); - console.error(err.message); + logger.error('jwtClient failed to authorize', err); reject(err); } else { - console.log('Access Token:', tokens.access_token); resolve(tokens.access_token); } }); @@ -87,7 +105,7 @@ class GoogleClient extends BaseClient { this.options = options; } - this.options.examples = this.options.examples + this.options.examples = (this.options.examples ?? []) .filter((ex) => ex) .filter((obj) => obj.input.content !== '' && obj.output.content !== ''); @@ -105,15 +123,33 @@ class GoogleClient extends BaseClient { // stop: modelOptions.stop // no stop method for now }; - this.isChatModel = this.modelOptions.model.includes('chat'); + if (this.options.attachments) { + this.modelOptions.model = 'gemini-pro-vision'; + } + + // TODO: as of 12/14/23, only gemini models are "Generative AI" models provided by Google + this.isGenerativeModel = this.modelOptions.model.includes('gemini'); + this.isVisionModel = validateVisionModel(this.modelOptions.model); + const { isGenerativeModel } = this; + if (this.isVisionModel && !this.options.attachments) { + this.modelOptions.model = 'gemini-pro'; + this.isVisionModel = false; + } + this.isChatModel = !isGenerativeModel && this.modelOptions.model.includes('chat'); const { isChatModel } = this; - this.isTextModel = !isChatModel && /code|text/.test(this.modelOptions.model); + this.isTextModel = + !isGenerativeModel && !isChatModel && /code|text/.test(this.modelOptions.model); const { isTextModel } = this; this.maxContextTokens = getModelMaxTokens(this.modelOptions.model, EModelEndpoint.google); // The max prompt tokens is determined by the max context tokens minus the max response tokens. // Earlier messages will be dropped until the prompt is within the limit. this.maxResponseTokens = this.modelOptions.maxOutputTokens || settings.maxOutputTokens.default; + + if (this.maxContextTokens > 32000) { + this.maxContextTokens = this.maxContextTokens - this.maxResponseTokens; + } + this.maxPromptTokens = this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens; @@ -136,7 +172,7 @@ class GoogleClient extends BaseClient { this.userLabel = this.options.userLabel || 'User'; this.modelLabel = this.options.modelLabel || 'Assistant'; - if (isChatModel) { + if (isChatModel || isGenerativeModel) { // Use these faux tokens to help the AI understand the context since we are building the chat log ourselves. // Trying to use "<|im_start|>" causes the AI to still generate "<" or "<|" at the end sometimes for some reason, // without tripping the stop sequences, so I'm using "||>" instead. @@ -190,18 +226,62 @@ class GoogleClient extends BaseClient { })).bind(this); } - buildMessages(messages = [], parentMessageId) { + async buildVisionMessages(messages = [], parentMessageId) { + const { prompt } = await this.buildMessagesPrompt(messages, parentMessageId); + const attachments = await this.options.attachments; + const { files, image_urls } = await encodeAndFormat( + this.options.req, + attachments.filter((file) => file.type.includes('image')), + EModelEndpoint.google, + ); + + const latestMessage = { ...messages[messages.length - 1] }; + + latestMessage.image_urls = image_urls; + this.options.attachments = files; + + latestMessage.text = prompt; + + const payload = { + instances: [ + { + messages: [new HumanMessage(formatMessage({ message: latestMessage }))], + }, + ], + parameters: this.modelOptions, + }; + return { prompt: payload }; + } + + async buildMessages(messages = [], parentMessageId) { + if (!this.isGenerativeModel && !this.project_id) { + throw new Error( + '[GoogleClient] a Service Account JSON Key is required for PaLM 2 and Codey models (Vertex AI)', + ); + } else if (this.isGenerativeModel && (!this.apiKey || this.apiKey === 'user_provided')) { + throw new Error( + '[GoogleClient] an API Key is required for Gemini models (Generative Language API)', + ); + } + + if (this.options.attachments) { + return this.buildVisionMessages(messages, parentMessageId); + } + if (this.isTextModel) { return this.buildMessagesPrompt(messages, parentMessageId); } - const formattedMessages = messages.map(this.formatMessages()); + let payload = { instances: [ { - messages: formattedMessages, + messages: messages + .map(this.formatMessages()) + .map((msg) => ({ ...msg, role: msg.author === 'User' ? 'user' : 'assistant' })) + .map((message) => formatMessage({ message, langChain: true })), }, ], - parameters: this.options.modelOptions, + parameters: this.modelOptions, }; if (this.options.promptPrefix) { @@ -212,10 +292,7 @@ class GoogleClient extends BaseClient { payload.instances[0].examples = this.options.examples; } - if (this.options.debug) { - console.debug('GoogleClient buildMessages'); - console.dir(payload, { depth: null }); - } + logger.debug('[GoogleClient] buildMessages', payload); return { prompt: payload }; } @@ -225,9 +302,11 @@ class GoogleClient extends BaseClient { messages, parentMessageId, }); - if (this.options.debug) { - console.debug('GoogleClient: orderedMessages', orderedMessages, parentMessageId); - } + + logger.debug('[GoogleClient]', { + orderedMessages, + parentMessageId, + }); const formattedMessages = orderedMessages.map((message) => ({ author: message.isCreatedByUser ? this.userLabel : this.modelLabel, @@ -356,7 +435,7 @@ class GoogleClient extends BaseClient { context.shift(); } - let prompt = `${promptBody}${promptSuffix}`; + let prompt = `${promptBody}${promptSuffix}`.trim(); // Add 2 tokens for metadata after all messages have been counted. currentTokenCount += 2; @@ -377,10 +456,7 @@ class GoogleClient extends BaseClient { const { debug } = this.options; const url = this.completionsUrl; if (debug) { - console.debug(); - console.debug(url); - console.debug(this.modelOptions); - console.debug(); + logger.debug('GoogleClient _getCompletion', { url, payload }); } const opts = { method: 'POST', @@ -397,10 +473,20 @@ class GoogleClient extends BaseClient { const client = await this.getClient(); const res = await client.request({ url, method: 'POST', data: payload }); - console.dir(res.data, { depth: null }); + logger.debug('GoogleClient _getCompletion', { res }); return res.data; } + createLLM(clientOptions) { + if (this.isGenerativeModel) { + return new ChatGoogleGenerativeAI({ ...clientOptions, apiKey: this.apiKey }); + } + + return this.isTextModel + ? new GoogleVertexAI(clientOptions) + : new ChatGoogleVertexAI(clientOptions); + } + async getCompletion(_payload, options = {}) { const { onProgress, abortController } = options; const { parameters, instances } = _payload; @@ -408,20 +494,26 @@ class GoogleClient extends BaseClient { let examples; - let clientOptions = { - authOptions: { + let clientOptions = { ...parameters, maxRetries: 2 }; + + if (!this.isGenerativeModel) { + clientOptions['authOptions'] = { credentials: { - ...this.credentials, + ...this.serviceKey, }, projectId: this.project_id, - }, - ...parameters, - }; + }; + } if (!parameters) { clientOptions = { ...clientOptions, ...this.modelOptions }; } + if (this.isGenerativeModel) { + clientOptions.modelName = clientOptions.model; + delete clientOptions.model; + } + if (_examples && _examples.length) { examples = _examples .map((ex) => { @@ -439,18 +531,12 @@ class GoogleClient extends BaseClient { clientOptions.examples = examples; } - const model = this.isTextModel - ? new GoogleVertexAI(clientOptions) - : new ChatGoogleVertexAI(clientOptions); + const model = this.createLLM(clientOptions); let reply = ''; - const messages = this.isTextModel - ? _payload.trim() - : _messages - .map((msg) => ({ ...msg, role: msg.author === 'User' ? 'user' : 'assistant' })) - .map((message) => formatMessage({ message, langChain: true })); + const messages = this.isTextModel ? _payload.trim() : _messages; - if (context && messages?.length > 0) { + if (!this.isVisionModel && context && messages?.length > 0) { messages.unshift(new SystemMessage(context)); } @@ -460,7 +546,9 @@ class GoogleClient extends BaseClient { }); for await (const chunk of stream) { - await this.generateTextStream(chunk?.content ?? chunk, onProgress, { delay: 7 }); + await this.generateTextStream(chunk?.content ?? chunk, onProgress, { + delay: this.isGenerativeModel ? 12 : 8, + }); reply += chunk?.content ?? chunk; } @@ -476,22 +564,12 @@ class GoogleClient extends BaseClient { } getBuildMessagesOptions() { - // console.log('GoogleClient doesn\'t use getBuildMessagesOptions'); + // logger.debug('GoogleClient doesn\'t use getBuildMessagesOptions'); } async sendCompletion(payload, opts = {}) { let reply = ''; - try { - reply = await this.getCompletion(payload, opts); - if (this.options.debug) { - console.debug('result'); - console.debug(reply); - } - } catch (err) { - console.error('Error: failed to send completion to Google'); - console.error(err); - console.error(err.message); - } + reply = await this.getCompletion(payload, opts); return reply.trim(); } diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 928c2416f5c..605646a4b6e 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -1,7 +1,7 @@ const OpenAI = require('openai'); const { HttpsProxyAgent } = require('https-proxy-agent'); -const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken'); const { getResponseSender, EModelEndpoint } = require('librechat-data-provider'); +const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken'); const { encodeAndFormat, validateVisionModel } = require('~/server/services/Files/images'); const { getModelMaxTokens, genAzureChatCompletion, extractBaseURL } = require('~/utils'); const { truncateText, formatMessage, CUT_OFF_PROMPT } = require('./prompts'); @@ -14,6 +14,7 @@ const { summaryBuffer } = require('./memory'); const { runTitleChain } = require('./chains'); const { tokenSplit } = require('./document'); const BaseClient = require('./BaseClient'); +const { logger } = require('~/config'); // Cache to store Tiktoken instances const tokenizersCache = {}; @@ -75,11 +76,14 @@ class OpenAIClient extends BaseClient { }; } - if (this.options.attachments && !validateVisionModel(this.modelOptions.model)) { + this.isVisionModel = validateVisionModel(this.modelOptions.model); + + if (this.options.attachments && !this.isVisionModel) { this.modelOptions.model = 'gpt-4-vision-preview'; + this.isVisionModel = true; } - if (validateVisionModel(this.modelOptions.model)) { + if (this.isVisionModel) { delete this.modelOptions.stop; } @@ -103,7 +107,7 @@ class OpenAIClient extends BaseClient { const { model } = this.modelOptions; - this.isChatCompletion = this.useOpenRouter || !!reverseProxy || model.includes('gpt-'); + this.isChatCompletion = this.useOpenRouter || !!reverseProxy || model.includes('gpt'); this.isChatGptModel = this.isChatCompletion; if ( model.includes('text-davinci') || @@ -123,7 +127,7 @@ class OpenAIClient extends BaseClient { } if (this.options.debug) { - console.debug('maxContextTokens', this.maxContextTokens); + logger.debug('[OpenAIClient] maxContextTokens', this.maxContextTokens); } this.maxResponseTokens = this.modelOptions.max_tokens || 1024; @@ -151,7 +155,7 @@ class OpenAIClient extends BaseClient { this.setupTokens(); - if (!this.modelOptions.stop && !validateVisionModel(this.modelOptions.model)) { + if (!this.modelOptions.stop && !this.isVisionModel) { const stopTokens = [this.startToken]; if (this.endToken && this.endToken !== this.startToken) { stopTokens.push(this.endToken); @@ -175,7 +179,7 @@ class OpenAIClient extends BaseClient { } if (this.azureEndpoint && this.options.debug) { - console.debug('Using Azure endpoint'); + logger.debug('Using Azure endpoint'); } if (this.useOpenRouter) { @@ -254,8 +258,7 @@ class OpenAIClient extends BaseClient { // Reset count tokenizerCallsCount = 1; } catch (error) { - console.log('Free and reset encoders error'); - console.error(error); + logger.error('[OpenAIClient] Free and reset encoders error', error); } } @@ -263,7 +266,7 @@ class OpenAIClient extends BaseClient { resetTokenizersIfNecessary() { if (tokenizerCallsCount >= 25) { if (this.options.debug) { - console.debug('freeAndResetAllEncoders: reached 25 encodings, resetting...'); + logger.debug('[OpenAIClient] freeAndResetAllEncoders: reached 25 encodings, resetting...'); } this.constructor.freeAndResetAllEncoders(); } @@ -394,7 +397,7 @@ class OpenAIClient extends BaseClient { let streamResult = null; this.modelOptions.user = this.user; const invalidBaseUrl = this.completionsUrl && extractBaseURL(this.completionsUrl) === null; - const useOldMethod = !!(this.azure || invalidBaseUrl || !this.isChatCompletion); + const useOldMethod = !!(invalidBaseUrl || !this.isChatCompletion); if (typeof opts.onProgress === 'function' && useOldMethod) { await this.getCompletion( payload, @@ -403,11 +406,6 @@ class OpenAIClient extends BaseClient { return; } - if (this.options.debug) { - // console.debug('progressMessage'); - // console.dir(progressMessage, { depth: null }); - } - if (progressMessage.choices) { streamResult = progressMessage; } @@ -427,9 +425,7 @@ class OpenAIClient extends BaseClient { if (!token) { return; } - if (this.options.debug) { - // console.debug(token); - } + if (token === this.endToken) { return; } @@ -451,9 +447,9 @@ class OpenAIClient extends BaseClient { null, opts.abortController || new AbortController(), ); - if (this.options.debug) { - console.debug(JSON.stringify(result)); - } + + logger.debug('[OpenAIClient] sendCompletion: result', result); + if (this.isChatCompletion) { reply = result.choices[0].message.content; } else { @@ -557,11 +553,13 @@ class OpenAIClient extends BaseClient { title = await runTitleChain({ llm, text, convo, signal: this.abortController.signal }); } catch (e) { if (e?.message?.toLowerCase()?.includes('abort')) { - this.options.debug && console.debug('Aborted title generation'); + logger.debug('[OpenAIClient] Aborted title generation'); return; } - console.log('There was an issue generating title with LangChain, trying the old method...'); - this.options.debug && console.error(e.message, e); + logger.error( + '[OpenAIClient] There was an issue generating title with LangChain, trying the old method...', + e, + ); modelOptions.model = OPENAI_TITLE_MODEL ?? 'gpt-3.5-turbo'; if (this.azure) { modelOptions.model = process.env.AZURE_OPENAI_DEFAULT_MODEL ?? modelOptions.model; @@ -582,17 +580,16 @@ ${convo} try { title = (await this.sendPayload(instructionsPayload, { modelOptions })).replaceAll('"', ''); } catch (e) { - console.error(e); - console.log('There was another issue generating the title, see error above.'); + logger.error('[OpenAIClient] There was another issue generating the title', e); } } - console.log('CONVERSATION TITLE', title); + logger.debug('[OpenAIClient] Convo Title: ' + title); return title; } async summarizeMessages({ messagesToRefine, remainingContextTokens }) { - this.options.debug && console.debug('Summarizing messages...'); + logger.debug('[OpenAIClient] Summarizing messages...'); let context = messagesToRefine; let prompt; @@ -615,8 +612,9 @@ ${convo} } if (context.length === 0) { - this.options.debug && - console.debug('Summary context is empty, using latest message within token limit'); + logger.debug( + '[OpenAIClient] Summary context is empty, using latest message within token limit', + ); promptBuffer = 32; const { text, ...latestMessage } = messagesToRefine[messagesToRefine.length - 1]; @@ -643,7 +641,7 @@ ${convo} // by recreating the summary prompt (single message) to avoid LangChain handling const initialPromptTokens = this.maxContextTokens - remainingContextTokens; - this.options.debug && console.debug(`initialPromptTokens: ${initialPromptTokens}`); + logger.debug('[OpenAIClient] initialPromptTokens', initialPromptTokens); const llm = this.initializeLLM({ model: OPENAI_SUMMARY_MODEL, @@ -669,9 +667,9 @@ ${convo} const summaryTokenCount = this.getTokenCountForMessage(summaryMessage); if (this.options.debug) { - console.debug('summaryMessage:', summaryMessage); - console.debug( - `remainingContextTokens: ${remainingContextTokens}, after refining: ${ + logger.debug('[OpenAIClient] summaryTokenCount', summaryTokenCount); + logger.debug( + `[OpenAIClient] Summarization complete: remainingContextTokens: ${remainingContextTokens}, after refining: ${ remainingContextTokens - summaryTokenCount }`, ); @@ -680,7 +678,7 @@ ${convo} return { summaryMessage, summaryTokenCount }; } catch (e) { if (e?.message?.toLowerCase()?.includes('abort')) { - this.options.debug && console.debug('Aborted summarization'); + logger.debug('[OpenAIClient] Aborted summarization'); const { run, runId } = this.runManager.getRunByConversationId(this.conversationId); if (run && run.error) { const { error } = run; @@ -688,17 +686,13 @@ ${convo} throw new Error(error); } } - console.error('Error summarizing messages'); - this.options.debug && console.error(e); + logger.error('[OpenAIClient] Error summarizing messages', e); return {}; } } async recordTokenUsage({ promptTokens, completionTokens }) { - if (this.options.debug) { - console.debug('promptTokens', promptTokens); - console.debug('completionTokens', completionTokens); - } + logger.debug('[OpenAIClient] recordTokenUsage:', { promptTokens, completionTokens }); await spendTokens( { user: this.user, @@ -736,14 +730,19 @@ ${convo} modelOptions.prompt = payload; } - const { debug } = this.options; - const url = extractBaseURL(this.completionsUrl); - if (debug) { - console.debug('baseURL', url); - console.debug('modelOptions', modelOptions); - } + const baseURL = extractBaseURL(this.completionsUrl); + // let { messages: _msgsToLog, ...modelOptionsToLog } = modelOptions; + // if (modelOptionsToLog.messages) { + // _msgsToLog = modelOptionsToLog.messages.map((msg) => { + // let { content, ...rest } = msg; + + // if (content) + // return { ...rest, content: truncateText(content) }; + // }); + // } + logger.debug('[OpenAIClient] chatCompletion', { baseURL, modelOptions }); const opts = { - baseURL: url, + baseURL, }; if (this.useOpenRouter) { @@ -761,10 +760,19 @@ ${convo} opts.httpAgent = new HttpsProxyAgent(this.options.proxy); } - if (validateVisionModel(modelOptions.model)) { + if (this.isVisionModel) { modelOptions.max_tokens = 4000; } + if (this.azure || this.options.azure) { + // Azure does not accept `model` in the body, so we need to remove it. + delete modelOptions.model; + + opts.baseURL = this.azureEndpoint.split('/chat')[0]; + opts.defaultQuery = { 'api-version': this.azure.azureOpenAIApiVersion }; + opts.defaultHeaders = { ...opts.defaultHeaders, 'api-key': this.apiKey }; + } + let chatCompletion; const openai = new OpenAI({ apiKey: this.apiKey, @@ -820,7 +828,7 @@ ${convo} if (!chatCompletion && UnexpectedRoleError) { throw new Error( - 'OpenAIError: Invalid final message: OpenAI expects final message to include role=assistant', + 'OpenAI error: Invalid final message: OpenAI expects final message to include role=assistant', ); } else if (!chatCompletion && error) { throw new Error(error); @@ -839,31 +847,27 @@ ${convo} err?.message?.includes('abort') || (err instanceof OpenAI.APIError && err?.message?.includes('abort')) ) { - return ''; + return intermediateReply; } if ( err?.message?.includes( - 'OpenAIError: Invalid final message: OpenAI expects final message to include role=assistant', + 'OpenAI error: Invalid final message: OpenAI expects final message to include role=assistant', ) || err?.message?.includes('The server had an error processing your request') || err?.message?.includes('missing finish_reason') || + err?.message?.includes('missing role') || (err instanceof OpenAI.OpenAIError && err?.message?.includes('missing finish_reason')) ) { - console.error(err); - await abortController.abortCompletion(); + logger.error('[OpenAIClient] Known OpenAI error:', err); return intermediateReply; } else if (err instanceof OpenAI.APIError) { - console.log(err.name); - console.log(err.status); - console.log(err.headers); if (intermediateReply) { return intermediateReply; } else { throw err; } } else { - console.warn('[OpenAIClient.chatCompletion] Unhandled error type'); - console.error(err); + logger.error('[OpenAIClient.chatCompletion] Unhandled error type', err); throw err; } } diff --git a/api/app/clients/PluginsClient.js b/api/app/clients/PluginsClient.js index 721afa7c958..f26df8a2d10 100644 --- a/api/app/clients/PluginsClient.js +++ b/api/app/clients/PluginsClient.js @@ -10,6 +10,7 @@ const { SelfReflectionTool } = require('./tools'); const { isEnabled } = require('~/server/utils'); const { extractBaseURL } = require('~/utils'); const { loadTools } = require('./tools/util'); +const { logger } = require('~/config'); class PluginsClient extends OpenAIClient { constructor(apiKey, options = {}) { @@ -85,17 +86,15 @@ class PluginsClient extends OpenAIClient { initialMessageCount: this.currentMessages.length + 1, }); - if (this.options.debug) { - console.debug( - `<-----Agent Model: ${model.modelName} | Temp: ${model.temperature} | Functions: ${this.functionsAgent}----->`, - ); - } + logger.debug( + `[PluginsClient] Agent Model: ${model.modelName} | Temp: ${model.temperature} | Functions: ${this.functionsAgent}`, + ); // Map Messages to Langchain format const pastMessages = formatLangChainMessages(this.currentMessages.slice(0, -1), { userName: this.options?.name, }); - this.options.debug && console.debug('pastMessages: ', pastMessages); + logger.debug('[PluginsClient] pastMessages: ' + pastMessages.length); // TODO: use readOnly memory, TokenBufferMemory? (both unavailable in LangChainJS) const memory = new BufferMemory({ @@ -124,19 +123,16 @@ class PluginsClient extends OpenAIClient { return; } - if (this.options.debug) { - console.debug('Requested Tools'); - console.debug(this.options.tools); - console.debug('Loaded Tools'); - console.debug(this.tools.map((tool) => tool.name)); - } + logger.debug('[PluginsClient] Requested Tools', this.options.tools); + logger.debug( + '[PluginsClient] Loaded Tools', + this.tools.map((tool) => tool.name), + ); const handleAction = (action, runId, callback = null) => { this.saveLatestAction(action); - if (this.options.debug) { - console.debug('Latest Agent Action ', this.actions[this.actions.length - 1]); - } + logger.debug('[PluginsClient] Latest Agent Action ', this.actions[this.actions.length - 1]); if (typeof callback === 'function') { callback(action, runId); @@ -165,9 +161,7 @@ class PluginsClient extends OpenAIClient { }), }); - if (this.options.debug) { - console.debug('Loaded agent.'); - } + logger.debug('[PluginsClient] Loaded agent.'); } async executorCall(message, { signal, stream, onToolStart, onToolEnd }) { @@ -183,12 +177,10 @@ class PluginsClient extends OpenAIClient { }); const input = attempts > 1 ? errorInput : message; - if (this.options.debug) { - console.debug(`Attempt ${attempts} of ${maxAttempts}`); - } + logger.debug(`[PluginsClient] Attempt ${attempts} of ${maxAttempts}`); - if (this.options.debug && errorMessage.length > 0) { - console.debug('Caught error, input:', input); + if (errorMessage.length > 0) { + logger.debug('[PluginsClient] Caught error, input: ' + JSON.stringify(input)); } try { @@ -211,10 +203,10 @@ class PluginsClient extends OpenAIClient { ]); break; // Exit the loop if the function call is successful } catch (err) { - console.error(err); + logger.error('[PluginsClient] executorCall error:', err); if (attempts === maxAttempts) { const { run } = this.runManager.getRunByConversationId(this.conversationId); - const defaultOutput = `Encountered an error while attempting to respond. Error: ${err.message}`; + const defaultOutput = `Encountered an error while attempting to respond: ${err.message}`; this.result.output = run && run.error ? run.error : defaultOutput; this.result.errorMessage = run && run.error ? run.error : err.message; this.result.intermediateSteps = this.actions; @@ -226,8 +218,11 @@ class PluginsClient extends OpenAIClient { async handleResponseMessage(responseMessage, saveOptions, user) { const { output, errorMessage, ...result } = this.result; - this.options.debug && - console.debug('[handleResponseMessage] Output:', { output, errorMessage, ...result }); + logger.debug('[PluginsClient][handleResponseMessage] Output:', { + output, + errorMessage, + ...result, + }); const { error } = responseMessage; if (!error) { responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage); @@ -251,7 +246,7 @@ class PluginsClient extends OpenAIClient { this.setOptions(opts); return super.sendMessage(message, opts); } - this.options.debug && console.log('Plugins sendMessage', message, opts); + logger.debug('[PluginsClient] sendMessage', { message, opts }); const { user, isEdited, @@ -281,10 +276,10 @@ class PluginsClient extends OpenAIClient { ); if (tokenCountMap) { - console.dir(tokenCountMap, { depth: null }); + logger.debug('[PluginsClient] tokenCountMap', { tokenCountMap }); if (tokenCountMap[userMessage.messageId]) { userMessage.tokenCount = tokenCountMap[userMessage.messageId]; - console.log('userMessage.tokenCount', userMessage.tokenCount); + logger.debug('[PluginsClient] userMessage.tokenCount', userMessage.tokenCount); } this.handleTokenCountMap(tokenCountMap); } @@ -370,10 +365,7 @@ class PluginsClient extends OpenAIClient { return await this.handleResponseMessage(responseMessage, saveOptions, user); } - if (this.options.debug) { - console.debug('Plugins completion phase: this.result'); - console.debug(this.result); - } + logger.debug('[PluginsClient] Completion phase: this.result', this.result); const promptPrefix = buildPromptPrefix({ result: this.result, @@ -381,28 +373,20 @@ class PluginsClient extends OpenAIClient { functionsAgent: this.functionsAgent, }); - if (this.options.debug) { - console.debug('Plugins: promptPrefix'); - console.debug(promptPrefix); - } + logger.debug('[PluginsClient]', { promptPrefix }); payload = await this.buildCompletionPrompt({ messages: this.currentMessages, promptPrefix, }); - if (this.options.debug) { - console.debug('buildCompletionPrompt Payload'); - console.debug(payload); - } + logger.debug('[PluginsClient] buildCompletionPrompt Payload', payload); responseMessage.text = await this.sendCompletion(payload, opts); return await this.handleResponseMessage(responseMessage, saveOptions, user); } async buildCompletionPrompt({ messages, promptPrefix: _promptPrefix }) { - if (this.options.debug) { - console.debug('buildCompletionPrompt messages', messages); - } + logger.debug('[PluginsClient] buildCompletionPrompt messages', messages); const orderedMessages = messages; let promptPrefix = _promptPrefix.trim(); diff --git a/api/app/clients/TextStream.js b/api/app/clients/TextStream.js index 59ecd82d1ac..01809e87fa0 100644 --- a/api/app/clients/TextStream.js +++ b/api/app/clients/TextStream.js @@ -1,4 +1,5 @@ const { Readable } = require('stream'); +const { logger } = require('~/config'); class TextStream extends Readable { constructor(text, options = {}) { @@ -38,7 +39,7 @@ class TextStream extends Readable { }); this.on('end', () => { - // console.log('Stream ended'); + // logger.debug('[processTextStream] Stream ended'); resolve(); }); @@ -50,7 +51,7 @@ class TextStream extends Readable { try { await streamPromise; } catch (err) { - console.error('Error processing text stream:', err); + logger.error('[processTextStream] Error in text stream:', err); // Handle the error appropriately, e.g., return an error message or throw an error } } diff --git a/api/app/clients/agents/CustomAgent/outputParser.js b/api/app/clients/agents/CustomAgent/outputParser.js index 80b2d729135..9d849519f5a 100644 --- a/api/app/clients/agents/CustomAgent/outputParser.js +++ b/api/app/clients/agents/CustomAgent/outputParser.js @@ -1,4 +1,5 @@ const { ZeroShotAgentOutputParser } = require('langchain/agents'); +const { logger } = require('~/config'); class CustomOutputParser extends ZeroShotAgentOutputParser { constructor(fields) { @@ -64,9 +65,9 @@ class CustomOutputParser extends ZeroShotAgentOutputParser { const match = this.actionValues.exec(text); // old v2 if (!match) { - console.log( - '\n\n<----------------------HIT NO MATCH PARSING ERROR---------------------->\n\n', - match, + logger.debug( + '\n\n<----------------------[CustomOutputParser] HIT NO MATCH PARSING ERROR---------------------->\n\n' + + match, ); const thoughts = text.replace(/[tT]hought:/, '').split('\n'); // return { @@ -84,9 +85,9 @@ class CustomOutputParser extends ZeroShotAgentOutputParser { let selectedTool = match?.[1].trim().toLowerCase(); if (match && selectedTool === 'n/a') { - console.log( - '\n\n<----------------------HIT N/A PARSING ERROR---------------------->\n\n', - match, + logger.debug( + '\n\n<----------------------[CustomOutputParser] HIT N/A PARSING ERROR---------------------->\n\n' + + match, ); return { tool: 'self-reflection', @@ -97,25 +98,25 @@ class CustomOutputParser extends ZeroShotAgentOutputParser { let toolIsValid = this.checkIfValidTool(selectedTool); if (match && !toolIsValid) { - console.log( - '\n\n<----------------Tool invalid: Re-assigning Selected Tool---------------->\n\n', - match, + logger.debug( + '\n\n<----------------[CustomOutputParser] Tool invalid: Re-assigning Selected Tool---------------->\n\n' + + match, ); selectedTool = this.getValidTool(selectedTool); } if (match && !selectedTool) { - console.log( - '\n\n<----------------------HIT INVALID TOOL PARSING ERROR---------------------->\n\n', - match, + logger.debug( + '\n\n<----------------------[CustomOutputParser] HIT INVALID TOOL PARSING ERROR---------------------->\n\n' + + match, ); selectedTool = 'self-reflection'; } if (match && !match[2]) { - console.log( - '\n\n<----------------------HIT NO ACTION INPUT PARSING ERROR---------------------->\n\n', - match, + logger.debug( + '\n\n<----------------------[CustomOutputParser] HIT NO ACTION INPUT PARSING ERROR---------------------->\n\n' + + match, ); // In case there is no action input, let's double-check if there is an action input in 'text' variable @@ -139,7 +140,9 @@ class CustomOutputParser extends ZeroShotAgentOutputParser { } if (match && selectedTool.length > this.longestToolName.length) { - console.log('\n\n<----------------------HIT LONG PARSING ERROR---------------------->\n\n'); + logger.debug( + '\n\n<----------------------[CustomOutputParser] HIT LONG PARSING ERROR---------------------->\n\n', + ); let action, input, thought; let firstIndex = Infinity; @@ -156,9 +159,9 @@ class CustomOutputParser extends ZeroShotAgentOutputParser { // In case there is no action input, let's double-check if there is an action input in 'text' variable const actionInputMatch = this.actionInputRegex.exec(text); if (action && actionInputMatch) { - console.log( - '\n\n<------Matched Action Input in Long Parsing Error------>\n\n', - actionInputMatch, + logger.debug( + '\n\n<------[CustomOutputParser] Matched Action Input in Long Parsing Error------>\n\n' + + actionInputMatch, ); return { tool: action, @@ -185,15 +188,14 @@ class CustomOutputParser extends ZeroShotAgentOutputParser { const inputMatch = this.actionValues.exec(returnValues.log); //new if (inputMatch) { - console.log('inputMatch'); - console.dir(inputMatch, { depth: null }); + logger.debug('[CustomOutputParser] inputMatch', inputMatch); returnValues.toolInput = inputMatch[1].replaceAll('"', '').trim(); returnValues.log = returnValues.log.replace(this.actionValues, ''); } return returnValues; } else { - console.log('No valid tool mentioned.', this.tools, text); + logger.debug('[CustomOutputParser] No valid tool mentioned.', this.tools, text); return { tool: 'self-reflection', toolInput: 'Hypothetical actions: \n"' + text + '"\n', @@ -202,8 +204,8 @@ class CustomOutputParser extends ZeroShotAgentOutputParser { } // if (action && input) { - // console.log('Action:', action); - // console.log('Input:', input); + // logger.debug('Action:', action); + // logger.debug('Input:', input); // } } diff --git a/api/app/clients/agents/Functions/FunctionsAgent.js b/api/app/clients/agents/Functions/FunctionsAgent.js index 399d3f84737..476a6bda5ce 100644 --- a/api/app/clients/agents/Functions/FunctionsAgent.js +++ b/api/app/clients/agents/Functions/FunctionsAgent.js @@ -7,6 +7,8 @@ const { SystemMessagePromptTemplate, HumanMessagePromptTemplate, } = require('langchain/prompts'); +const { logger } = require('~/config'); + const PREFIX = 'You are a helpful AI assistant.'; function parseOutput(message) { @@ -112,7 +114,7 @@ class FunctionsAgent extends Agent { valuesForLLM, callbackManager, ); - console.log('message', message); + logger.debug('[FunctionsAgent] plan message', message); return parseOutput(message); } } diff --git a/api/app/clients/callbacks/createStartHandler.js b/api/app/clients/callbacks/createStartHandler.js index 48d17c9adf9..4bc32bc0c2e 100644 --- a/api/app/clients/callbacks/createStartHandler.js +++ b/api/app/clients/callbacks/createStartHandler.js @@ -1,8 +1,9 @@ const { promptTokensEstimate } = require('openai-chat-tokens'); -const { EModelEndpoint } = require('librechat-data-provider'); +const { EModelEndpoint, supportsBalanceCheck } = require('librechat-data-provider'); const { formatFromLangChain } = require('~/app/clients/prompts'); const checkBalance = require('~/models/checkBalance'); const { isEnabled } = require('~/server/utils'); +const { logger } = require('~/config'); const createStartHandler = ({ context, @@ -16,9 +17,15 @@ const createStartHandler = ({ const { model, functions, function_call } = invocation_params; const messages = _messages[0].map(formatFromLangChain); - if (manager.debug) { - console.log(`handleChatModelStart: ${context}`); - console.dir({ model, functions, function_call }, { depth: null }); + logger.debug(`[createStartHandler] handleChatModelStart: ${context}`, { + model, + function_call, + }); + + if (context !== 'title') { + logger.debug(`[createStartHandler] handleChatModelStart: ${context}`, { + functions, + }); } const payload = { messages }; @@ -35,13 +42,15 @@ const createStartHandler = ({ } prelimPromptTokens += promptTokensEstimate(payload); - if (manager.debug) { - console.log('Prelim Prompt Tokens & Token Buffer', prelimPromptTokens, tokenBuffer); - } + logger.debug('[createStartHandler]', { + prelimPromptTokens, + tokenBuffer, + }); prelimPromptTokens += tokenBuffer; try { - if (isEnabled(process.env.CHECK_BALANCE)) { + // TODO: if plugins extends to non-OpenAI models, this will need to be updated + if (isEnabled(process.env.CHECK_BALANCE) && supportsBalanceCheck[EModelEndpoint.openAI]) { const generations = initialMessageCount && messages.length > initialMessageCount ? messages.slice(initialMessageCount) @@ -61,7 +70,7 @@ const createStartHandler = ({ }); } } catch (err) { - console.error(`[${context}] checkBalance error`, err); + logger.error(`[createStartHandler][${context}] checkBalance error`, err); manager.abortController.abort(); if (context === 'summary' || context === 'plugins') { manager.addRun(runId, { conversationId, error: err.message }); diff --git a/api/app/clients/chains/runTitleChain.js b/api/app/clients/chains/runTitleChain.js index ec7b6e48c8e..a020ffb8e39 100644 --- a/api/app/clients/chains/runTitleChain.js +++ b/api/app/clients/chains/runTitleChain.js @@ -1,6 +1,7 @@ const { z } = require('zod'); const { langPrompt, createTitlePrompt, escapeBraces, getSnippet } = require('../prompts'); const { createStructuredOutputChainFromZod } = require('langchain/chains/openai_functions'); +const { logger } = require('~/config'); const langSchema = z.object({ language: z.string().describe('The language of the input text (full noun, no abbreviations).'), @@ -30,8 +31,7 @@ const runTitleChain = async ({ llm, text, convo, signal, callbacks }) => { try { snippet = getSnippet(text); } catch (e) { - console.log('Error getting snippet of text for titleChain'); - console.log(e); + logger.error('[runTitleChain] Error getting snippet of text for titleChain', e); } const languageChain = createLanguageChain({ llm, callbacks }); const titleChain = createTitleChain({ llm, callbacks, convo: escapeBraces(convo) }); diff --git a/api/app/clients/llm/RunManager.js b/api/app/clients/llm/RunManager.js index 8e0219cae73..7ab0b06b520 100644 --- a/api/app/clients/llm/RunManager.js +++ b/api/app/clients/llm/RunManager.js @@ -1,5 +1,6 @@ -const { createStartHandler } = require('../callbacks'); -const spendTokens = require('../../../models/spendTokens'); +const { createStartHandler } = require('~/app/clients/callbacks'); +const spendTokens = require('~/models/spendTokens'); +const { logger } = require('~/config'); class RunManager { constructor(fields) { @@ -35,7 +36,7 @@ class RunManager { if (this.runs.has(runId)) { this.runs.delete(runId); } else { - console.error(`Run with ID ${runId} does not exist.`); + logger.error(`[api/app/clients/llm/RunManager] Run with ID ${runId} does not exist.`); } } @@ -57,10 +58,19 @@ class RunManager { { handleChatModelStart: createStartHandler({ ...metadata, manager: this }), handleLLMEnd: async (output, runId, _parentRunId) => { - if (this.debug) { - console.log(`handleLLMEnd: ${JSON.stringify(metadata)}`); - console.dir({ output, runId, _parentRunId }, { depth: null }); + const { llmOutput, ..._output } = output; + logger.debug(`[RunManager] handleLLMEnd: ${JSON.stringify(metadata)}`, { + runId, + _parentRunId, + llmOutput, + }); + + if (metadata.context !== 'title') { + logger.debug('[RunManager] handleLLMEnd:', { + output: _output, + }); } + const { tokenUsage } = output.llmOutput; const run = this.getRunById(runId); this.removeRun(runId); @@ -74,8 +84,7 @@ class RunManager { await spendTokens(txData, tokenUsage); }, handleLLMError: async (err) => { - this.debug && console.log(`handleLLMError: ${JSON.stringify(metadata)}`); - this.debug && console.error(err); + logger.error(`[RunManager] handleLLMError: ${JSON.stringify(metadata)}`, err); if (metadata.context === 'title') { return; } else if (metadata.context === 'plugins') { diff --git a/api/app/clients/memory/summaryBuffer.js b/api/app/clients/memory/summaryBuffer.js index eb36e71a572..0555fc214ec 100644 --- a/api/app/clients/memory/summaryBuffer.js +++ b/api/app/clients/memory/summaryBuffer.js @@ -1,6 +1,7 @@ const { ConversationSummaryBufferMemory, ChatMessageHistory } = require('langchain/memory'); const { formatLangChainMessages, SUMMARY_PROMPT } = require('../prompts'); const { predictNewSummary } = require('../chains'); +const { logger } = require('~/config'); const createSummaryBufferMemory = ({ llm, prompt, messages, ...rest }) => { const chatHistory = new ChatMessageHistory(messages); @@ -22,9 +23,8 @@ const summaryBuffer = async ({ prompt = SUMMARY_PROMPT, signal, }) => { - if (debug && previous_summary) { - console.log('<-----------PREVIOUS SUMMARY----------->\n\n'); - console.log(previous_summary); + if (previous_summary) { + logger.debug('[summaryBuffer]', { previous_summary }); } const formattedMessages = formatLangChainMessages(context, formatOptions); @@ -46,8 +46,7 @@ const summaryBuffer = async ({ const messages = await chatPromptMemory.chatHistory.getMessages(); if (debug) { - console.log('<-----------SUMMARY BUFFER MESSAGES----------->\n\n'); - console.log(JSON.stringify(messages)); + logger.debug('[summaryBuffer]', { summary_buffer_messages: messages.length }); } const predictSummary = await predictNewSummary({ @@ -58,8 +57,7 @@ const summaryBuffer = async ({ }); if (debug) { - console.log('<-----------SUMMARY----------->\n\n'); - console.log(JSON.stringify(predictSummary)); + logger.debug('[summaryBuffer]', { summary: predictSummary }); } return { role: 'system', content: predictSummary }; diff --git a/api/app/clients/output_parsers/addImages.js b/api/app/clients/output_parsers/addImages.js index b64dc16d46c..ec04bcac86c 100644 --- a/api/app/clients/output_parsers/addImages.js +++ b/api/app/clients/output_parsers/addImages.js @@ -1,3 +1,5 @@ +const { logger } = require('~/config'); + /** * The `addImages` function corrects any erroneous image URLs in the `responseMessage.text` * and appends image observations from `intermediateSteps` if they are not already present. @@ -20,7 +22,7 @@ * * addImages(intermediateSteps, responseMessage); * - * console.log(responseMessage.text); + * logger.debug(responseMessage.text); * // Outputs: 'Some text with ![desc](/images/test.png)\n![desc](/images/test.png)' * * @returns {void} @@ -58,12 +60,10 @@ function addImages(intermediateSteps, responseMessage) { if (!observation || !observation.includes('![')) { return; } - const observedImagePath = observation.match(/\(\/images\/.*\.\w*\)/g); + const observedImagePath = observation.match(/!\[.*\]\([^)]*\)/g); if (observedImagePath && !responseMessage.text.includes(observedImagePath[0])) { responseMessage.text += '\n' + observation; - if (process.env.DEBUG_PLUGINS) { - console.debug('[addImages] added image from intermediateSteps'); - } + logger.debug('[addImages] added image from intermediateSteps:', observation); } }); } diff --git a/api/app/clients/prompts/formatMessages.spec.js b/api/app/clients/prompts/formatMessages.spec.js index 0497e4c47a0..636cdb1c8e5 100644 --- a/api/app/clients/prompts/formatMessages.spec.js +++ b/api/app/clients/prompts/formatMessages.spec.js @@ -54,7 +54,6 @@ describe('formatMessage', () => { _id: '6512cdfb92cbf69fea615331', messageId: 'b620bf73-c5c3-4a38-b724-76886aac24c4', __v: 0, - cancelled: false, conversationId: '5c23d24f-941f-4aab-85df-127b596c8aa5', createdAt: Date.now(), error: false, diff --git a/api/app/clients/specs/BaseClient.test.js b/api/app/clients/specs/BaseClient.test.js index 6e9b383de7e..889499fbc29 100644 --- a/api/app/clients/specs/BaseClient.test.js +++ b/api/app/clients/specs/BaseClient.test.js @@ -1,19 +1,33 @@ const { initializeFakeClient } = require('./FakeClient'); jest.mock('../../../lib/db/connectDb'); -jest.mock('../../../models', () => { - return function () { - return { - save: jest.fn(), - deleteConvos: jest.fn(), - getConvo: jest.fn(), - getMessages: jest.fn(), - saveMessage: jest.fn(), - updateMessage: jest.fn(), - saveConvo: jest.fn(), - }; - }; -}); +jest.mock('~/models', () => ({ + User: jest.fn(), + Key: jest.fn(), + Session: jest.fn(), + Balance: jest.fn(), + Transaction: jest.fn(), + getMessages: jest.fn().mockResolvedValue([]), + saveMessage: jest.fn(), + updateMessage: jest.fn(), + deleteMessagesSince: jest.fn(), + deleteMessages: jest.fn(), + getConvoTitle: jest.fn(), + getConvo: jest.fn(), + saveConvo: jest.fn(), + deleteConvos: jest.fn(), + getPreset: jest.fn(), + getPresets: jest.fn(), + savePreset: jest.fn(), + deletePresets: jest.fn(), + findFileById: jest.fn(), + createFile: jest.fn(), + updateFile: jest.fn(), + deleteFile: jest.fn(), + deleteFiles: jest.fn(), + getFiles: jest.fn(), + updateFileUsage: jest.fn(), +})); jest.mock('langchain/chat_models/openai', () => { return { diff --git a/api/app/clients/specs/FakeClient.js b/api/app/clients/specs/FakeClient.js index cc6a54d3dbe..a5915adcf24 100644 --- a/api/app/clients/specs/FakeClient.js +++ b/api/app/clients/specs/FakeClient.js @@ -42,7 +42,6 @@ class FakeClient extends BaseClient { this.maxContextTokens = getModelMaxTokens(this.modelOptions.model) ?? 4097; } - getCompletion() {} buildMessages() {} getTokenCount(str) { return str.length; @@ -86,6 +85,19 @@ const initializeFakeClient = (apiKey, options, fakeMessages) => { return 'Mock response text'; }); + // eslint-disable-next-line no-unused-vars + TestClient.getCompletion = jest.fn().mockImplementation(async (..._args) => { + return { + choices: [ + { + message: { + content: 'Mock response text', + }, + }, + ], + }; + }); + TestClient.buildMessages = jest.fn(async (messages, parentMessageId) => { const orderedMessages = TestClient.constructor.getMessagesForConversation({ messages, diff --git a/api/app/clients/specs/OpenAIClient.test.js b/api/app/clients/specs/OpenAIClient.test.js index 3fbf75f5745..a31d82fe126 100644 --- a/api/app/clients/specs/OpenAIClient.test.js +++ b/api/app/clients/specs/OpenAIClient.test.js @@ -1,8 +1,138 @@ require('dotenv').config(); +const OpenAI = require('openai'); +const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source'); +const { genAzureChatCompletion } = require('~/utils/azureUtils'); const OpenAIClient = require('../OpenAIClient'); - jest.mock('meilisearch'); +jest.mock('~/lib/db/connectDb'); +jest.mock('~/models', () => ({ + User: jest.fn(), + Key: jest.fn(), + Session: jest.fn(), + Balance: jest.fn(), + Transaction: jest.fn(), + getMessages: jest.fn().mockResolvedValue([]), + saveMessage: jest.fn(), + updateMessage: jest.fn(), + deleteMessagesSince: jest.fn(), + deleteMessages: jest.fn(), + getConvoTitle: jest.fn(), + getConvo: jest.fn(), + saveConvo: jest.fn(), + deleteConvos: jest.fn(), + getPreset: jest.fn(), + getPresets: jest.fn(), + savePreset: jest.fn(), + deletePresets: jest.fn(), + findFileById: jest.fn(), + createFile: jest.fn(), + updateFile: jest.fn(), + deleteFile: jest.fn(), + deleteFiles: jest.fn(), + getFiles: jest.fn(), + updateFileUsage: jest.fn(), +})); + +jest.mock('langchain/chat_models/openai', () => { + return { + ChatOpenAI: jest.fn().mockImplementation(() => { + return {}; + }), + }; +}); + +jest.mock('openai'); + +jest.spyOn(OpenAI, 'constructor').mockImplementation(function (...options) { + // We can add additional logic here if needed + return new OpenAI(...options); +}); + +const finalChatCompletion = jest.fn().mockResolvedValue({ + choices: [ + { + message: { role: 'assistant', content: 'Mock message content' }, + finish_reason: 'Mock finish reason', + }, + ], +}); + +const stream = jest.fn().mockImplementation(() => { + let isDone = false; + let isError = false; + let errorCallback = null; + + const onEventHandlers = { + abort: () => { + // Mock abort behavior + }, + error: (callback) => { + errorCallback = callback; // Save the error callback for later use + }, + finalMessage: (callback) => { + callback({ role: 'assistant', content: 'Mock Response' }); + isDone = true; // Set stream to done + }, + }; + + const mockStream = { + on: jest.fn((event, callback) => { + if (onEventHandlers[event]) { + onEventHandlers[event](callback); + } + return mockStream; + }), + finalChatCompletion, + controller: { abort: jest.fn() }, + triggerError: () => { + isError = true; + if (errorCallback) { + errorCallback(new Error('Mock error')); + } + }, + [Symbol.asyncIterator]: () => { + return { + next: () => { + if (isError) { + return Promise.reject(new Error('Mock error')); + } + if (isDone) { + return Promise.resolve({ done: true }); + } + const chunk = { choices: [{ delta: { content: 'Mock chunk' } }] }; + return Promise.resolve({ value: chunk, done: false }); + }, + }; + }, + }; + return mockStream; +}); + +const create = jest.fn().mockResolvedValue({ + choices: [ + { + message: { content: 'Mock message content' }, + finish_reason: 'Mock finish reason', + }, + ], +}); + +OpenAI.mockImplementation(() => ({ + beta: { + chat: { + completions: { + stream, + }, + }, + }, + chat: { + completions: { + create, + }, + }, +})); + describe('OpenAIClient', () => { let client, client2; const model = 'gpt-4'; @@ -12,6 +142,21 @@ describe('OpenAIClient', () => { { role: 'assistant', sender: 'Assistant', text: 'Hi', messageId: '2' }, ]; + const defaultOptions = { + // debug: true, + openaiApiKey: 'new-api-key', + modelOptions: { + model, + temperature: 0.7, + }, + }; + + const defaultAzureOptions = { + azureOpenAIApiInstanceName: 'your-instance-name', + azureOpenAIApiDeploymentName: 'your-deployment-name', + azureOpenAIApiVersion: '2020-07-01-preview', + }; + beforeAll(() => { jest.spyOn(console, 'warn').mockImplementation(() => {}); }); @@ -21,14 +166,7 @@ describe('OpenAIClient', () => { }); beforeEach(() => { - const options = { - // debug: true, - openaiApiKey: 'new-api-key', - modelOptions: { - model, - temperature: 0.7, - }, - }; + const options = { ...defaultOptions }; client = new OpenAIClient('test-api-key', options); client2 = new OpenAIClient('test-api-key', options); client.summarizeMessages = jest.fn().mockResolvedValue({ @@ -40,6 +178,7 @@ describe('OpenAIClient', () => { .fn() .mockResolvedValue({ prompt: messages.map((m) => m.text).join('\n') }); client.constructor.freeAndResetAllEncoders(); + client.getMessages = jest.fn().mockResolvedValue([]); }); describe('setOptions', () => { @@ -408,4 +547,86 @@ describe('OpenAIClient', () => { }); }); }); + + describe('sendMessage/getCompletion/chatCompletion', () => { + afterEach(() => { + delete process.env.AZURE_OPENAI_DEFAULT_MODEL; + delete process.env.AZURE_USE_MODEL_AS_DEPLOYMENT_NAME; + delete process.env.OPENROUTER_API_KEY; + }); + + it('should call getCompletion and fetchEventSource when using a text/instruct model', async () => { + const model = 'text-davinci-003'; + const onProgress = jest.fn().mockImplementation(() => ({})); + + const testClient = new OpenAIClient('test-api-key', { + ...defaultOptions, + modelOptions: { model }, + }); + + const getCompletion = jest.spyOn(testClient, 'getCompletion'); + await testClient.sendMessage('Hi mom!', { onProgress }); + + expect(getCompletion).toHaveBeenCalled(); + expect(getCompletion.mock.calls.length).toBe(1); + + const currentDateString = new Date().toLocaleDateString('en-us', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + + expect(getCompletion.mock.calls[0][0]).toBe( + `||>Instructions:\nYou are ChatGPT, a large language model trained by OpenAI. Respond conversationally.\nCurrent date: ${currentDateString}\n\n||>User:\nHi mom!\n||>Assistant:\n`, + ); + + expect(fetchEventSource).toHaveBeenCalled(); + expect(fetchEventSource.mock.calls.length).toBe(1); + + // Check if the first argument (url) is correct + const firstCallArgs = fetchEventSource.mock.calls[0]; + + const expectedURL = 'https://api.openai.com/v1/completions'; + expect(firstCallArgs[0]).toBe(expectedURL); + + const requestBody = JSON.parse(firstCallArgs[1].body); + expect(requestBody).toHaveProperty('model'); + expect(requestBody.model).toBe(model); + }); + + it('[Azure OpenAI] should call chatCompletion and OpenAI.stream with correct args', async () => { + // Set a default model + process.env.AZURE_OPENAI_DEFAULT_MODEL = 'gpt4-turbo'; + + const onProgress = jest.fn().mockImplementation(() => ({})); + client.azure = defaultAzureOptions; + const chatCompletion = jest.spyOn(client, 'chatCompletion'); + await client.sendMessage('Hi mom!', { + replaceOptions: true, + ...defaultOptions, + modelOptions: { model: 'gpt4-turbo', stream: true }, + onProgress, + azure: defaultAzureOptions, + }); + + expect(chatCompletion).toHaveBeenCalled(); + expect(chatCompletion.mock.calls.length).toBe(1); + + const chatCompletionArgs = chatCompletion.mock.calls[0][0]; + const { payload } = chatCompletionArgs; + + expect(payload[0].role).toBe('user'); + expect(payload[0].content).toBe('Hi mom!'); + + // Azure OpenAI does not use the model property, and will error if it's passed + // This check ensures the model property is not present + const streamArgs = stream.mock.calls[0][0]; + expect(streamArgs).not.toHaveProperty('model'); + + // Check if the baseURL is correct + const constructorArgs = OpenAI.mock.calls[0][0]; + const expectedURL = genAzureChatCompletion(defaultAzureOptions).split('/chat')[0]; + expect(constructorArgs.baseURL).toBe(expectedURL); + }); + }); }); diff --git a/api/app/clients/tools/AIPluginTool.js b/api/app/clients/tools/AIPluginTool.js deleted file mode 100644 index b89d3f0be17..00000000000 --- a/api/app/clients/tools/AIPluginTool.js +++ /dev/null @@ -1,238 +0,0 @@ -const { Tool } = require('langchain/tools'); -const yaml = require('js-yaml'); - -/* -export interface AIPluginToolParams { - name: string; - description: string; - apiSpec: string; - openaiSpec: string; - model: BaseLanguageModel; -} - -export interface PathParameter { - name: string; - description: string; -} - -export interface Info { - title: string; - description: string; - version: string; -} -export interface PathMethod { - summary: string; - operationId: string; - parameters?: PathParameter[]; -} - -interface ApiSpec { - openapi: string; - info: Info; - paths: { [key: string]: { [key: string]: PathMethod } }; -} -*/ - -function isJson(str) { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; -} - -function convertJsonToYamlIfApplicable(spec) { - if (isJson(spec)) { - const jsonData = JSON.parse(spec); - return yaml.dump(jsonData); - } - return spec; -} - -function extractShortVersion(openapiSpec) { - openapiSpec = convertJsonToYamlIfApplicable(openapiSpec); - try { - const fullApiSpec = yaml.load(openapiSpec); - const shortApiSpec = { - openapi: fullApiSpec.openapi, - info: fullApiSpec.info, - paths: {}, - }; - - for (let path in fullApiSpec.paths) { - shortApiSpec.paths[path] = {}; - for (let method in fullApiSpec.paths[path]) { - shortApiSpec.paths[path][method] = { - summary: fullApiSpec.paths[path][method].summary, - operationId: fullApiSpec.paths[path][method].operationId, - parameters: fullApiSpec.paths[path][method].parameters?.map((parameter) => ({ - name: parameter.name, - description: parameter.description, - })), - }; - } - } - - return yaml.dump(shortApiSpec); - } catch (e) { - console.log(e); - return ''; - } -} -function printOperationDetails(operationId, openapiSpec) { - openapiSpec = convertJsonToYamlIfApplicable(openapiSpec); - let returnText = ''; - try { - let doc = yaml.load(openapiSpec); - let servers = doc.servers; - let paths = doc.paths; - let components = doc.components; - - for (let path in paths) { - for (let method in paths[path]) { - let operation = paths[path][method]; - if (operation.operationId === operationId) { - returnText += `The API request to do for operationId "${operationId}" is:\n`; - returnText += `Method: ${method.toUpperCase()}\n`; - - let url = servers[0].url + path; - returnText += `Path: ${url}\n`; - - returnText += 'Parameters:\n'; - if (operation.parameters) { - for (let param of operation.parameters) { - let required = param.required ? '' : ' (optional),'; - returnText += `- ${param.name} (${param.in},${required} ${param.schema.type}): ${param.description}\n`; - } - } else { - returnText += ' None\n'; - } - returnText += '\n'; - - let responseSchema = operation.responses['200'].content['application/json'].schema; - - // Check if schema is a reference - if (responseSchema.$ref) { - // Extract schema name from reference - let schemaName = responseSchema.$ref.split('/').pop(); - // Look up schema in components - responseSchema = components.schemas[schemaName]; - } - - returnText += 'Response schema:\n'; - returnText += '- Type: ' + responseSchema.type + '\n'; - returnText += '- Additional properties:\n'; - returnText += ' - Type: ' + responseSchema.additionalProperties?.type + '\n'; - if (responseSchema.additionalProperties?.properties) { - returnText += ' - Properties:\n'; - for (let prop in responseSchema.additionalProperties.properties) { - returnText += ` - ${prop} (${responseSchema.additionalProperties.properties[prop].type}): Description not provided in OpenAPI spec\n`; - } - } - } - } - } - if (returnText === '') { - returnText += `No operation with operationId "${operationId}" found.`; - } - return returnText; - } catch (e) { - console.log(e); - return ''; - } -} - -class AIPluginTool extends Tool { - /* - private _name: string; - private _description: string; - apiSpec: string; - openaiSpec: string; - model: BaseLanguageModel; - */ - - get name() { - return this._name; - } - - get description() { - return this._description; - } - - constructor(params) { - super(); - this._name = params.name; - this._description = params.description; - this.apiSpec = params.apiSpec; - this.openaiSpec = params.openaiSpec; - this.model = params.model; - } - - async _call(input) { - let date = new Date(); - let fullDate = `Date: ${date.getDate()}/${ - date.getMonth() + 1 - }/${date.getFullYear()}, Time: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; - const prompt = `${fullDate}\nQuestion: ${input} \n${this.apiSpec}.`; - console.log(prompt); - const gptResponse = await this.model.predict(prompt); - let operationId = gptResponse.match(/operationId: (.*)/)?.[1]; - if (!operationId) { - return 'No operationId found in the response'; - } - if (operationId == 'No API path found to answer the question') { - return 'No API path found to answer the question'; - } - - let openApiData = printOperationDetails(operationId, this.openaiSpec); - - return openApiData; - } - - static async fromPluginUrl(url, model) { - const aiPluginRes = await fetch(url, {}); - if (!aiPluginRes.ok) { - throw new Error(`Failed to fetch plugin from ${url} with status ${aiPluginRes.status}`); - } - const aiPluginJson = await aiPluginRes.json(); - const apiUrlRes = await fetch(aiPluginJson.api.url, {}); - if (!apiUrlRes.ok) { - throw new Error( - `Failed to fetch API spec from ${aiPluginJson.api.url} with status ${apiUrlRes.status}`, - ); - } - const apiUrlJson = await apiUrlRes.text(); - const shortApiSpec = extractShortVersion(apiUrlJson); - return new AIPluginTool({ - name: aiPluginJson.name_for_model.toLowerCase(), - description: `A \`tool\` to learn the API documentation for ${aiPluginJson.name_for_model.toLowerCase()}, after which you can use 'http_request' to make the actual API call. Short description of how to use the API's results: ${ - aiPluginJson.description_for_model - })`, - apiSpec: ` -As an AI, your task is to identify the operationId of the relevant API path based on the condensed OpenAPI specifications provided. - -Please note: - -1. Do not imagine URLs. Only use the information provided in the condensed OpenAPI specifications. - -2. Do not guess the operationId. Identify it strictly based on the API paths and their descriptions. - -Your output should only include: -- operationId: The operationId of the relevant API path - -If you cannot find a suitable API path based on the OpenAPI specifications, please answer only "operationId: No API path found to answer the question". - -Now, based on the question above and the condensed OpenAPI specifications given below, identify the operationId: - -\`\`\` -${shortApiSpec} -\`\`\` -`, - openaiSpec: apiUrlJson, - model: model, - }); - } -} - -module.exports = AIPluginTool; diff --git a/api/app/clients/tools/AzureAiSearch.js b/api/app/clients/tools/AzureAiSearch.js index 4f4b8a1ffee..2d74c005439 100644 --- a/api/app/clients/tools/AzureAiSearch.js +++ b/api/app/clients/tools/AzureAiSearch.js @@ -1,6 +1,7 @@ -const { StructuredTool } = require('langchain/tools'); const { z } = require('zod'); +const { StructuredTool } = require('langchain/tools'); const { SearchClient, AzureKeyCredential } = require('@azure/search-documents'); +const { logger } = require('~/config'); class AzureAISearch extends StructuredTool { // Constants for default values @@ -94,7 +95,7 @@ class AzureAISearch extends StructuredTool { } return JSON.stringify(resultDocuments); } catch (error) { - console.error(`Azure AI Search request failed: ${error.message}`); + logger.error('Azure AI Search request failed', error); return 'There was an error with Azure AI Search.'; } } diff --git a/api/app/clients/tools/DALL-E.js b/api/app/clients/tools/DALL-E.js index 505f570ace8..387294a1cbb 100644 --- a/api/app/clients/tools/DALL-E.js +++ b/api/app/clients/tools/DALL-E.js @@ -3,18 +3,28 @@ const fs = require('fs'); const path = require('path'); const OpenAI = require('openai'); -// const { genAzureEndpoint } = require('../../../utils/genAzureEndpoints'); +// const { genAzureEndpoint } = require('~/utils/genAzureEndpoints'); +const { v4: uuidv4 } = require('uuid'); const { Tool } = require('langchain/tools'); const { HttpsProxyAgent } = require('https-proxy-agent'); +const { + saveImageToFirebaseStorage, + getFirebaseStorageImageUrl, + getFirebaseStorage, +} = require('~/server/services/Files/Firebase'); +const { getImageBasename } = require('~/server/services/Files/images'); +const extractBaseURL = require('~/utils/extractBaseURL'); const saveImageFromUrl = require('./saveImageFromUrl'); -const extractBaseURL = require('../../../utils/extractBaseURL'); -const { DALLE_REVERSE_PROXY, PROXY } = process.env; +const { logger } = require('~/config'); +const { DALLE_REVERSE_PROXY, PROXY } = process.env; class OpenAICreateImage extends Tool { constructor(fields = {}) { super(); + this.userId = fields.userId; let apiKey = fields.DALLE_API_KEY || this.getApiKey(); + const config = { apiKey }; if (DALLE_REVERSE_PROXY) { config.baseURL = extractBaseURL(DALLE_REVERSE_PROXY); @@ -23,7 +33,6 @@ class OpenAICreateImage extends Tool { if (PROXY) { config.httpAgent = new HttpsProxyAgent(PROXY); } - // let azureKey = fields.AZURE_API_KEY || process.env.AZURE_API_KEY; // if (azureKey) { @@ -96,18 +105,31 @@ Guidelines: throw new Error('No image URL returned from OpenAI API.'); } - const regex = /img-[\w\d]+.png/; - const match = theImageUrl.match(regex); - let imageName = '1.png'; + const imageBasename = getImageBasename(theImageUrl); + let imageName = `image_${uuidv4()}.png`; - if (match) { - imageName = match[0]; - console.log(imageName); // Output: img-lgCf7ppcbhqQrz6a5ear6FOb.png + if (imageBasename) { + imageName = imageBasename; + logger.debug('[DALL-E]', { imageName }); // Output: img-lgCf7ppcbhqQrz6a5ear6FOb.png } else { - console.log('No image name found in the string.'); + logger.debug('[DALL-E] No image name found in the string.', { + theImageUrl, + data: resp.data[0], + }); } - this.outputPath = path.resolve(__dirname, '..', '..', '..', '..', 'client', 'public', 'images'); + this.outputPath = path.resolve( + __dirname, + '..', + '..', + '..', + '..', + 'client', + 'public', + 'images', + this.userId, + ); + const appRoot = path.resolve(__dirname, '..', '..', '..', '..', 'client'); this.relativeImageUrl = path.relative(appRoot, this.outputPath); @@ -116,14 +138,25 @@ Guidelines: fs.mkdirSync(this.outputPath, { recursive: true }); } - try { - await saveImageFromUrl(theImageUrl, this.outputPath, imageName); - this.result = this.getMarkdownImageUrl(imageName); - } catch (error) { - console.error('Error while saving the image:', error); - this.result = theImageUrl; + const storage = getFirebaseStorage(); + if (storage) { + try { + await saveImageToFirebaseStorage(this.userId, theImageUrl, imageName); + this.result = await getFirebaseStorageImageUrl(`${this.userId}/${imageName}`); + logger.debug('[DALL-E] result: ' + this.result); + } catch (error) { + logger.error('Error while saving the image to Firebase Storage:', error); + this.result = `Failed to save the image to Firebase Storage. ${error.message}`; + } + } else { + try { + await saveImageFromUrl(theImageUrl, this.outputPath, imageName); + this.result = this.getMarkdownImageUrl(imageName); + } catch (error) { + logger.error('Error while saving the image locally:', error); + this.result = `Failed to save the image locally. ${error.message}`; + } } - return this.result; } } diff --git a/api/app/clients/tools/GoogleSearch.js b/api/app/clients/tools/GoogleSearch.js index 3d782f164a9..3d7574b6c19 100644 --- a/api/app/clients/tools/GoogleSearch.js +++ b/api/app/clients/tools/GoogleSearch.js @@ -1,5 +1,6 @@ -const { Tool } = require('langchain/tools'); const { google } = require('googleapis'); +const { Tool } = require('langchain/tools'); +const { logger } = require('~/config'); /** * Represents a tool that allows an agent to use the Google Custom Search API. @@ -86,7 +87,7 @@ class GoogleSearchAPI extends Tool { }); // return response.data; - // console.log(response.data); + // logger.debug(response.data); if (!response.data.items || response.data.items.length === 0) { return this.resultsToReadableFormat([ @@ -110,7 +111,7 @@ class GoogleSearchAPI extends Tool { return this.resultsToReadableFormat(metadataResults); } catch (error) { - console.log(`Error searching Google: ${error}`); + logger.error('[GoogleSearchAPI]', error); // throw error; return 'There was an error searching Google.'; } diff --git a/api/app/clients/tools/HttpRequestTool.js b/api/app/clients/tools/HttpRequestTool.js deleted file mode 100644 index a85e783b221..00000000000 --- a/api/app/clients/tools/HttpRequestTool.js +++ /dev/null @@ -1,108 +0,0 @@ -const { Tool } = require('langchain/tools'); - -// class RequestsGetTool extends Tool { -// constructor(headers = {}, { maxOutputLength } = {}) { -// super(); -// this.name = 'requests_get'; -// this.headers = headers; -// this.maxOutputLength = maxOutputLength || 2000; -// this.description = `A portal to the internet. Use this when you need to get specific content from a website. -// - Input should be a url (i.e. https://www.google.com). The output will be the text response of the GET request.`; -// } - -// async _call(input) { -// const res = await fetch(input, { -// headers: this.headers -// }); -// const text = await res.text(); -// return text.slice(0, this.maxOutputLength); -// } -// } - -// class RequestsPostTool extends Tool { -// constructor(headers = {}, { maxOutputLength } = {}) { -// super(); -// this.name = 'requests_post'; -// this.headers = headers; -// this.maxOutputLength = maxOutputLength || Infinity; -// this.description = `Use this when you want to POST to a website. -// - Input should be a json string with two keys: "url" and "data". -// - The value of "url" should be a string, and the value of "data" should be a dictionary of -// - key-value pairs you want to POST to the url as a JSON body. -// - Be careful to always use double quotes for strings in the json string -// - The output will be the text response of the POST request.`; -// } - -// async _call(input) { -// try { -// const { url, data } = JSON.parse(input); -// const res = await fetch(url, { -// method: 'POST', -// headers: this.headers, -// body: JSON.stringify(data) -// }); -// const text = await res.text(); -// return text.slice(0, this.maxOutputLength); -// } catch (error) { -// return `${error}`; -// } -// } -// } - -class HttpRequestTool extends Tool { - constructor(headers = {}, { maxOutputLength = Infinity } = {}) { - super(); - this.headers = headers; - this.name = 'http_request'; - this.maxOutputLength = maxOutputLength; - this.description = - 'Executes HTTP methods (GET, POST, PUT, DELETE, etc.). The input is an object with three keys: "url", "method", and "data". Even for GET or DELETE, include "data" key as an empty string. "method" is the HTTP method, and "url" is the desired endpoint. If POST or PUT, "data" should contain a stringified JSON representing the body to send. Only one url per use.'; - } - - async _call(input) { - try { - const urlPattern = /"url":\s*"([^"]*)"/; - const methodPattern = /"method":\s*"([^"]*)"/; - const dataPattern = /"data":\s*"([^"]*)"/; - - const url = input.match(urlPattern)[1]; - const method = input.match(methodPattern)[1]; - let data = input.match(dataPattern)[1]; - - // Parse 'data' back to JSON if possible - try { - data = JSON.parse(data); - } catch (e) { - // If it's not a JSON string, keep it as is - } - - let options = { - method: method, - headers: this.headers, - }; - - if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && data) { - if (typeof data === 'object') { - options.body = JSON.stringify(data); - } else { - options.body = data; - } - options.headers['Content-Type'] = 'application/json'; - } - - const res = await fetch(url, options); - - const text = await res.text(); - if (text.includes(' 0) { - verbose && console.debug('headers detected', headers); + logger.debug('[createOpenAPIPlugin] headers detected', headers); chainOptions.headers = headers; } if (data.params) { - verbose && console.debug('params detected', data.params); + logger.debug('[createOpenAPIPlugin] params detected', data.params); chainOptions.params = data.params; } let history = ''; if (memory) { - verbose && console.debug('openAPI chain: memory detected', memory); + logger.debug('[createOpenAPIPlugin] openAPI chain: memory detected', memory); const { history: chat_history } = await memory.loadMemoryVariables({}); history = chat_history?.length > 0 ? `\n\n## Chat History:\n${chat_history}\n` : ''; } diff --git a/api/app/clients/tools/index.js b/api/app/clients/tools/index.js index ad8b61a7fc3..f5410e89eec 100644 --- a/api/app/clients/tools/index.js +++ b/api/app/clients/tools/index.js @@ -1,6 +1,4 @@ const GoogleSearchAPI = require('./GoogleSearch'); -const HttpRequestTool = require('./HttpRequestTool'); -const AIPluginTool = require('./AIPluginTool'); const OpenAICreateImage = require('./DALL-E'); const DALLE3 = require('./structured/DALLE3'); const StructuredSD = require('./structured/StableDiffusion'); @@ -20,8 +18,6 @@ const CodeBrew = require('./CodeBrew'); module.exports = { availableTools, GoogleSearchAPI, - HttpRequestTool, - AIPluginTool, OpenAICreateImage, DALLE3, StableDiffusionAPI, diff --git a/api/app/clients/tools/saveImageFromUrl.js b/api/app/clients/tools/saveImageFromUrl.js index e67f532cdf3..d8b14ad4783 100644 --- a/api/app/clients/tools/saveImageFromUrl.js +++ b/api/app/clients/tools/saveImageFromUrl.js @@ -1,6 +1,7 @@ -const axios = require('axios'); const fs = require('fs'); const path = require('path'); +const axios = require('axios'); +const { logger } = require('~/config'); async function saveImageFromUrl(url, outputPath, outputFilename) { try { @@ -32,7 +33,7 @@ async function saveImageFromUrl(url, outputPath, outputFilename) { writer.on('error', reject); }); } catch (error) { - console.error('Error while saving the image:', error); + logger.error('[saveImageFromUrl] Error while saving the image:', error); } } diff --git a/api/app/clients/tools/structured/AzureAISearch.js b/api/app/clients/tools/structured/AzureAISearch.js index 4f4b8a1ffee..2d74c005439 100644 --- a/api/app/clients/tools/structured/AzureAISearch.js +++ b/api/app/clients/tools/structured/AzureAISearch.js @@ -1,6 +1,7 @@ -const { StructuredTool } = require('langchain/tools'); const { z } = require('zod'); +const { StructuredTool } = require('langchain/tools'); const { SearchClient, AzureKeyCredential } = require('@azure/search-documents'); +const { logger } = require('~/config'); class AzureAISearch extends StructuredTool { // Constants for default values @@ -94,7 +95,7 @@ class AzureAISearch extends StructuredTool { } return JSON.stringify(resultDocuments); } catch (error) { - console.error(`Azure AI Search request failed: ${error.message}`); + logger.error('Azure AI Search request failed', error); return 'There was an error with Azure AI Search.'; } } diff --git a/api/app/clients/tools/structured/CodeSherpa.js b/api/app/clients/tools/structured/CodeSherpa.js index ebfe5129e1b..66311fca22d 100644 --- a/api/app/clients/tools/structured/CodeSherpa.js +++ b/api/app/clients/tools/structured/CodeSherpa.js @@ -28,14 +28,14 @@ class RunCode extends StructuredTool { } async _call({ code, language = 'python' }) { - // console.log('<--------------- Running Code --------------->', { code, language }); + // logger.debug('<--------------- Running Code --------------->', { code, language }); const response = await axios({ url: `${this.url}/repl`, method: 'post', headers: this.headers, data: { code, language }, }); - // console.log('<--------------- Sucessfully ran Code --------------->', response.data); + // logger.debug('<--------------- Sucessfully ran Code --------------->', response.data); return response.data.result; } } diff --git a/api/app/clients/tools/structured/CodeSherpaTools.js b/api/app/clients/tools/structured/CodeSherpaTools.js index 49c9a8c9154..4d1ab9805fe 100644 --- a/api/app/clients/tools/structured/CodeSherpaTools.js +++ b/api/app/clients/tools/structured/CodeSherpaTools.js @@ -42,14 +42,14 @@ class RunCode extends StructuredTool { } async _call({ code, language = 'python' }) { - // console.log('<--------------- Running Code --------------->', { code, language }); + // logger.debug('<--------------- Running Code --------------->', { code, language }); const response = await axios({ url: `${this.url}/repl`, method: 'post', headers: this.headers, data: { code, language }, }); - // console.log('<--------------- Sucessfully ran Code --------------->', response.data); + // logger.debug('<--------------- Sucessfully ran Code --------------->', response.data); return response.data.result; } } diff --git a/api/app/clients/tools/structured/DALLE3.js b/api/app/clients/tools/structured/DALLE3.js index d868190d0a2..17d0368f395 100644 --- a/api/app/clients/tools/structured/DALLE3.js +++ b/api/app/clients/tools/structured/DALLE3.js @@ -4,15 +4,25 @@ const fs = require('fs'); const path = require('path'); const { z } = require('zod'); const OpenAI = require('openai'); +const { v4: uuidv4 } = require('uuid'); const { Tool } = require('langchain/tools'); const { HttpsProxyAgent } = require('https-proxy-agent'); +const { + saveImageToFirebaseStorage, + getFirebaseStorageImageUrl, + getFirebaseStorage, +} = require('~/server/services/Files/Firebase'); +const { getImageBasename } = require('~/server/services/Files/images'); +const extractBaseURL = require('~/utils/extractBaseURL'); const saveImageFromUrl = require('../saveImageFromUrl'); -const extractBaseURL = require('../../../../utils/extractBaseURL'); +const { logger } = require('~/config'); + const { DALLE3_SYSTEM_PROMPT, DALLE_REVERSE_PROXY, PROXY } = process.env; class DALLE3 extends Tool { constructor(fields = {}) { super(); + this.userId = fields.userId; let apiKey = fields.DALLE_API_KEY || this.getApiKey(); const config = { apiKey }; if (DALLE_REVERSE_PROXY) { @@ -106,12 +116,12 @@ class DALLE3 extends Tool { n: 1, }); } catch (error) { - return `Something went wrong when trying to generate the image. The DALL-E API may unavailable: + return `Something went wrong when trying to generate the image. The DALL-E API may be unavailable: Error Message: ${error.message}`; } if (!resp) { - return 'Something went wrong when trying to generate the image. The DALL-E API may unavailable'; + return 'Something went wrong when trying to generate the image. The DALL-E API may be unavailable'; } const theImageUrl = resp.data[0].url; @@ -120,15 +130,17 @@ Error Message: ${error.message}`; return 'No image URL returned from OpenAI API. There may be a problem with the API or your configuration.'; } - const regex = /img-[\w\d]+.png/; - const match = theImageUrl.match(regex); - let imageName = '1.png'; + const imageBasename = getImageBasename(theImageUrl); + let imageName = `image_${uuidv4()}.png`; - if (match) { - imageName = match[0]; - console.log(imageName); // Output: img-lgCf7ppcbhqQrz6a5ear6FOb.png + if (imageBasename) { + imageName = imageBasename; + logger.debug('[DALL-E-3]', { imageName }); // Output: img-lgCf7ppcbhqQrz6a5ear6FOb.png } else { - console.log('No image name found in the string.'); + logger.debug('[DALL-E-3] No image name found in the string.', { + theImageUrl, + data: resp.data[0], + }); } this.outputPath = path.resolve( @@ -141,6 +153,7 @@ Error Message: ${error.message}`; 'client', 'public', 'images', + this.userId, ); const appRoot = path.resolve(__dirname, '..', '..', '..', '..', '..', 'client'); this.relativeImageUrl = path.relative(appRoot, this.outputPath); @@ -149,13 +162,24 @@ Error Message: ${error.message}`; if (!fs.existsSync(this.outputPath)) { fs.mkdirSync(this.outputPath, { recursive: true }); } - - try { - await saveImageFromUrl(theImageUrl, this.outputPath, imageName); - this.result = this.getMarkdownImageUrl(imageName); - } catch (error) { - console.error('Error while saving the image:', error); - this.result = theImageUrl; + const storage = getFirebaseStorage(); + if (storage) { + try { + await saveImageToFirebaseStorage(this.userId, theImageUrl, imageName); + this.result = await getFirebaseStorageImageUrl(`${this.userId}/${imageName}`); + logger.debug('[DALL-E-3] result: ' + this.result); + } catch (error) { + logger.error('Error while saving the image to Firebase Storage:', error); + this.result = `Failed to save the image to Firebase Storage. ${error.message}`; + } + } else { + try { + await saveImageFromUrl(theImageUrl, this.outputPath, imageName); + this.result = this.getMarkdownImageUrl(imageName); + } catch (error) { + logger.error('Error while saving the image locally:', error); + this.result = `Failed to save the image locally. ${error.message}`; + } } return this.result; diff --git a/api/app/clients/tools/structured/E2BTools.js b/api/app/clients/tools/structured/E2BTools.js index fc5fd6032f2..7e6148008c4 100644 --- a/api/app/clients/tools/structured/E2BTools.js +++ b/api/app/clients/tools/structured/E2BTools.js @@ -1,9 +1,10 @@ +const { z } = require('zod'); +const axios = require('axios'); const { StructuredTool } = require('langchain/tools'); const { PromptTemplate } = require('langchain/prompts'); -const { createExtractionChainFromZod } = require('./extractionChain'); // const { ChatOpenAI } = require('langchain/chat_models/openai'); -const axios = require('axios'); -const { z } = require('zod'); +const { createExtractionChainFromZod } = require('./extractionChain'); +const { logger } = require('~/config'); const envs = ['Nodejs', 'Go', 'Bash', 'Rust', 'Python3', 'PHP', 'Java', 'Perl', 'DotNET']; const env = z.enum(envs); @@ -34,8 +35,8 @@ async function extractEnvFromCode(code, model) { // const chatModel = new ChatOpenAI({ openAIApiKey, modelName: 'gpt-4-0613', temperature: 0 }); const chain = createExtractionChainFromZod(zodSchema, model, { prompt, verbose: true }); const result = await chain.run(code); - console.log('<--------------- extractEnvFromCode --------------->'); - console.log(result); + logger.debug('<--------------- extractEnvFromCode --------------->'); + logger.debug(result); return result.env; } @@ -69,7 +70,7 @@ class RunCommand extends StructuredTool { } async _call(data) { - console.log(`<--------------- Running ${data} --------------->`); + logger.debug(`<--------------- Running ${data} --------------->`); const response = await axios({ url: `${this.url}/commands`, method: 'post', @@ -96,7 +97,7 @@ class ReadFile extends StructuredTool { } async _call(data) { - console.log(`<--------------- Reading ${data} --------------->`); + logger.debug(`<--------------- Reading ${data} --------------->`); const response = await axios.get(`${this.url}/files`, { params: data, headers: this.headers }); return response.data; } @@ -121,12 +122,12 @@ class WriteFile extends StructuredTool { async _call(data) { let { env, path, content } = data; - console.log(`<--------------- environment ${env} typeof ${typeof env}--------------->`); + logger.debug(`<--------------- environment ${env} typeof ${typeof env}--------------->`); if (env && !envs.includes(env)) { - console.log(`<--------------- Invalid environment ${env} --------------->`); + logger.debug(`<--------------- Invalid environment ${env} --------------->`); env = await extractEnvFromCode(content, this.model); } else if (!env) { - console.log('<--------------- Undefined environment --------------->'); + logger.debug('<--------------- Undefined environment --------------->'); env = await extractEnvFromCode(content, this.model); } @@ -139,7 +140,7 @@ class WriteFile extends StructuredTool { content, }, }; - console.log('Writing to file', JSON.stringify(payload)); + logger.debug('Writing to file', JSON.stringify(payload)); await axios({ url: `${this.url}/files`, diff --git a/api/app/clients/tools/structured/StableDiffusion.js b/api/app/clients/tools/structured/StableDiffusion.js index c4c32cd3c0f..1fc5096730e 100644 --- a/api/app/clients/tools/structured/StableDiffusion.js +++ b/api/app/clients/tools/structured/StableDiffusion.js @@ -1,10 +1,11 @@ // Generates image using stable diffusion webui's api (automatic1111) const fs = require('fs'); -const { StructuredTool } = require('langchain/tools'); const { z } = require('zod'); const path = require('path'); const axios = require('axios'); const sharp = require('sharp'); +const { StructuredTool } = require('langchain/tools'); +const { logger } = require('~/config'); class StableDiffusionAPI extends StructuredTool { constructor(fields) { @@ -107,7 +108,7 @@ class StableDiffusionAPI extends StructuredTool { .toFile(this.outputPath + '/' + imageName); this.result = this.getMarkdownImageUrl(imageName); } catch (error) { - console.error('Error while saving the image:', error); + logger.error('[StableDiffusion] Error while saving the image:', error); // this.result = theImageUrl; } diff --git a/api/app/clients/tools/structured/Wolfram.js b/api/app/clients/tools/structured/Wolfram.js index dadd2048ae7..2c5c6e023a1 100644 --- a/api/app/clients/tools/structured/Wolfram.js +++ b/api/app/clients/tools/structured/Wolfram.js @@ -1,7 +1,8 @@ /* eslint-disable no-useless-escape */ const axios = require('axios'); -const { StructuredTool } = require('langchain/tools'); const { z } = require('zod'); +const { StructuredTool } = require('langchain/tools'); +const { logger } = require('~/config'); class WolframAlphaAPI extends StructuredTool { constructor(fields) { @@ -47,7 +48,7 @@ class WolframAlphaAPI extends StructuredTool { const response = await axios.get(url, { responseType: 'text' }); return response.data; } catch (error) { - console.error(`Error fetching raw text: ${error}`); + logger.error('[WolframAlphaAPI] Error fetching raw text:', error); throw error; } } @@ -78,11 +79,10 @@ class WolframAlphaAPI extends StructuredTool { return response; } catch (error) { if (error.response && error.response.data) { - console.log('Error data:', error.response.data); + logger.error('[WolframAlphaAPI] Error data:', error); return error.response.data; } else { - console.log('Error querying Wolfram Alpha', error.message); - // throw error; + logger.error('[WolframAlphaAPI] Error querying Wolfram Alpha', error); return 'There was an error querying Wolfram Alpha.'; } } diff --git a/api/app/clients/tools/structured/specs/DALLE3.spec.js b/api/app/clients/tools/structured/specs/DALLE3.spec.js index 0958886df7b..34fa3ebf00a 100644 --- a/api/app/clients/tools/structured/specs/DALLE3.spec.js +++ b/api/app/clients/tools/structured/specs/DALLE3.spec.js @@ -2,10 +2,40 @@ const fs = require('fs'); const path = require('path'); const OpenAI = require('openai'); const DALLE3 = require('../DALLE3'); +const { + getFirebaseStorage, + saveImageToFirebaseStorage, +} = require('~/server/services/Files/Firebase'); const saveImageFromUrl = require('../../saveImageFromUrl'); +const { logger } = require('~/config'); jest.mock('openai'); +jest.mock('~/server/services/Files/Firebase', () => ({ + getFirebaseStorage: jest.fn(), + saveImageToFirebaseStorage: jest.fn(), + getFirebaseStorageImageUrl: jest.fn(), +})); + +jest.mock('~/server/services/Files/images', () => ({ + getImageBasename: jest.fn().mockImplementation((url) => { + // Split the URL by '/' + const parts = url.split('/'); + + // Get the last part of the URL + const lastPart = parts.pop(); + + // Check if the last part of the URL matches the image extension regex + const imageExtensionRegex = /\.(jpg|jpeg|png|gif|bmp|tiff|svg)$/i; + if (imageExtensionRegex.test(lastPart)) { + return lastPart; + } + + // If the regex test fails, return an empty string + return ''; + }), +})); + const generate = jest.fn(); OpenAI.mockImplementation(() => ({ images: { @@ -145,10 +175,13 @@ describe('DALLE3', () => { }, ], }; - console.log = jest.fn(); // Mock console.log + generate.mockResolvedValue(mockResponse); await dalle._call(mockData); - expect(console.log).toHaveBeenCalledWith('No image name found in the string.'); + expect(logger.debug).toHaveBeenCalledWith('[DALL-E-3] No image name found in the string.', { + data: { url: 'http://example.com/invalid-url' }, + theImageUrl: 'http://example.com/invalid-url', + }); }); it('should create the directory if it does not exist', async () => { @@ -182,9 +215,49 @@ describe('DALLE3', () => { const error = new Error('Error while saving the image'); generate.mockResolvedValue(mockResponse); saveImageFromUrl.mockRejectedValue(error); - console.error = jest.fn(); // Mock console.error const result = await dalle._call(mockData); - expect(console.error).toHaveBeenCalledWith('Error while saving the image:', error); - expect(result).toBe(mockResponse.data[0].url); + expect(logger.error).toHaveBeenCalledWith('Error while saving the image locally:', error); + expect(result).toBe('Failed to save the image locally. Error while saving the image'); + }); + + it('should save image to Firebase Storage if Firebase is initialized', async () => { + const mockData = { + prompt: 'A test prompt', + }; + const mockImageUrl = 'http://example.com/img-test.png'; + const mockResponse = { data: [{ url: mockImageUrl }] }; + generate.mockResolvedValue(mockResponse); + getFirebaseStorage.mockReturnValue({}); // Simulate Firebase being initialized + + await dalle._call(mockData); + + expect(getFirebaseStorage).toHaveBeenCalled(); + expect(saveImageToFirebaseStorage).toHaveBeenCalledWith( + undefined, + mockImageUrl, + expect.any(String), + ); + }); + + it('should handle error when saving image to Firebase Storage fails', async () => { + const mockData = { + prompt: 'A test prompt', + }; + const mockImageUrl = 'http://example.com/img-test.png'; + const mockResponse = { data: [{ url: mockImageUrl }] }; + const error = new Error('Error while saving to Firebase'); + generate.mockResolvedValue(mockResponse); + getFirebaseStorage.mockReturnValue({}); // Simulate Firebase being initialized + saveImageToFirebaseStorage.mockRejectedValue(error); + + const result = await dalle._call(mockData); + + expect(logger.error).toHaveBeenCalledWith( + 'Error while saving the image to Firebase Storage:', + error, + ); + expect(result).toBe( + 'Failed to save the image to Firebase Storage. Error while saving to Firebase', + ); }); }); diff --git a/api/app/clients/tools/util/handleOpenAIErrors.js b/api/app/clients/tools/util/handleOpenAIErrors.js index b5a31f7f405..53a4f37acef 100644 --- a/api/app/clients/tools/util/handleOpenAIErrors.js +++ b/api/app/clients/tools/util/handleOpenAIErrors.js @@ -1,4 +1,5 @@ const OpenAI = require('openai'); +const { logger } = require('~/config'); /** * Handles errors that may occur when making requests to OpenAI's API. @@ -12,14 +13,14 @@ const OpenAI = require('openai'); */ async function handleOpenAIErrors(err, errorCallback, context = 'stream') { if (err instanceof OpenAI.APIError && err?.message?.includes('abort')) { - console.warn(`[OpenAIClient.chatCompletion][${context}] Aborted Message`); + logger.warn(`[OpenAIClient.chatCompletion][${context}] Aborted Message`); } if (err instanceof OpenAI.OpenAIError && err?.message?.includes('missing finish_reason')) { - console.warn(`[OpenAIClient.chatCompletion][${context}] Missing finish_reason`); + logger.warn(`[OpenAIClient.chatCompletion][${context}] Missing finish_reason`); } else if (err instanceof OpenAI.APIError) { - console.warn(`[OpenAIClient.chatCompletion][${context}] API Error`); + logger.warn(`[OpenAIClient.chatCompletion][${context}] API error`); } else { - console.warn(`[OpenAIClient.chatCompletion][${context}] Unhandled error type`); + logger.warn(`[OpenAIClient.chatCompletion][${context}] Unhandled error type`); } if (errorCallback) { diff --git a/api/app/clients/tools/util/handleTools.js b/api/app/clients/tools/util/handleTools.js index 6109e2b9c26..352dd5dec74 100644 --- a/api/app/clients/tools/util/handleTools.js +++ b/api/app/clients/tools/util/handleTools.js @@ -1,17 +1,14 @@ -const { getUserPluginAuthValue } = require('../../../../server/services/PluginService'); -const { OpenAIEmbeddings } = require('langchain/embeddings/openai'); const { ZapierToolKit } = require('langchain/agents'); -const { SerpAPI, ZapierNLAWrapper } = require('langchain/tools'); -const { ChatOpenAI } = require('langchain/chat_models/openai'); const { Calculator } = require('langchain/tools/calculator'); const { WebBrowser } = require('langchain/tools/webbrowser'); +const { SerpAPI, ZapierNLAWrapper } = require('langchain/tools'); +const { OpenAIEmbeddings } = require('langchain/embeddings/openai'); +const { getUserPluginAuthValue } = require('~/server/services/PluginService'); const { availableTools, - AIPluginTool, GoogleSearchAPI, WolframAlphaAPI, StructuredWolfram, - HttpRequestTool, OpenAICreateImage, StableDiffusionAPI, DALLE3, @@ -23,8 +20,9 @@ const { CodeSherpaTools, CodeBrew, } = require('../'); -const { loadSpecs } = require('./loadSpecs'); const { loadToolSuite } = require('./loadToolSuite'); +const { loadSpecs } = require('./loadSpecs'); +const { logger } = require('~/config'); const getOpenAIKey = async (options, user) => { let openAIApiKey = options.openAIApiKey ?? process.env.OPENAI_API_KEY; @@ -64,24 +62,24 @@ const validateTools = async (user, tools = []) => { return Array.from(validToolsSet.values()); } catch (err) { - console.log('There was a problem validating tools', err); + logger.error('[validateTools] There was a problem validating tools', err); throw new Error(err); } }; -const loadToolWithAuth = async (user, authFields, ToolConstructor, options = {}) => { +const loadToolWithAuth = async (userId, authFields, ToolConstructor, options = {}) => { return async function () { let authValues = {}; for (const authField of authFields) { let authValue = process.env[authField]; if (!authValue) { - authValue = await getUserPluginAuthValue(user, authField); + authValue = await getUserPluginAuthValue(userId, authField); } authValues[authField] = authValue; } - return new ToolConstructor({ ...options, ...authValues }); + return new ToolConstructor({ ...options, ...authValues, userId }); }; }; @@ -161,15 +159,6 @@ const loadTools = async ({ const zapier = new ZapierNLAWrapper({ apiKey }); return ZapierToolKit.fromZapierNLAWrapper(zapier); }, - plugins: async () => { - return [ - new HttpRequestTool(), - await AIPluginTool.fromPluginUrl( - 'https://www.klarna.com/.well-known/ai-plugin.json', - new ChatOpenAI({ openAIApiKey: options.openAIApiKey, temperature: 0 }), - ), - ]; - }, }; const requestedTools = {}; diff --git a/api/app/clients/tools/util/loadSpecs.js b/api/app/clients/tools/util/loadSpecs.js index da787c60949..e5b543132ac 100644 --- a/api/app/clients/tools/util/loadSpecs.js +++ b/api/app/clients/tools/util/loadSpecs.js @@ -1,7 +1,8 @@ const fs = require('fs'); const path = require('path'); const { z } = require('zod'); -const { createOpenAPIPlugin } = require('../dynamic/OpenAPIPlugin'); +const { logger } = require('~/config'); +const { createOpenAPIPlugin } = require('~/app/clients/tools/dynamic/OpenAPIPlugin'); // The minimum Manifest definition const ManifestDefinition = z.object({ @@ -26,28 +27,17 @@ const ManifestDefinition = z.object({ legal_info_url: z.string().optional(), }); -function validateJson(json, verbose = true) { +function validateJson(json) { try { return ManifestDefinition.parse(json); } catch (error) { - if (verbose) { - console.debug('validateJson error', error); - } + logger.debug('[validateJson] manifest parsing error', error); return false; } } // omit the LLM to return the well known jsons as objects -async function loadSpecs({ - llm, - user, - message, - tools = [], - map = false, - memory, - signal, - verbose = false, -}) { +async function loadSpecs({ llm, user, message, tools = [], map = false, memory, signal }) { const directoryPath = path.join(__dirname, '..', '.well-known'); let files = []; @@ -60,7 +50,7 @@ async function loadSpecs({ await fs.promises.access(filePath, fs.constants.F_OK); files.push(tools[i] + '.json'); } catch (err) { - console.error(`File ${tools[i] + '.json'} does not exist`); + logger.error(`[loadSpecs] File ${tools[i] + '.json'} does not exist`, err); } } @@ -73,9 +63,7 @@ async function loadSpecs({ const validJsons = []; const constructorMap = {}; - if (verbose) { - console.debug('files', files); - } + logger.debug('[validateJson] files', files); for (const file of files) { if (path.extname(file) === '.json') { @@ -84,7 +72,7 @@ async function loadSpecs({ const json = JSON.parse(fileContent); if (!validateJson(json)) { - verbose && console.debug('Invalid json', json); + logger.debug('[validateJson] Invalid json', json); continue; } @@ -97,13 +85,12 @@ async function loadSpecs({ memory, signal, user, - verbose, }); continue; } if (llm) { - validJsons.push(createOpenAPIPlugin({ data: json, llm, verbose })); + validJsons.push(createOpenAPIPlugin({ data: json, llm })); continue; } @@ -117,10 +104,8 @@ async function loadSpecs({ const plugins = (await Promise.all(validJsons)).filter((plugin) => plugin); - // if (verbose) { - // console.debug('plugins', plugins); - // console.debug(plugins[0].name); - // } + // logger.debug('[validateJson] plugins', plugins); + // logger.debug(plugins[0].name); return plugins; } diff --git a/api/app/titleConvoBing.js b/api/app/titleConvoBing.js index 8dd32160d73..7c5c7e2c611 100644 --- a/api/app/titleConvoBing.js +++ b/api/app/titleConvoBing.js @@ -1,5 +1,6 @@ -const { isEnabled } = require('../server/utils'); const throttle = require('lodash/throttle'); +const { isEnabled } = require('~/server/utils'); +const { logger } = require('~/config'); const titleConvo = async ({ text, response }) => { let title = 'New Chat'; @@ -30,11 +31,10 @@ const titleConvo = async ({ text, response }) => { const res = await titleGenerator.sendMessage(titlePrompt, options); title = res.response.replace(/Title: /, '').replace(/[".]/g, ''); } catch (e) { - console.error(e); - console.log('There was an issue generating title, see error above'); + logger.error('There was an issue generating title with BingAI', e); } - console.log('CONVERSATION TITLE', title); + logger.debug('[/ask/bingAI] CONVERSATION TITLE: ' + title); return title; }; diff --git a/api/cache/banViolation.js b/api/cache/banViolation.js index e0ae26319e3..3d67e578722 100644 --- a/api/cache/banViolation.js +++ b/api/cache/banViolation.js @@ -1,6 +1,8 @@ -const Session = require('../models/Session'); +const Session = require('~/models/Session'); const getLogStores = require('./getLogStores'); -const { isEnabled, math, removePorts } = require('../server/utils'); +const { isEnabled, math, removePorts } = require('~/server/utils'); +const { logger } = require('~/config'); + const { BAN_VIOLATIONS, BAN_INTERVAL } = process.env ?? {}; const interval = math(BAN_INTERVAL, 20); @@ -54,7 +56,7 @@ const banViolation = async (req, res, errorMessage) => { } req.ip = removePorts(req); - console.log( + logger.info( `[BAN] Banning user ${user_id} ${req.ip ? `@ ${req.ip} ` : ''}for ${ duration / 1000 / 60 } minutes`, diff --git a/api/cache/keyvMongo.js b/api/cache/keyvMongo.js index 429329adc63..8f5b9fd8d80 100644 --- a/api/cache/keyvMongo.js +++ b/api/cache/keyvMongo.js @@ -1,7 +1,9 @@ const KeyvMongo = require('@keyv/mongo'); +const { logger } = require('~/config'); + const { MONGO_URI } = process.env ?? {}; const keyvMongo = new KeyvMongo(MONGO_URI, { collection: 'logs' }); -keyvMongo.on('error', (err) => console.error('KeyvMongo connection error:', err)); +keyvMongo.on('error', (err) => logger.error('KeyvMongo connection error:', err)); module.exports = keyvMongo; diff --git a/api/cache/keyvRedis.js b/api/cache/keyvRedis.js index 942b1b239fa..f723429ee27 100644 --- a/api/cache/keyvRedis.js +++ b/api/cache/keyvRedis.js @@ -1,14 +1,19 @@ const KeyvRedis = require('@keyv/redis'); +const { logger } = require('~/config'); +const { isEnabled } = require('~/server/utils'); -const { REDIS_URI } = process.env; +const { REDIS_URI, USE_REDIS } = process.env; let keyvRedis; -if (REDIS_URI) { +if (REDIS_URI && isEnabled(USE_REDIS)) { keyvRedis = new KeyvRedis(REDIS_URI, { useRedisSets: false }); - keyvRedis.on('error', (err) => console.error('KeyvRedis connection error:', err)); + keyvRedis.on('error', (err) => logger.error('KeyvRedis connection error:', err)); + keyvRedis.setMaxListeners(20); } else { - // console.log('REDIS_URI not provided. Redis module will not be initialized.'); + logger.info( + '`REDIS_URI` not provided, or `USE_REDIS` not set. Redis module will not be initialized.', + ); } module.exports = keyvRedis; diff --git a/api/common/enums.js b/api/common/enums.js index dc1c757b2f1..849ae43f59c 100644 --- a/api/common/enums.js +++ b/api/common/enums.js @@ -1,12 +1,14 @@ /** * @typedef {Object} CacheKeys * @property {'config'} CONFIG - Key for the config cache. + * @property {'plugins'} PLUGINS - Key for the plugins cache. * @property {'modelsConfig'} MODELS_CONFIG - Key for the model config cache. * @property {'defaultConfig'} DEFAULT_CONFIG - Key for the default config cache. * @property {'overrideConfig'} OVERRIDE_CONFIG - Key for the override config cache. */ const CacheKeys = { CONFIG: 'config', + PLUGINS: 'plugins', MODELS_CONFIG: 'modelsConfig', DEFAULT_CONFIG: 'defaultConfig', OVERRIDE_CONFIG: 'overrideConfig', diff --git a/api/config.js b/api/config.js deleted file mode 100644 index a17b607490b..00000000000 --- a/api/config.js +++ /dev/null @@ -1,6 +0,0 @@ -const path = require('path'); - -module.exports = { - publicPath: path.resolve(__dirname, '..', 'client', 'public'), - imageOutput: path.resolve(__dirname, '..', 'client', 'public', 'images'), -}; diff --git a/api/config/index.js b/api/config/index.js new file mode 100644 index 00000000000..3198ff2fb21 --- /dev/null +++ b/api/config/index.js @@ -0,0 +1,5 @@ +const logger = require('./winston'); + +module.exports = { + logger, +}; diff --git a/api/config/meiliLogger.js b/api/config/meiliLogger.js new file mode 100644 index 00000000000..195b387ae56 --- /dev/null +++ b/api/config/meiliLogger.js @@ -0,0 +1,78 @@ +const path = require('path'); +const winston = require('winston'); +require('winston-daily-rotate-file'); + +const logDir = path.join(__dirname, '..', 'logs'); + +const { NODE_ENV } = process.env; + +const levels = { + error: 0, + warn: 1, + info: 2, + http: 3, + verbose: 4, + debug: 5, + activity: 6, + silly: 7, +}; + +winston.addColors({ + info: 'green', // fontStyle color + warn: 'italic yellow', + error: 'red', + debug: 'blue', +}); + +const level = () => { + const env = NODE_ENV || 'development'; + const isDevelopment = env === 'development'; + return isDevelopment ? 'debug' : 'warn'; +}; + +const fileFormat = winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.splat(), +); + +const transports = [ + new winston.transports.DailyRotateFile({ + level: 'debug', + filename: `${logDir}/meiliSync-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', + format: fileFormat, + }), +]; + +// if (NODE_ENV !== 'production') { +// transports.push( +// new winston.transports.Console({ +// format: winston.format.combine(winston.format.colorize(), winston.format.simple()), +// }), +// ); +// } + +const consoleFormat = winston.format.combine( + winston.format.colorize({ all: true }), + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`), +); + +transports.push( + new winston.transports.Console({ + level: 'info', + format: consoleFormat, + }), +); + +const logger = winston.createLogger({ + level: level(), + levels, + transports, +}); + +module.exports = logger; diff --git a/api/config/parsers.js b/api/config/parsers.js new file mode 100644 index 00000000000..59685eab0bf --- /dev/null +++ b/api/config/parsers.js @@ -0,0 +1,182 @@ +const { klona } = require('klona'); +const winston = require('winston'); +const traverse = require('traverse'); + +const SPLAT_SYMBOL = Symbol.for('splat'); +const MESSAGE_SYMBOL = Symbol.for('message'); + +const sensitiveKeys = [ + /^(sk-)[^\s]+/, // OpenAI API key pattern + /(Bearer )[^\s]+/, // Header: Bearer token pattern + /(api-key:? )[^\s]+/, // Header: API key pattern + /(key=)[^\s]+/, // URL query param: sensitive key pattern (Google) +]; + +/** + * Determines if a given value string is sensitive and returns matching regex patterns. + * + * @param {string} valueStr - The value string to check. + * @returns {Array} An array of regex patterns that match the value string. + */ +function getMatchingSensitivePatterns(valueStr) { + if (valueStr) { + // Filter and return all regex patterns that match the value string + return sensitiveKeys.filter((regex) => regex.test(valueStr)); + } + return []; +} + +/** + * Redacts sensitive information from a console message. + * + * @param {string} str - The console message to be redacted. + * @returns {string} - The redacted console message. + */ +function redactMessage(str) { + const patterns = getMatchingSensitivePatterns(str); + + if (patterns.length === 0) { + return str; + } + + patterns.forEach((pattern) => { + str = str.replace(pattern, '$1[REDACTED]'); + }); + + return str; +} + +/** + * Redacts sensitive information from log messages if the log level is 'error'. + * Note: Intentionally mutates the object. + * @param {Object} info - The log information object. + * @returns {Object} - The modified log information object. + */ +const redactFormat = winston.format((info) => { + if (info.level === 'error') { + info.message = redactMessage(info.message); + if (info[MESSAGE_SYMBOL]) { + info[MESSAGE_SYMBOL] = redactMessage(info[MESSAGE_SYMBOL]); + } + } + return info; +}); + +/** + * Truncates long strings, especially base64 image data, within log messages. + * + * @param {any} value - The value to be inspected and potentially truncated. + * @param {number} [length] - The length at which to truncate the value. Default: 100. + * @returns {any} - The truncated or original value. + */ +const truncateLongStrings = (value, length = 100) => { + if (typeof value === 'string') { + return value.length > length ? value.substring(0, length) + '... [truncated]' : value; + } + + return value; +}; + +/** + * An array mapping function that truncates long strings (objects converted to JSON strings). + * @param {any} item - The item to be condensed. + * @returns {any} - The condensed item. + */ +const condenseArray = (item) => { + if (typeof item === 'string') { + return truncateLongStrings(JSON.stringify(item)); + } else if (typeof item === 'object') { + return truncateLongStrings(JSON.stringify(item)); + } + return item; +}; + +/** + * Formats log messages for debugging purposes. + * - Truncates long strings within log messages. + * - Condenses arrays by truncating long strings and objects as strings within array items. + * - Redacts sensitive information from log messages if the log level is 'error'. + * - Converts log information object to a formatted string. + * + * @param {Object} options - The options for formatting log messages. + * @param {string} options.level - The log level. + * @param {string} options.message - The log message. + * @param {string} options.timestamp - The timestamp of the log message. + * @param {Object} options.metadata - Additional metadata associated with the log message. + * @returns {string} - The formatted log message. + */ +const debugTraverse = winston.format.printf(({ level, message, timestamp, ...metadata }) => { + let msg = `${timestamp} ${level}: ${truncateLongStrings(message?.trim(), 150)}`; + try { + if (level !== 'debug') { + return msg; + } + + if (!metadata) { + return msg; + } + + const debugValue = metadata[SPLAT_SYMBOL]?.[0]; + + if (!debugValue) { + return msg; + } + + if (debugValue && Array.isArray(debugValue)) { + msg += `\n${JSON.stringify(debugValue.map(condenseArray))}`; + return msg; + } + + if (typeof debugValue !== 'object') { + return (msg += ` ${debugValue}`); + } + + msg += '\n{'; + + const copy = klona(metadata); + traverse(copy).forEach(function (value) { + if (typeof this?.key === 'symbol') { + return; + } + + let _parentKey = ''; + const parent = this.parent; + + if (typeof parent?.key !== 'symbol' && parent?.key) { + _parentKey = parent.key; + } + + const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`; + + const tabs = `${parent && parent.notRoot ? ' ' : ' '}`; + + const currentKey = this?.key ?? 'unknown'; + + if (this.isLeaf && typeof value === 'string') { + const truncatedText = truncateLongStrings(value); + msg += `\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`; + } else if (this.notLeaf && Array.isArray(value) && value.length > 0) { + const currentMessage = `\n${tabs}// ${value.length} ${currentKey.replace(/s$/, '')}(s)`; + this.update(currentMessage, true); + msg += currentMessage; + const stringifiedArray = value.map(condenseArray); + msg += `\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`; + } else if (this.isLeaf && typeof value === 'function') { + msg += `\n${tabs}${parentKey}${currentKey}: function,`; + } else if (this.isLeaf) { + msg += `\n${tabs}${parentKey}${currentKey}: ${value},`; + } + }); + + msg += '\n}'; + return msg; + } catch (e) { + return (msg += `\n[LOGGER PARSING ERROR] ${e.message}`); + } +}); + +module.exports = { + redactFormat, + redactMessage, + debugTraverse, +}; diff --git a/api/config/paths.js b/api/config/paths.js new file mode 100644 index 00000000000..2f577a183fd --- /dev/null +++ b/api/config/paths.js @@ -0,0 +1,6 @@ +const path = require('path'); + +module.exports = { + publicPath: path.resolve(__dirname, '..', '..', 'client', 'public'), + imageOutput: path.resolve(__dirname, '..', '..', 'client', 'public', 'images'), +}; diff --git a/api/config/winston.js b/api/config/winston.js new file mode 100644 index 00000000000..6cba153f163 --- /dev/null +++ b/api/config/winston.js @@ -0,0 +1,127 @@ +const path = require('path'); +const winston = require('winston'); +require('winston-daily-rotate-file'); +const { redactFormat, redactMessage, debugTraverse } = require('./parsers'); + +const logDir = path.join(__dirname, '..', 'logs'); + +const { NODE_ENV, DEBUG_LOGGING = true, DEBUG_CONSOLE = false } = process.env; + +const levels = { + error: 0, + warn: 1, + info: 2, + http: 3, + verbose: 4, + debug: 5, + activity: 6, + silly: 7, +}; + +winston.addColors({ + info: 'green', // fontStyle color + warn: 'italic yellow', + error: 'red', + debug: 'blue', +}); + +const level = () => { + const env = NODE_ENV || 'development'; + const isDevelopment = env === 'development'; + return isDevelopment ? 'debug' : 'warn'; +}; + +const fileFormat = winston.format.combine( + redactFormat(), + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.splat(), + // redactErrors(), +); + +const transports = [ + new winston.transports.DailyRotateFile({ + level: 'error', + filename: `${logDir}/error-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', + format: fileFormat, + }), + // new winston.transports.DailyRotateFile({ + // level: 'info', + // filename: `${logDir}/info-%DATE%.log`, + // datePattern: 'YYYY-MM-DD', + // zippedArchive: true, + // maxSize: '20m', + // maxFiles: '14d', + // }), +]; + +// if (NODE_ENV !== 'production') { +// transports.push( +// new winston.transports.Console({ +// format: winston.format.combine(winston.format.colorize(), winston.format.simple()), +// }), +// ); +// } + +if ( + (typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING?.toLowerCase() === 'true') || + DEBUG_LOGGING === true +) { + transports.push( + new winston.transports.DailyRotateFile({ + level: 'debug', + filename: `${logDir}/debug-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', + format: winston.format.combine(fileFormat, debugTraverse), + }), + ); +} + +const consoleFormat = winston.format.combine( + redactFormat(), + winston.format.colorize({ all: true }), + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + // redactErrors(), + winston.format.printf((info) => { + const message = `${info.timestamp} ${info.level}: ${info.message}`; + if (info.level.includes('error')) { + return redactMessage(message); + } + + return message; + }), +); + +if ( + (typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE?.toLowerCase() === 'true') || + DEBUG_CONSOLE === true +) { + transports.push( + new winston.transports.Console({ + level: 'debug', + format: winston.format.combine(consoleFormat, debugTraverse), + }), + ); +} else { + transports.push( + new winston.transports.Console({ + level: 'info', + format: consoleFormat, + }), + ); +} + +const logger = winston.createLogger({ + level: level(), + levels, + transports, +}); + +module.exports = logger; diff --git a/api/jest.config.js b/api/jest.config.js index 17ca55fa2a9..ec44bd7f56a 100644 --- a/api/jest.config.js +++ b/api/jest.config.js @@ -3,8 +3,14 @@ module.exports = { clearMocks: true, roots: [''], coverageDirectory: 'coverage', - setupFiles: ['./test/jestSetup.js', './test/__mocks__/KeyvMongo.js'], + setupFiles: [ + './test/jestSetup.js', + './test/__mocks__/KeyvMongo.js', + './test/__mocks__/logger.js', + './test/__mocks__/fetchEventSource.js', + ], moduleNameMapper: { '~/(.*)': '/$1', + '~/data/auth.json': '/__mocks__/auth.mock.json', }, }; diff --git a/api/lib/db/indexSync.js b/api/lib/db/indexSync.js index d753635499d..53ac3d3a266 100644 --- a/api/lib/db/indexSync.js +++ b/api/lib/db/indexSync.js @@ -1,8 +1,10 @@ -const Conversation = require('../../models/schema/convoSchema'); -const Message = require('../../models/schema/messageSchema'); const { MeiliSearch } = require('meilisearch'); -let currentTimeout = null; +const Message = require('~/models/schema/messageSchema'); +const Conversation = require('~/models/schema/convoSchema'); +const { logger } = require('~/config'); + const searchEnabled = process.env?.SEARCH?.toLowerCase() === 'true'; +let currentTimeout = null; // eslint-disable-next-line no-unused-vars async function indexSync(req, res, next) { @@ -21,7 +23,7 @@ async function indexSync(req, res, next) { }); const { status } = await client.health(); - // console.log(`Meilisearch: ${status}`); + // logger.debug(`[indexSync] Meilisearch: ${status}`); const result = status === 'available' && !!process.env.SEARCH; if (!result) { @@ -35,39 +37,45 @@ async function indexSync(req, res, next) { const messagesIndexed = messages.numberOfDocuments; const convosIndexed = convos.numberOfDocuments; - console.log(`There are ${messageCount} messages in the database, ${messagesIndexed} indexed`); - console.log(`There are ${convoCount} convos in the database, ${convosIndexed} indexed`); + logger.debug( + `[indexSync] There are ${messageCount} messages in the database, ${messagesIndexed} indexed`, + ); + logger.debug( + `[indexSync] There are ${convoCount} convos in the database, ${convosIndexed} indexed`, + ); if (messageCount !== messagesIndexed) { - console.log('Messages out of sync, indexing'); + logger.debug('[indexSync] Messages out of sync, indexing'); Message.syncWithMeili(); } if (convoCount !== convosIndexed) { - console.log('Convos out of sync, indexing'); + logger.debug('[indexSync] Convos out of sync, indexing'); Conversation.syncWithMeili(); } } catch (err) { - // console.log('in index sync'); + // logger.debug('[indexSync] in index sync'); if (err.message.includes('not found')) { - console.log('Creating indices...'); + logger.debug('[indexSync] Creating indices...'); currentTimeout = setTimeout(async () => { try { await Message.syncWithMeili(); await Conversation.syncWithMeili(); } catch (err) { - console.error('Trouble creating indices, try restarting the server.'); + logger.error('[indexSync] Trouble creating indices, try restarting the server.', err); } }, 750); + } else if (err.message.includes('Meilisearch not configured')) { + logger.info('[indexSync] Meilisearch not configured, search will be disabled.'); } else { - console.error(err); + logger.error('[indexSync] error', err); // res.status(500).json({ error: 'Server error' }); } } } process.on('exit', () => { - console.log('Clearing sync timeouts before exiting...'); + logger.debug('[indexSync] Clearing sync timeouts before exiting...'); clearTimeout(currentTimeout); }); diff --git a/api/models/Balance.js b/api/models/Balance.js index f0e6d73d1e4..45dec696304 100644 --- a/api/models/Balance.js +++ b/api/models/Balance.js @@ -1,6 +1,7 @@ const mongoose = require('mongoose'); const balanceSchema = require('./schema/balance'); const { getMultiplier } = require('./tx'); +const { logger } = require('~/config'); balanceSchema.statics.check = async function ({ user, @@ -9,25 +10,21 @@ balanceSchema.statics.check = async function ({ valueKey, tokenType, amount, - debug, }) { const multiplier = getMultiplier({ valueKey, tokenType, model, endpoint }); const tokenCost = amount * multiplier; const { tokenCredits: balance } = (await this.findOne({ user }, 'tokenCredits').lean()) ?? {}; - if (debug) { - console.log('balance check', { - user, - model, - endpoint, - valueKey, - tokenType, - amount, - debug, - balance, - multiplier, - }); - } + logger.debug('[Balance.check]', { + user, + model, + endpoint, + valueKey, + tokenType, + amount, + balance, + multiplier, + }); if (!balance) { return { @@ -37,9 +34,7 @@ balanceSchema.statics.check = async function ({ }; } - if (debug) { - console.log('balance check', { tokenCost }); - } + logger.debug('[Balance.check]', { tokenCost }); return { canSpend: balance >= tokenCost, balance, tokenCost }; }; diff --git a/api/models/Config.js b/api/models/Config.js index d9de9391465..fefb84b8f95 100644 --- a/api/models/Config.js +++ b/api/models/Config.js @@ -1,4 +1,6 @@ const mongoose = require('mongoose'); +const { logger } = require('~/config'); + const major = [0, 0]; const minor = [0, 0]; const patch = [0, 5]; @@ -69,7 +71,7 @@ module.exports = { try { return await Config.find(filter).lean(); } catch (error) { - console.error(error); + logger.error('Error getting configs', error); return { config: 'Error getting configs' }; } }, @@ -77,7 +79,7 @@ module.exports = { try { return await Config.deleteMany(filter); } catch (error) { - console.error(error); + logger.error('Error deleting configs', error); return { config: 'Error deleting configs' }; } }, diff --git a/api/models/Conversation.js b/api/models/Conversation.js index c946a28af6c..f1aa7bfe718 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -1,12 +1,12 @@ -// const { Conversation } = require('./plugins'); const Conversation = require('./schema/convoSchema'); const { getMessages, deleteMessages } = require('./Message'); +const logger = require('~/config/winston'); const getConvo = async (user, conversationId) => { try { return await Conversation.findOne({ user, conversationId }).lean(); } catch (error) { - console.log(error); + logger.error('[getConvo] Error getting single conversation', error); return { message: 'Error getting single conversation' }; } }; @@ -26,7 +26,7 @@ module.exports = { upsert: true, }); } catch (error) { - console.log(error); + logger.error('[saveConvo] Error saving conversation', error); return { message: 'Error saving conversation' }; } }, @@ -41,7 +41,7 @@ module.exports = { .lean(); return { conversations: convos, pages: totalPages, pageNumber, pageSize }; } catch (error) { - console.log(error); + logger.error('[getConvosByPage] Error getting conversations', error); return { message: 'Error getting conversations' }; } }, @@ -87,7 +87,7 @@ module.exports = { convoMap, }; } catch (error) { - console.log(error); + logger.error('[getConvosQueried] Error getting conversations', error); return { message: 'Error fetching conversations' }; } }, @@ -104,7 +104,7 @@ module.exports = { return convo?.title || 'New Chat'; } } catch (error) { - console.log(error); + logger.error('[getConvoTitle] Error getting conversation title', error); return { message: 'Error getting conversation title' }; } }, @@ -123,7 +123,7 @@ module.exports = { * const user = 'someUserId'; * const filter = { someField: 'someValue' }; * const result = await deleteConvos(user, filter); - * console.log(result); // { n: 5, ok: 1, deletedCount: 5, messages: { n: 10, ok: 1, deletedCount: 10 } } + * logger.error(result); // { n: 5, ok: 1, deletedCount: 5, messages: { n: 10, ok: 1, deletedCount: 10 } } */ deleteConvos: async (user, filter) => { let toRemove = await Conversation.find({ ...filter, user }).select('conversationId'); diff --git a/api/models/File.js b/api/models/File.js index 84822a71d7a..4c353fd70b0 100644 --- a/api/models/File.js +++ b/api/models/File.js @@ -24,7 +24,7 @@ const getFiles = async (filter) => { /** * Creates a new file with a TTL of 1 hour. - * @param {Object} data - The file data to be created, must contain file_id. + * @param {MongoFile} data - The file data to be created, must contain file_id. * @returns {Promise} A promise that resolves to the created file document. */ const createFile = async (data) => { @@ -40,7 +40,7 @@ const createFile = async (data) => { /** * Updates a file identified by file_id with new data and removes the TTL. - * @param {Object} data - The data to update, must contain file_id. + * @param {MongoFile} data - The data to update, must contain file_id. * @returns {Promise} A promise that resolves to the updated file document. */ const updateFile = async (data) => { @@ -54,7 +54,7 @@ const updateFile = async (data) => { /** * Increments the usage of a file identified by file_id. - * @param {Object} data - The data to update, must contain file_id and the increment value for usage. + * @param {MongoFile} data - The data to update, must contain file_id and the increment value for usage. * @returns {Promise} A promise that resolves to the updated file document. */ const updateFileUsage = async (data) => { diff --git a/api/models/Message.js b/api/models/Message.js index 1f9b8c16ab7..7accf9285a8 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -1,5 +1,6 @@ const { z } = require('zod'); const Message = require('./schema/messageSchema'); +const logger = require('~/config/winston'); const idSchema = z.string().uuid(); @@ -14,17 +15,16 @@ module.exports = { parentMessageId, sender, text, - isCreatedByUser = false, + isCreatedByUser, error, unfinished, - cancelled, files, - isEdited = false, - finish_reason = null, - tokenCount = null, - plugin = null, - plugins = null, - model = null, + isEdited, + finish_reason, + tokenCount, + plugin, + plugins, + model, }) { try { const validConvoId = idSchema.safeParse(conversationId); @@ -44,7 +44,6 @@ module.exports = { finish_reason, error, unfinished, - cancelled, tokenCount, plugin, plugins, @@ -67,7 +66,7 @@ module.exports = { tokenCount, }; } catch (err) { - console.error(`Error saving message: ${err}`); + logger.error('Error saving message:', err); throw new Error('Failed to save message.'); } }, @@ -92,7 +91,7 @@ module.exports = { isEdited: true, }; } catch (err) { - console.error(`Error updating message: ${err}`); + logger.error('Error updating message:', err); throw new Error('Failed to update message.'); } }, @@ -106,7 +105,7 @@ module.exports = { }); } } catch (err) { - console.error(`Error deleting messages: ${err}`); + logger.error('Error deleting messages:', err); throw new Error('Failed to delete messages.'); } }, @@ -115,7 +114,7 @@ module.exports = { try { return await Message.find(filter).sort({ createdAt: 1 }).lean(); } catch (err) { - console.error(`Error getting messages: ${err}`); + logger.error('Error getting messages:', err); throw new Error('Failed to get messages.'); } }, @@ -124,7 +123,7 @@ module.exports = { try { return await Message.deleteMany(filter); } catch (err) { - console.error(`Error deleting messages: ${err}`); + logger.error('Error deleting messages:', err); throw new Error('Failed to delete messages.'); } }, diff --git a/api/models/Preset.js b/api/models/Preset.js index 553e2e7fcec..e9f0a1e77e8 100644 --- a/api/models/Preset.js +++ b/api/models/Preset.js @@ -1,10 +1,11 @@ const Preset = require('./schema/presetSchema'); +const { logger } = require('~/config'); const getPreset = async (user, presetId) => { try { return await Preset.findOne({ user, presetId }).lean(); } catch (error) { - console.log(error); + logger.error('[getPreset] Error getting single preset', error); return { message: 'Error getting single preset' }; } }; @@ -30,7 +31,7 @@ module.exports = { return presets; } catch (error) { - console.log(error); + logger.error('[getPresets] Error getting presets', error); return { message: 'Error retrieving presets' }; } }, @@ -62,7 +63,7 @@ module.exports = { setter.$set = update; return await Preset.findOneAndUpdate({ presetId, user }, setter, { new: true, upsert: true }); } catch (error) { - console.log(error); + logger.error('[savePreset] Error saving preset', error); return { message: 'Error saving preset' }; } }, diff --git a/api/models/Prompt.js b/api/models/Prompt.js index cd77b42b356..f2759472b66 100644 --- a/api/models/Prompt.js +++ b/api/models/Prompt.js @@ -1,4 +1,5 @@ const mongoose = require('mongoose'); +const { logger } = require('~/config'); const promptSchema = mongoose.Schema( { @@ -28,7 +29,7 @@ module.exports = { }); return { title, prompt }; } catch (error) { - console.error(error); + logger.error('Error saving prompt', error); return { prompt: 'Error saving prompt' }; } }, @@ -36,7 +37,7 @@ module.exports = { try { return await Prompt.find(filter).lean(); } catch (error) { - console.error(error); + logger.error('Error getting prompts', error); return { prompt: 'Error getting prompts' }; } }, @@ -44,7 +45,7 @@ module.exports = { try { return await Prompt.deleteMany(filter); } catch (error) { - console.error(error); + logger.error('Error deleting prompts', error); return { prompt: 'Error deleting prompts' }; } }, diff --git a/api/models/Session.js b/api/models/Session.js index d93ac526c14..de7e07400a3 100644 --- a/api/models/Session.js +++ b/api/models/Session.js @@ -1,6 +1,8 @@ -const mongoose = require('mongoose'); const crypto = require('crypto'); -const signPayload = require('../server/services/signPayload'); +const mongoose = require('mongoose'); +const signPayload = require('~/server/services/signPayload'); +const { logger } = require('~/config'); + const { REFRESH_TOKEN_EXPIRY } = process.env ?? {}; const expires = eval(REFRESH_TOKEN_EXPIRY) ?? 1000 * 60 * 60 * 24 * 7; @@ -44,8 +46,8 @@ sessionSchema.methods.generateRefreshToken = async function () { return refreshToken; } catch (error) { - console.error( - 'Error generating refresh token. Have you set a JWT_REFRESH_SECRET in the .env file?\n\n', + logger.error( + 'Error generating refresh token. Is a `JWT_REFRESH_SECRET` set in the .env file?\n\n', error, ); throw error; @@ -59,10 +61,12 @@ sessionSchema.statics.deleteAllUserSessions = async function (userId) { } const result = await this.deleteMany({ user: userId }); if (result && result?.deletedCount > 0) { - console.log(`Deleted ${result.deletedCount} sessions for user ${userId}.`); + logger.debug( + `[deleteAllUserSessions] Deleted ${result.deletedCount} sessions for user ${userId}.`, + ); } } catch (error) { - console.log('Error in deleting user sessions:', error); + logger.error('[deleteAllUserSessions] Error in deleting user sessions:', error); throw error; } }; diff --git a/api/models/Transaction.js b/api/models/Transaction.js index 635db45b635..0bc26fc37ee 100644 --- a/api/models/Transaction.js +++ b/api/models/Transaction.js @@ -39,7 +39,7 @@ transactionSchema.statics.create = async function (transactionData) { { user: transaction.user }, { $inc: { tokenCredits: transaction.tokenValue } }, { upsert: true, new: true }, - ); + ).lean(); }; module.exports = mongoose.model('Transaction', transactionSchema); diff --git a/api/models/checkBalance.js b/api/models/checkBalance.js index d36b77afe62..c0bbd060bfb 100644 --- a/api/models/checkBalance.js +++ b/api/models/checkBalance.js @@ -13,7 +13,6 @@ const { logViolation } = require('../cache'); * @param {string} params.txData.user - The user ID or identifier. * @param {('prompt' | 'completion')} params.txData.tokenType - The type of token. * @param {number} params.txData.amount - The amount of tokens. - * @param {boolean} params.txData.debug - Debug flag. * @param {string} params.txData.model - The model name or identifier. * @returns {Promise} Returns true if the user can spend the amount, otherwise denies the request. * @throws {Error} Throws an error if there's an issue with the balance check. diff --git a/api/models/plugins/mongoMeili.js b/api/models/plugins/mongoMeili.js index 4d97ed5d038..abba8486148 100644 --- a/api/models/plugins/mongoMeili.js +++ b/api/models/plugins/mongoMeili.js @@ -1,7 +1,9 @@ +const _ = require('lodash'); const mongoose = require('mongoose'); const { MeiliSearch } = require('meilisearch'); -const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc'); -const _ = require('lodash'); +const { cleanUpPrimaryKeyValue } = require('~/lib/utils/misc'); +const logger = require('~/config/meiliLogger'); + const searchEnabled = process.env.SEARCH && process.env.SEARCH.toLowerCase() === 'true'; const meiliEnabled = process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY && searchEnabled; @@ -64,8 +66,7 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) { offset += batchSize; } - console.log('indexMap', indexMap.size); - console.log('mongoMap', mongoMap.size); + logger.debug('[syncWithMeili]', { indexMap: indexMap.size, mongoMap: mongoMap.size }); const updateOps = []; @@ -80,7 +81,11 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) { (doc.text && doc.text !== mongoMap.get(id).text) || (doc.title && doc.title !== mongoMap.get(id).title) ) { - console.log(`${id} had document discrepancy in ${doc.text ? 'text' : 'title'} field`); + logger.debug( + `[syncWithMeili] ${id} had document discrepancy in ${ + doc.text ? 'text' : 'title' + } field`, + ); updateOps.push({ updateOne: { filter: update, update: { $set: { _meiliIndex: true } } }, }); @@ -116,15 +121,14 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) { if (updateOps.length > 0) { await this.collection.bulkWrite(updateOps); - console.log( - `[Meilisearch] Finished indexing ${ + logger.debug( + `[syncWithMeili] Finished indexing ${ primaryKey === 'messageId' ? 'messages' : 'conversations' }`, ); } } catch (error) { - console.log('[Meilisearch] Error adding document to Meili'); - console.error(error); + logger.error('[syncWithMeili] Error adding document to Meili', error); } } @@ -143,7 +147,7 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) { const query = {}; // query[primaryKey] = { $in: _.map(data.hits, primaryKey) }; query[primaryKey] = _.map(data.hits, (hit) => cleanUpPrimaryKeyValue(hit[primaryKey])); - // console.log('query', query); + // logger.debug('query', query); const hitsFromMongoose = await this.find( query, _.reduce( @@ -186,11 +190,11 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) { async addObjectToMeili() { const object = this.preprocessObjectForIndex(); try { - // console.log('Adding document to Meili', object); + // logger.debug('Adding document to Meili', object); await index.addDocuments([object]); } catch (error) { - // console.log('Error adding document to Meili'); - // console.error(error); + // logger.debug('Error adding document to Meili'); + // logger.error(error); } await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } }); @@ -311,10 +315,10 @@ module.exports = function mongoMeili(schema, options) { return next(); } catch (error) { if (meiliEnabled) { - console.log( - '[Meilisearch] There was an issue deleting conversation indexes upon deletion, next startup may be slow due to syncing', + logger.error( + '[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion, next startup may be slow due to syncing', + error, ); - console.error(error); } return next(); } @@ -335,7 +339,11 @@ module.exports = function mongoMeili(schema, options) { try { meiliDoc = await client.index('convos').getDocument(doc.conversationId); } catch (error) { - console.log('[Meilisearch] Convo not found and will index', doc.conversationId); + logger.error( + '[MeiliMongooseModel.findOneAndUpdate] Convo not found in MeiliSearch and will index ' + + doc.conversationId, + error, + ); } } diff --git a/api/models/schema/messageSchema.js b/api/models/schema/messageSchema.js index 26648ab4ee7..33d799544b2 100644 --- a/api/models/schema/messageSchema.js +++ b/api/models/schema/messageSchema.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const mongoMeili = require('../plugins/mongoMeili'); +const mongoMeili = require('~/models/plugins/mongoMeili'); const messageSchema = mongoose.Schema( { messageId: { @@ -21,6 +21,7 @@ const messageSchema = mongoose.Schema( }, model: { type: String, + default: null, }, conversationSignature: { type: String, @@ -68,10 +69,6 @@ const messageSchema = mongoose.Schema( type: Boolean, default: false, }, - cancelled: { - type: Boolean, - default: false, - }, error: { type: Boolean, default: false, diff --git a/api/models/spendTokens.js b/api/models/spendTokens.js index abaab6145e5..fe3a2be87ae 100644 --- a/api/models/spendTokens.js +++ b/api/models/spendTokens.js @@ -1,4 +1,5 @@ const Transaction = require('./Transaction'); +const { logger } = require('~/config'); /** * Creates up to two transactions to record the spending of tokens. @@ -30,7 +31,7 @@ const spendTokens = async (txData, tokenUsage) => { } if (!completionTokens) { - this.debug && console.dir({ prompt, completion }, { depth: null }); + logger.debug('[spendTokens] !completionTokens', { prompt, completion }); return; } @@ -40,9 +41,9 @@ const spendTokens = async (txData, tokenUsage) => { rawAmount: -completionTokens, }); - this.debug && console.dir({ prompt, completion }, { depth: null }); + logger.debug('[spendTokens] post-transaction', { prompt, completion }); } catch (err) { - console.error(err); + logger.error('[spendTokens]', err); } }; diff --git a/api/package.json b/api/package.json index 354a35c7279..56d1a7e59b9 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "@librechat/backend", - "version": "0.6.1", + "version": "0.6.5", "description": "", "scripts": { "start": "echo 'please run this from the root directory'", @@ -30,7 +30,8 @@ "@anthropic-ai/sdk": "^0.5.4", "@azure/search-documents": "^12.0.0", "@keyv/mongo": "^2.1.8", - "@keyv/redis": "^2.8.0", + "@keyv/redis": "^2.8.1", + "@langchain/google-genai": "^0.0.2", "axios": "^1.3.4", "bcryptjs": "^2.4.3", "cheerio": "^1.0.0-rc.12", @@ -43,6 +44,7 @@ "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^6.9.0", "express-session": "^1.17.3", + "firebase": "^10.6.0", "googleapis": "^126.0.1", "handlebars": "^4.7.7", "html": "^1.0.0", @@ -51,7 +53,8 @@ "jsonwebtoken": "^9.0.0", "keyv": "^4.5.4", "keyv-file": "^0.2.0", - "langchain": "^0.0.186", + "klona": "^2.0.6", + "langchain": "^0.0.213", "librechat-data-provider": "*", "lodash": "^4.17.21", "meilisearch": "^0.33.0", @@ -74,8 +77,10 @@ "pino": "^8.12.1", "sharp": "^0.32.6", "tiktoken": "^1.0.10", + "traverse": "^0.6.7", "ua-parser-js": "^1.0.36", - "winston": "^3.10.0", + "winston": "^3.11.0", + "winston-daily-rotate-file": "^4.7.1", "zod": "^3.22.4" }, "devDependencies": { diff --git a/api/server/controllers/AskController.js b/api/server/controllers/AskController.js index 8a30f0fad1c..78933feebc1 100644 --- a/api/server/controllers/AskController.js +++ b/api/server/controllers/AskController.js @@ -1,9 +1,10 @@ +const { getResponseSender } = require('librechat-data-provider'); const { sendMessage, createOnProgress } = require('~/server/utils'); const { saveMessage, getConvoTitle, getConvo } = require('~/models'); -const { getResponseSender } = require('librechat-data-provider'); const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { logger } = require('~/config'); -const AskController = async (req, res, next, initializeClient) => { +const AskController = async (req, res, next, initializeClient, addTitle) => { let { text, endpointOption, @@ -11,8 +12,9 @@ const AskController = async (req, res, next, initializeClient) => { parentMessageId = null, overrideParentMessageId = null, } = req.body; - console.log('ask log'); - console.dir({ text, conversationId, endpointOption }, { depth: null }); + + logger.debug('[AskController]', { text, conversationId, ...endpointOption }); + let metadata; let userMessage; let promptTokens; @@ -21,8 +23,11 @@ const AskController = async (req, res, next, initializeClient) => { let lastSavedTimestamp = 0; let saveDelay = 100; const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model }); + const newConvo = !conversationId; const user = req.user.id; + const addMetadata = (data) => (metadata = data); + const getReqData = (data = {}) => { for (let key in data) { if (key === 'userMessage') { @@ -38,36 +43,42 @@ const AskController = async (req, res, next, initializeClient) => { } }; - const { onProgress: progressCallback, getPartialText } = createOnProgress({ - onProgress: ({ text: partialText }) => { - const currentTimestamp = Date.now(); - - if (currentTimestamp - lastSavedTimestamp > saveDelay) { - lastSavedTimestamp = currentTimestamp; - saveMessage({ - messageId: responseMessageId, - sender, - conversationId, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: partialText, - unfinished: true, - cancelled: false, - error: false, - user, - }); - } + let getText; - if (saveDelay < 500) { - saveDelay = 500; - } - }, - }); try { - const addMetadata = (data) => (metadata = data); + const { client } = await initializeClient({ req, res, endpointOption }); + + const { onProgress: progressCallback, getPartialText } = createOnProgress({ + onProgress: ({ text: partialText }) => { + const currentTimestamp = Date.now(); + + if (currentTimestamp - lastSavedTimestamp > saveDelay) { + lastSavedTimestamp = currentTimestamp; + saveMessage({ + messageId: responseMessageId, + sender, + conversationId, + parentMessageId: overrideParentMessageId ?? userMessageId, + text: partialText, + model: client.modelOptions.model, + unfinished: true, + error: false, + user, + }); + } + + if (saveDelay < 500) { + saveDelay = 500; + } + }, + }); + + getText = getPartialText; + const getAbortData = () => ({ + sender, conversationId, messageId: responseMessageId, - sender, parentMessageId: overrideParentMessageId ?? userMessageId, text: getPartialText(), userMessage, @@ -76,49 +87,61 @@ const AskController = async (req, res, next, initializeClient) => { const { abortController, onStart } = createAbortController(req, res, getAbortData); - const { client } = await initializeClient({ req, res, endpointOption }); - - let response = await client.sendMessage(text, { - // debug: true, + const messageOptions = { user, - conversationId, parentMessageId, + conversationId, overrideParentMessageId, - ...endpointOption, + getReqData, + onStart, + addMetadata, + abortController, onProgress: progressCallback.call(null, { res, text, - parentMessageId: overrideParentMessageId ?? userMessageId, + parentMessageId: overrideParentMessageId || userMessageId, }), - onStart, - getReqData, - addMetadata, - abortController, - }); + }; + + let response = await client.sendMessage(text, messageOptions); + + if (overrideParentMessageId) { + response.parentMessageId = overrideParentMessageId; + } if (metadata) { response = { ...response, ...metadata }; } - if (overrideParentMessageId) { - response.parentMessageId = overrideParentMessageId; + if (client.options.attachments) { + userMessage.files = client.options.attachments; + delete userMessage.image_urls; } - sendMessage(res, { - title: await getConvoTitle(user, conversationId), - final: true, - conversation: await getConvo(user, conversationId), - requestMessage: userMessage, - responseMessage: response, - }); - res.end(); + if (!abortController.signal.aborted) { + sendMessage(res, { + title: await getConvoTitle(user, conversationId), + final: true, + conversation: await getConvo(user, conversationId), + requestMessage: userMessage, + responseMessage: response, + }); + res.end(); + + await saveMessage({ ...response, user }); + } - await saveMessage({ ...response, user }); await saveMessage(userMessage); - // TODO: add title service + if (addTitle && parentMessageId === '00000000-0000-0000-0000-000000000000' && newConvo) { + addTitle(req, { + text, + response, + client, + }); + } } catch (error) { - const partialText = getPartialText(); + const partialText = getText && getText(); handleAbortError(res, req, error, { partialText, conversationId, diff --git a/api/server/controllers/AuthController.js b/api/server/controllers/AuthController.js index eeec0ef9488..921ba3d8388 100644 --- a/api/server/controllers/AuthController.js +++ b/api/server/controllers/AuthController.js @@ -1,13 +1,14 @@ const crypto = require('crypto'); const cookies = require('cookie'); const jwt = require('jsonwebtoken'); -const { Session, User } = require('../../models'); +const { Session, User } = require('~/models'); const { registerUser, - requestPasswordReset, resetPassword, setAuthTokens, -} = require('../services/AuthService'); + requestPasswordReset, +} = require('~/server/services/AuthService'); +const { logger } = require('~/config'); const registrationController = async (req, res) => { try { @@ -27,7 +28,7 @@ const registrationController = async (req, res) => { res.status(status).send({ message }); } } catch (err) { - console.log(err); + logger.error('[registrationController]', err); return res.status(500).json({ message: err.message }); } }; @@ -45,7 +46,7 @@ const resetPasswordRequestController = async (req, res) => { return res.status(200).json(resetService); } } catch (e) { - console.log(e); + logger.error('[resetPasswordRequestController]', e); return res.status(400).json({ message: e.message }); } }; @@ -63,7 +64,7 @@ const resetPasswordController = async (req, res) => { return res.status(200).json(resetPasswordService); } } catch (e) { - console.log(e); + logger.error('[resetPasswordController]', e); return res.status(400).json({ message: e.message }); } }; @@ -108,8 +109,7 @@ const refreshController = async (req, res) => { res.status(401).send('Refresh token expired or not found for this user'); } } catch (err) { - console.error('Refresh token error', refreshToken); - console.error(err); + logger.error(`[refreshController] Refresh token: ${refreshToken}`, err); res.status(403).send('Invalid refresh token'); } }; diff --git a/api/server/controllers/EditController.js b/api/server/controllers/EditController.js index 862b7ee3ba4..72ee58026a4 100644 --- a/api/server/controllers/EditController.js +++ b/api/server/controllers/EditController.js @@ -1,7 +1,8 @@ +const { getResponseSender } = require('librechat-data-provider'); const { sendMessage, createOnProgress } = require('~/server/utils'); const { saveMessage, getConvoTitle, getConvo } = require('~/models'); -const { getResponseSender } = require('librechat-data-provider'); const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { logger } = require('~/config'); const EditController = async (req, res, next, initializeClient) => { let { @@ -14,8 +15,15 @@ const EditController = async (req, res, next, initializeClient) => { parentMessageId = null, overrideParentMessageId = null, } = req.body; - console.log('edit log'); - console.dir({ text, generation, isContinued, conversationId, endpointOption }, { depth: null }); + + logger.debug('[EditController]', { + text, + generation, + isContinued, + conversationId, + ...endpointOption, + }); + let metadata; let userMessage; let promptTokens; @@ -42,6 +50,7 @@ const EditController = async (req, res, next, initializeClient) => { generation, onProgress: ({ text: partialText }) => { const currentTimestamp = Date.now(); + if (currentTimestamp - lastSavedTimestamp > saveDelay) { lastSavedTimestamp = currentTimestamp; saveMessage({ @@ -50,8 +59,8 @@ const EditController = async (req, res, next, initializeClient) => { conversationId, parentMessageId: overrideParentMessageId ?? userMessageId, text: partialText, + model: endpointOption.modelOptions.model, unfinished: true, - cancelled: false, isEdited: true, error: false, user, @@ -63,19 +72,20 @@ const EditController = async (req, res, next, initializeClient) => { } }, }); - try { - const getAbortData = () => ({ - conversationId, - messageId: responseMessageId, - sender, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: getPartialText(), - userMessage, - promptTokens, - }); - const { abortController, onStart } = createAbortController(req, res, getAbortData); + const getAbortData = () => ({ + conversationId, + messageId: responseMessageId, + sender, + parentMessageId: overrideParentMessageId ?? userMessageId, + text: getPartialText(), + userMessage, + promptTokens, + }); + const { abortController, onStart } = createAbortController(req, res, getAbortData); + + try { const { client } = await initializeClient({ req, res, endpointOption }); let response = await client.sendMessage(text, { @@ -87,39 +97,33 @@ const EditController = async (req, res, next, initializeClient) => { parentMessageId, responseMessageId, overrideParentMessageId, - ...endpointOption, - onProgress: progressCallback.call(null, { - res, - text, - parentMessageId: overrideParentMessageId ?? userMessageId, - }), getReqData, onStart, addMetadata, abortController, + onProgress: progressCallback.call(null, { + res, + text, + parentMessageId: overrideParentMessageId || userMessageId, + }), }); if (metadata) { response = { ...response, ...metadata }; } - if (overrideParentMessageId) { - response.parentMessageId = overrideParentMessageId; - } + if (!abortController.signal.aborted) { + sendMessage(res, { + title: await getConvoTitle(user, conversationId), + final: true, + conversation: await getConvo(user, conversationId), + requestMessage: userMessage, + responseMessage: response, + }); + res.end(); - sendMessage(res, { - title: await getConvoTitle(user, conversationId), - final: true, - conversation: await getConvo(user, conversationId), - requestMessage: userMessage, - responseMessage: response, - }); - res.end(); - - await saveMessage({ ...response, user }); - await saveMessage(userMessage); - - // TODO: add title service + await saveMessage({ ...response, user }); + } } catch (error) { const partialText = getPartialText(); handleAbortError(res, req, error, { diff --git a/api/server/controllers/ErrorController.js b/api/server/controllers/ErrorController.js index cdfd5b97a61..1308527b8cd 100644 --- a/api/server/controllers/ErrorController.js +++ b/api/server/controllers/ErrorController.js @@ -1,15 +1,17 @@ +const { logger } = require('~/config'); + //handle duplicates const handleDuplicateKeyError = (err, res) => { + logger.error('Duplicate key error:', err.keyValue); const field = Object.keys(err.keyValue); const code = 409; const error = `An document with that ${field} already exists.`; - console.log('congrats you hit the duped keys error'); res.status(code).send({ messages: error, fields: field }); }; //handle validation errors const handleValidationError = (err, res) => { - console.log('congrats you hit the validation middleware'); + logger.error('Validation error:', err.errors); let errors = Object.values(err.errors).map((el) => el.message); let fields = Object.values(err.errors).map((el) => el.path); let code = 400; @@ -24,7 +26,6 @@ const handleValidationError = (err, res) => { // eslint-disable-next-line no-unused-vars module.exports = (err, req, res, next) => { try { - console.log('congrats you hit the error middleware'); if (err.name === 'ValidationError') { return (err = handleValidationError(err, res)); } @@ -32,6 +33,7 @@ module.exports = (err, req, res, next) => { return (err = handleDuplicateKeyError(err, res)); } } catch (err) { + logger.error('ErrorController => error', err); res.status(500).send('An unknown error occurred.'); } }; diff --git a/api/server/controllers/PluginController.js b/api/server/controllers/PluginController.js index 304c089657a..697a499796c 100644 --- a/api/server/controllers/PluginController.js +++ b/api/server/controllers/PluginController.js @@ -1,6 +1,8 @@ -const { promises: fs } = require('fs'); const path = require('path'); -const { addOpenAPISpecs } = require('../../app/clients/tools/util/addOpenAPISpecs'); +const { promises: fs } = require('fs'); +const { addOpenAPISpecs } = require('~/app/clients/tools/util/addOpenAPISpecs'); +const { CacheKeys } = require('~/common/enums'); +const { getLogStores } = require('~/cache'); const filterUniquePlugins = (plugins) => { const seen = new Set(); @@ -27,6 +29,13 @@ const isPluginAuthenticated = (plugin) => { const getAvailablePluginsController = async (req, res) => { try { + const cache = getLogStores(CacheKeys.CONFIG); + const cachedPlugins = await cache.get(CacheKeys.PLUGINS); + if (cachedPlugins) { + res.status(200).json(cachedPlugins); + return; + } + const manifestFile = await fs.readFile( path.join(__dirname, '..', '..', 'app', 'clients', 'tools', 'manifest.json'), 'utf8', @@ -42,6 +51,7 @@ const getAvailablePluginsController = async (req, res) => { } }); const plugins = await addOpenAPISpecs(authenticatedPlugins); + await cache.set(CacheKeys.PLUGINS, plugins); res.status(200).json(plugins); } catch (error) { res.status(500).json({ message: error.message }); diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index 21f03f686c1..fa08cd54529 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -1,5 +1,6 @@ -const { updateUserPluginsService } = require('../services/UserService'); -const { updateUserPluginAuth, deleteUserPluginAuth } = require('../services/PluginService'); +const { updateUserPluginsService } = require('~/server/services/UserService'); +const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService'); +const { logger } = require('~/config'); const getUserController = async (req, res) => { res.status(200).send(req.user); @@ -13,7 +14,7 @@ const updateUserPluginsController = async (req, res) => { const userPluginsService = await updateUserPluginsService(user, pluginKey, action); if (userPluginsService instanceof Error) { - console.log(userPluginsService); + logger.error('[userPluginsService]', userPluginsService); const { status, message } = userPluginsService; res.status(status).send({ message }); } @@ -24,7 +25,7 @@ const updateUserPluginsController = async (req, res) => { for (let i = 0; i < keys.length; i++) { authService = await updateUserPluginAuth(user.id, keys[i], pluginKey, values[i]); if (authService instanceof Error) { - console.log(authService); + logger.error('[authService]', authService); const { status, message } = authService; res.status(status).send({ message }); } @@ -34,7 +35,7 @@ const updateUserPluginsController = async (req, res) => { for (let i = 0; i < keys.length; i++) { authService = await deleteUserPluginAuth(user.id, keys[i]); if (authService instanceof Error) { - console.log(authService); + logger.error('[authService]', authService); const { status, message } = authService; res.status(status).send({ message }); } @@ -44,7 +45,7 @@ const updateUserPluginsController = async (req, res) => { res.status(200).send(); } catch (err) { - console.log(err); + logger.error('[updateUserPluginsController]', err); res.status(500).json({ message: err.message }); } }; diff --git a/api/server/controllers/auth/LoginController.js b/api/server/controllers/auth/LoginController.js index 9c3b556f688..1b3b6180b9a 100644 --- a/api/server/controllers/auth/LoginController.js +++ b/api/server/controllers/auth/LoginController.js @@ -1,5 +1,6 @@ -const User = require('../../../models/User'); -const { setAuthTokens } = require('../../services/AuthService'); +const User = require('~/models/User'); +const { setAuthTokens } = require('~/server/services/AuthService'); +const { logger } = require('~/config'); const loginController = async (req, res) => { try { @@ -15,7 +16,7 @@ const loginController = async (req, res) => { return res.status(200).send({ token, user }); } catch (err) { - console.log(err); + logger.error('[loginController]', err); } // Generic error messages are safer diff --git a/api/server/controllers/auth/LogoutController.js b/api/server/controllers/auth/LogoutController.js index 714a6466dad..b09b8722aa1 100644 --- a/api/server/controllers/auth/LogoutController.js +++ b/api/server/controllers/auth/LogoutController.js @@ -1,5 +1,6 @@ -const { logoutUser } = require('../../services/AuthService'); const cookies = require('cookie'); +const { logoutUser } = require('~/server/services/AuthService'); +const { logger } = require('~/config'); const logoutController = async (req, res) => { const refreshToken = req.headers.cookie ? cookies.parse(req.headers.cookie).refreshToken : null; @@ -9,7 +10,7 @@ const logoutController = async (req, res) => { res.clearCookie('refreshToken'); return res.status(status).send({ message }); } catch (err) { - console.log(err); + logger.error('[logoutController]', err); return res.status(500).json({ message: err.message }); } }; diff --git a/api/server/index.js b/api/server/index.js index 1120dfe6dc9..698620c56f3 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -4,10 +4,14 @@ const cors = require('cors'); const express = require('express'); const passport = require('passport'); const mongoSanitize = require('express-mongo-sanitize'); +const { initializeFirebase } = require('~/server/services/Files/Firebase/initialize'); const errorController = require('./controllers/ErrorController'); const configureSocialLogins = require('./socialLogins'); -const { connectDb, indexSync } = require('../lib/db'); -const config = require('../config'); +const { connectDb, indexSync } = require('~/lib/db'); +const { logger } = require('~/config'); +const noIndex = require('./middleware/noIndex'); + +const paths = require('~/config/paths'); const routes = require('./routes'); const { PORT, HOST, ALLOW_SOCIAL_LOGIN } = process.env ?? {}; @@ -15,17 +19,19 @@ const { PORT, HOST, ALLOW_SOCIAL_LOGIN } = process.env ?? {}; const port = Number(PORT) || 3080; const host = HOST || 'localhost'; const projectPath = path.join(__dirname, '..', '..', 'client'); -const { jwtLogin, passportLogin } = require('../strategies'); +const { jwtLogin, passportLogin } = require('~/strategies'); const startServer = async () => { await connectDb(); - console.log('Connected to MongoDB'); + logger.info('Connected to MongoDB'); + initializeFirebase(); await indexSync(); const app = express(); - app.locals.config = config; + app.locals.config = paths; // Middleware + app.use(noIndex); app.use(errorController); app.use(express.json({ limit: '3mb' })); app.use(mongoSanitize()); @@ -77,11 +83,11 @@ const startServer = async () => { app.listen(port, host, () => { if (host == '0.0.0.0') { - console.log( + logger.info( `Server listening on all interfaces at port ${port}. Use http://localhost:${port} to access it`, ); } else { - console.log(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); + logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); } }); }; @@ -91,13 +97,12 @@ startServer(); let messageCount = 0; process.on('uncaughtException', (err) => { if (!err.message.includes('fetch failed')) { - console.error('There was an uncaught error:'); - console.error(err); + logger.error('There was an uncaught error:', err); } if (err.message.includes('fetch failed')) { if (messageCount === 0) { - console.error('Meilisearch error, search will be disabled'); + logger.warn('Meilisearch error, search will be disabled'); messageCount++; } @@ -105,7 +110,7 @@ process.on('uncaughtException', (err) => { } if (err.message.includes('OpenAIError') || err.message.includes('ChatCompletionMessage')) { - console.error( + logger.error( '\n\nAn Uncaught `OpenAIError` error may be due to your reverse-proxy setup or stream configuration, or a bug in the `openai` node package.', ); return; diff --git a/api/server/middleware/abortMiddleware.js b/api/server/middleware/abortMiddleware.js index ea331bcbbdf..811963174c9 100644 --- a/api/server/middleware/abortMiddleware.js +++ b/api/server/middleware/abortMiddleware.js @@ -2,20 +2,31 @@ const { sendMessage, sendError, countTokens, isEnabled } = require('~/server/uti const { saveMessage, getConvo, getConvoTitle } = require('~/models'); const clearPendingReq = require('~/cache/clearPendingReq'); const abortControllers = require('./abortControllers'); +const { redactMessage } = require('~/config/parsers'); const spendTokens = require('~/models/spendTokens'); +const { logger } = require('~/config'); async function abortMessage(req, res) { - const { abortKey } = req.body; + let { abortKey, conversationId } = req.body; + + if (!abortKey && conversationId) { + abortKey = conversationId; + } if (!abortControllers.has(abortKey) && !res.headersSent) { return res.status(404).send({ message: 'Request not found' }); } const { abortController } = abortControllers.get(abortKey); - const ret = await abortController.abortCompletion(); - console.log('Aborted request', abortKey); + const finalEvent = await abortController.abortCompletion(); + logger.debug('[abortMessage] Aborted request', { abortKey }); abortControllers.delete(abortKey); - res.send(JSON.stringify(ret)); + + if (res.headersSent && finalEvent) { + return sendMessage(res, finalEvent); + } + + res.send(JSON.stringify(finalEvent)); } const handleAbort = () => { @@ -26,7 +37,7 @@ const handleAbort = () => { } return await abortMessage(req, res); } catch (err) { - console.error(err); + logger.error('[abortMessage] handleAbort error', err); } }; }; @@ -56,7 +67,6 @@ const createAbortController = (req, res, getAbortData) => { finish_reason: 'incomplete', model: endpointOption.modelOptions.model, unfinished: false, - cancelled: true, error: false, isCreatedByUser: false, tokenCount: completionTokens, @@ -82,19 +92,34 @@ const createAbortController = (req, res, getAbortData) => { }; const handleAbortError = async (res, req, error, data) => { - console.error(error); + logger.error('[handleAbortError] AI response error; aborting request:', error); const { sender, conversationId, messageId, parentMessageId, partialText } = data; - const respondWithError = async () => { + if (error.stack && error.stack.includes('google')) { + logger.warn( + `AI Response error for conversation ${conversationId} likely caused by Google censor/filter`, + ); + } + + const respondWithError = async (partialText) => { const options = { sender, messageId, conversationId, parentMessageId, - text: error.message, + text: redactMessage(error.message), shouldSaveMessage: true, user: req.user.id, }; + + if (partialText) { + options.overrideProps = { + error: false, + unfinished: true, + text: partialText, + }; + } + const callback = async () => { if (abortControllers.has(conversationId)) { const { abortController } = abortControllers.get(conversationId); @@ -110,8 +135,8 @@ const handleAbortError = async (res, req, error, data) => { try { return await abortMessage(req, res); } catch (err) { - console.error(err); - return respondWithError(); + logger.error('[handleAbortError] error while trying to abort message', err); + return respondWithError(partialText); } } else { return respondWithError(); diff --git a/api/server/middleware/index.js b/api/server/middleware/index.js index 553f2c663ab..77afd971650 100644 --- a/api/server/middleware/index.js +++ b/api/server/middleware/index.js @@ -12,6 +12,8 @@ const concurrentLimiter = require('./concurrentLimiter'); const validateMessageReq = require('./validateMessageReq'); const buildEndpointOption = require('./buildEndpointOption'); const validateRegistration = require('./validateRegistration'); +const moderateText = require('./moderateText'); +const noIndex = require('./noIndex'); module.exports = { ...abortMiddleware, @@ -28,4 +30,6 @@ module.exports = { validateMessageReq, buildEndpointOption, validateRegistration, + moderateText, + noIndex, }; diff --git a/api/server/middleware/moderateText.js b/api/server/middleware/moderateText.js new file mode 100644 index 00000000000..c4bfd8a13ae --- /dev/null +++ b/api/server/middleware/moderateText.js @@ -0,0 +1,39 @@ +const axios = require('axios'); +const denyRequest = require('./denyRequest'); + +async function moderateText(req, res, next) { + if (process.env.OPENAI_MODERATION === 'true') { + try { + const { text } = req.body; + + const response = await axios.post( + process.env.OPENAI_MODERATION_REVERSE_PROXY || 'https://api.openai.com/v1/moderations', + { + input: text, + }, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${process.env.OPENAI_MODERATION_API_KEY}`, + }, + }, + ); + + const results = response.data.results; + const flagged = results.some((result) => result.flagged); + + if (flagged) { + const type = 'moderation'; + const errorMessage = { type }; + return await denyRequest(req, res, errorMessage); + } + } catch (error) { + console.error('Error in moderateText:', error); + const errorMessage = 'error in moderation check'; + return await denyRequest(req, res, errorMessage); + } + } + next(); +} + +module.exports = moderateText; diff --git a/api/server/middleware/noIndex.js b/api/server/middleware/noIndex.js new file mode 100644 index 00000000000..c4d7b55f2de --- /dev/null +++ b/api/server/middleware/noIndex.js @@ -0,0 +1,11 @@ +const noIndex = (req, res, next) => { + const shouldNoIndex = process.env.NO_INDEX ? process.env.NO_INDEX === 'true' : true; + + if (shouldNoIndex) { + res.setHeader('X-Robots-Tag', 'noindex'); + } + + next(); +}; + +module.exports = noIndex; diff --git a/api/server/routes/ask/addToCache.js b/api/server/routes/ask/addToCache.js index 616c9d91b0a..4ecdea0e0c6 100644 --- a/api/server/routes/ask/addToCache.js +++ b/api/server/routes/ask/addToCache.js @@ -1,5 +1,6 @@ const Keyv = require('keyv'); const { KeyvFile } = require('keyv-file'); +const { logger } = require('~/config'); const addToCache = async ({ endpoint, endpointOption, userMessage, responseMessage }) => { try { @@ -57,7 +58,7 @@ const addToCache = async ({ endpoint, endpointOption, userMessage, responseMessa await conversationsCache.set(conversationId, conversation); } catch (error) { - console.error('Trouble adding to cache', error); + logger.error('[addToCache] Error adding conversation to cache', error); } }; diff --git a/api/server/routes/ask/askChatGPTBrowser.js b/api/server/routes/ask/askChatGPTBrowser.js index 04772a74a36..34f1096a871 100644 --- a/api/server/routes/ask/askChatGPTBrowser.js +++ b/api/server/routes/ask/askChatGPTBrowser.js @@ -1,10 +1,12 @@ -const express = require('express'); const crypto = require('crypto'); +const express = require('express'); +const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('~/models'); +const { handleError, sendMessage, createOnProgress, handleText } = require('~/server/utils'); +const { setHeaders } = require('~/server/middleware'); +const { browserClient } = require('~/app/'); +const { logger } = require('~/config'); + const router = express.Router(); -const { browserClient } = require('../../../app/'); -const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); -const { handleError, sendMessage, createOnProgress, handleText } = require('../../utils'); -const { setHeaders } = require('../../middleware'); router.post('/', setHeaders, async (req, res) => { const { @@ -41,10 +43,10 @@ router.post('/', setHeaders, async (req, res) => { key: req.body?.key ?? null, }; - console.log('ask log', { + logger.debug('[/ask/chatGPTBrowser]', { userMessage, - endpointOption, conversationId, + ...endpointOption, }); if (!overrideParentMessageId) { @@ -97,7 +99,6 @@ const ask = async ({ parentMessageId: overrideParentMessageId || userMessageId, text: text, unfinished: true, - cancelled: false, error: false, isCreatedByUser: false, user, @@ -136,7 +137,7 @@ const ask = async ({ }, }); - console.log('CLIENT RESPONSE', response); + logger.debug('[/ask/chatGPTBrowser]', response); const newConversationId = response.conversationId || conversationId; const newUserMassageId = response.parentMessageId || userMessageId; @@ -153,7 +154,6 @@ const ask = async ({ text: await handleText(response), sender: endpointOption?.chatGptLabel || 'ChatGPT', unfinished: false, - cancelled: false, error: false, isCreatedByUser: false, }; @@ -224,7 +224,6 @@ const ask = async ({ conversationId, parentMessageId: overrideParentMessageId || userMessageId, unfinished: false, - cancelled: false, error: true, isCreatedByUser: false, text: `${getPartialMessage() ?? ''}\n\nError message: "${error.message}"`, diff --git a/api/server/routes/ask/bingAI.js b/api/server/routes/ask/bingAI.js index 4a170209b64..1281b56ae35 100644 --- a/api/server/routes/ask/bingAI.js +++ b/api/server/routes/ask/bingAI.js @@ -1,10 +1,12 @@ const express = require('express'); const crypto = require('crypto'); +const { handleError, sendMessage, createOnProgress, handleText } = require('~/server/utils'); +const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('~/models'); +const { setHeaders } = require('~/server/middleware'); +const { titleConvoBing, askBing } = require('~/app'); +const { logger } = require('~/config'); + const router = express.Router(); -const { titleConvoBing, askBing } = require('../../../app'); -const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); -const { handleError, sendMessage, createOnProgress, handleText } = require('../../utils'); -const { setHeaders } = require('../../middleware'); router.post('/', setHeaders, async (req, res) => { const { @@ -60,7 +62,7 @@ router.post('/', setHeaders, async (req, res) => { }; } - console.log('ask log', { + logger.debug('[/ask/bingAI] ask log', { userMessage, endpointOption, conversationId, @@ -123,7 +125,6 @@ const ask = async ({ model, text: text, unfinished: true, - cancelled: false, error: false, isCreatedByUser: false, user, @@ -153,10 +154,10 @@ const ask = async ({ abortController, }); - console.log('BING RESPONSE', response); + logger.debug('[/ask/bingAI] BING RESPONSE', response); if (response.details && response.details.scores) { - console.log('SCORES', response.details.scores); + logger.debug('[/ask/bingAI] SCORES', response.details.scores); } const newConversationId = endpointOption?.jailbreak @@ -191,7 +192,6 @@ const ask = async ({ response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text), unfinished, - cancelled: false, error: false, isCreatedByUser: false, }; @@ -250,7 +250,7 @@ const ask = async ({ }); } } catch (error) { - console.error(error); + logger.error('[/ask/bingAI] Error handling BingAI response', error); const partialText = getPartialText(); if (partialText?.length > 2) { const responseMessage = { @@ -261,7 +261,6 @@ const ask = async ({ text: partialText, model, unfinished: true, - cancelled: false, error: false, isCreatedByUser: false, }; @@ -276,14 +275,13 @@ const ask = async ({ responseMessage: responseMessage, }; } else { - console.log(error); + logger.error('[/ask/bingAI] Error handling BingAI response', error); const errorMessage = { messageId: responseMessageId, sender: model, conversationId, parentMessageId: overrideParentMessageId || userMessageId, unfinished: false, - cancelled: false, error: true, text: error.message, model, diff --git a/api/server/routes/ask/gptPlugins.js b/api/server/routes/ask/gptPlugins.js index f93f9e95f0c..85616cd1b31 100644 --- a/api/server/routes/ask/gptPlugins.js +++ b/api/server/routes/ask/gptPlugins.js @@ -13,8 +13,11 @@ const { setHeaders, validateEndpoint, buildEndpointOption, + moderateText, } = require('~/server/middleware'); +const { logger } = require('~/config'); +router.use(moderateText); router.post('/abort', handleAbort()); router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => { @@ -25,8 +28,7 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, parentMessageId = null, overrideParentMessageId = null, } = req.body; - console.log('ask log'); - console.dir({ text, conversationId, endpointOption }, { depth: null }); + logger.debug('[/ask/gptPlugins]', { text, conversationId, ...endpointOption }); let metadata; let userMessage; let promptTokens; @@ -81,7 +83,6 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, text: partialText, model: endpointOption.modelOptions.model, unfinished: true, - cancelled: false, error: false, plugins, user, @@ -189,8 +190,8 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, response = { ...response, ...metadata }; } - console.log('CLIENT RESPONSE'); - console.dir(response, { depth: null }); + logger.debug('[/ask/gptPlugins]', response); + response.plugins = plugins.map((p) => ({ ...p, loading: false })); await saveMessage({ ...response, user }); diff --git a/api/server/routes/ask/openAI.js b/api/server/routes/ask/openAI.js index 1f292bee8f8..31b3111077f 100644 --- a/api/server/routes/ask/openAI.js +++ b/api/server/routes/ask/openAI.js @@ -1,158 +1,20 @@ const express = require('express'); -const router = express.Router(); -const { sendMessage, createOnProgress } = require('~/server/utils'); -const { saveMessage, getConvoTitle, getConvo } = require('~/models'); -const { getResponseSender } = require('librechat-data-provider'); +const AskController = require('~/server/controllers/AskController'); const { addTitle, initializeClient } = require('~/server/services/Endpoints/openAI'); const { handleAbort, - createAbortController, - handleAbortError, setHeaders, validateEndpoint, buildEndpointOption, + moderateText, } = require('~/server/middleware'); +const router = express.Router(); +router.use(moderateText); router.post('/abort', handleAbort()); -router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => { - let { - text, - endpointOption, - conversationId, - parentMessageId = null, - overrideParentMessageId = null, - } = req.body; - console.log('ask log'); - console.dir({ text, conversationId, endpointOption }, { depth: null }); - let metadata; - let userMessage; - let promptTokens; - let userMessageId; - let responseMessageId; - let lastSavedTimestamp = 0; - let saveDelay = 100; - const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model }); - const newConvo = !conversationId; - const user = req.user.id; - - const addMetadata = (data) => (metadata = data); - - const getReqData = (data = {}) => { - for (let key in data) { - if (key === 'userMessage') { - userMessage = data[key]; - userMessageId = data[key].messageId; - } else if (key === 'responseMessageId') { - responseMessageId = data[key]; - } else if (key === 'promptTokens') { - promptTokens = data[key]; - } else if (!conversationId && key === 'conversationId') { - conversationId = data[key]; - } - } - }; - - const { onProgress: progressCallback, getPartialText } = createOnProgress({ - onProgress: ({ text: partialText }) => { - const currentTimestamp = Date.now(); - - if (currentTimestamp - lastSavedTimestamp > saveDelay) { - lastSavedTimestamp = currentTimestamp; - saveMessage({ - messageId: responseMessageId, - sender, - conversationId, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: partialText, - model: endpointOption.modelOptions.model, - unfinished: true, - cancelled: false, - error: false, - user, - }); - } - - if (saveDelay < 500) { - saveDelay = 500; - } - }, - }); - - const getAbortData = () => ({ - sender, - conversationId, - messageId: responseMessageId, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: getPartialText(), - userMessage, - promptTokens, - }); - - const { abortController, onStart } = createAbortController(req, res, getAbortData); - - try { - const { client } = await initializeClient({ req, res, endpointOption }); - const messageOptions = { - user, - parentMessageId, - conversationId, - overrideParentMessageId, - getReqData, - onStart, - addMetadata, - abortController, - onProgress: progressCallback.call(null, { - res, - text, - parentMessageId: overrideParentMessageId || userMessageId, - }), - }; - - let response = await client.sendMessage(text, messageOptions); - - if (overrideParentMessageId) { - response.parentMessageId = overrideParentMessageId; - } - - if (metadata) { - response = { ...response, ...metadata }; - } - - if (client.options.attachments) { - userMessage.files = client.options.attachments; - delete userMessage.image_urls; - } - - sendMessage(res, { - title: await getConvoTitle(user, conversationId), - final: true, - conversation: await getConvo(user, conversationId), - requestMessage: userMessage, - responseMessage: response, - }); - res.end(); - - await saveMessage({ ...response, user }); - await saveMessage(userMessage); - - if (parentMessageId === '00000000-0000-0000-0000-000000000000' && newConvo) { - addTitle(req, { - text, - response, - client, - }); - } - } catch (error) { - const partialText = getPartialText(); - handleAbortError(res, req, error, { - partialText, - conversationId, - sender, - messageId: responseMessageId, - parentMessageId: userMessageId ?? parentMessageId, - }); - } +router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => { + await AskController(req, res, next, initializeClient, addTitle); }); module.exports = router; diff --git a/api/server/routes/assistants/assistants.js b/api/server/routes/assistants/assistants.js index a33729b2b6d..b911c685aa9 100644 --- a/api/server/routes/assistants/assistants.js +++ b/api/server/routes/assistants/assistants.js @@ -1,5 +1,7 @@ const OpenAI = require('openai'); const express = require('express'); +const { logger } = require('~/config'); + const router = express.Router(); /** @@ -13,7 +15,7 @@ router.post('/', async (req, res) => { const openai = new OpenAI(process.env.OPENAI_API_KEY); const assistantData = req.body; const assistant = await openai.beta.assistants.create(assistantData); - console.log(assistant); + logger.debug('/assistants/', assistant); res.status(201).json(assistant); } catch (error) { res.status(500).json({ error: error.message }); diff --git a/api/server/routes/assistants/chat.js b/api/server/routes/assistants/chat.js index 71cbef22180..e45bad191e9 100644 --- a/api/server/routes/assistants/chat.js +++ b/api/server/routes/assistants/chat.js @@ -1,5 +1,6 @@ const crypto = require('crypto'); const OpenAI = require('openai'); +const { logger } = require('~/config'); const { sendMessage } = require('../../utils'); const { initThread, createRun, handleRun } = require('../../services/AssistantService'); const express = require('express'); @@ -23,7 +24,7 @@ const { */ router.post('/', setHeaders, async (req, res) => { try { - console.log(req.body); + logger.debug('[/assistants/chat/] req.body', req.body); // test message: // How many polls of 500 ms intervals are there in 18 seconds? @@ -100,7 +101,7 @@ router.post('/', setHeaders, async (req, res) => { res.end(); } catch (error) { // res.status(500).json({ error: error.message }); - console.error(error); + logger.error('[/assistants/chat/]', error); res.end(); } }); diff --git a/api/server/routes/config.js b/api/server/routes/config.js index 9c02aa53feb..85889f4b818 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -1,6 +1,8 @@ const express = require('express'); +const { isEnabled } = require('~/server/utils'); +const { logger } = require('~/config'); + const router = express.Router(); -const { isEnabled } = require('../utils'); const emailLoginEnabled = process.env.ALLOW_EMAIL_LOGIN === undefined || isEnabled(process.env.ALLOW_EMAIL_LOGIN); @@ -38,7 +40,7 @@ router.get('/', async function (req, res) { return res.status(200).send(payload); } catch (err) { - console.error(err); + logger.error('Error in startup config', err); return res.status(500).send({ error: err.message }); } }); diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index d4b919d309c..4395df0fee1 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -1,8 +1,9 @@ const express = require('express'); const router = express.Router(); -const { getConvo, saveConvo } = require('../../models'); -const { getConvosByPage, deleteConvos } = require('../../models/Conversation'); -const requireJwtAuth = require('../middleware/requireJwtAuth'); +const { getConvosByPage, deleteConvos } = require('~/models/Conversation'); +const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); +const { getConvo, saveConvo } = require('~/models'); +const { logger } = require('~/config'); router.use(requireJwtAuth); @@ -30,7 +31,7 @@ router.post('/clear', async (req, res) => { } // for debugging deletion source - // console.log('source:', source); + // logger.debug('source:', source); if (source === 'button' && !conversationId) { return res.status(200).send('No conversationId provided'); @@ -40,7 +41,7 @@ router.post('/clear', async (req, res) => { const dbResponse = await deleteConvos(req.user.id, filter); res.status(201).send(dbResponse); } catch (error) { - console.error(error); + logger.error('Error clearing conversations', error); res.status(500).send(error); } }); @@ -52,7 +53,7 @@ router.post('/update', async (req, res) => { const dbResponse = await saveConvo(req.user.id, update); res.status(201).send(dbResponse); } catch (error) { - console.error(error); + logger.error('Error updating conversation', error); res.status(500).send(error); } }); diff --git a/api/server/routes/edit/gptPlugins.js b/api/server/routes/edit/gptPlugins.js index f396663503c..8ddf92c2507 100644 --- a/api/server/routes/edit/gptPlugins.js +++ b/api/server/routes/edit/gptPlugins.js @@ -1,8 +1,8 @@ const express = require('express'); const router = express.Router(); const { validateTools } = require('~/app'); -const { saveMessage, getConvoTitle, getConvo } = require('~/models'); const { getResponseSender } = require('librechat-data-provider'); +const { saveMessage, getConvoTitle, getConvo } = require('~/models'); const { initializeClient } = require('~/server/services/Endpoints/gptPlugins'); const { sendMessage, createOnProgress, formatSteps, formatAction } = require('~/server/utils'); const { @@ -12,8 +12,11 @@ const { setHeaders, validateEndpoint, buildEndpointOption, + moderateText, } = require('~/server/middleware'); +const { logger } = require('~/config'); +router.use(moderateText); router.post('/abort', handleAbort()); router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => { @@ -27,8 +30,14 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, parentMessageId = null, overrideParentMessageId = null, } = req.body; - console.log('edit log'); - console.dir({ text, generation, isContinued, conversationId, endpointOption }, { depth: null }); + + logger.debug('[/edit/gptPlugins]', { + text, + generation, + isContinued, + conversationId, + ...endpointOption, + }); let metadata; let userMessage; let promptTokens; @@ -81,7 +90,6 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, text: partialText, model: endpointOption.modelOptions.model, unfinished: true, - cancelled: false, isEdited: true, error: false, user, @@ -102,7 +110,7 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, saveMessage({ ...userMessage, user }); } sendIntermediateMessage(res, { plugin }); - // console.log('PLUGIN ACTION', formattedAction); + // logger.debug('PLUGIN ACTION', formattedAction); }; const onChainEnd = (data) => { @@ -111,7 +119,7 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, plugin.loading = false; saveMessage({ ...userMessage, user }); sendIntermediateMessage(res, { plugin }); - // console.log('CHAIN END', plugin.outputs); + // logger.debug('CHAIN END', plugin.outputs); }; const getAbortData = () => ({ @@ -162,8 +170,7 @@ router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, response = { ...response, ...metadata }; } - console.log('CLIENT RESPONSE'); - console.dir(response, { depth: null }); + logger.debug('[/edit/gptPlugins] CLIENT RESPONSE', response); response.plugin = { ...plugin, loading: false }; await saveMessage({ ...response, user }); diff --git a/api/server/routes/edit/openAI.js b/api/server/routes/edit/openAI.js index 11e993903a8..e54881148dc 100644 --- a/api/server/routes/edit/openAI.js +++ b/api/server/routes/edit/openAI.js @@ -1,142 +1,20 @@ const express = require('express'); -const router = express.Router(); -const { getResponseSender } = require('librechat-data-provider'); +const EditController = require('~/server/controllers/EditController'); const { initializeClient } = require('~/server/services/Endpoints/openAI'); -const { saveMessage, getConvoTitle, getConvo } = require('~/models'); -const { sendMessage, createOnProgress } = require('~/server/utils'); const { handleAbort, - createAbortController, - handleAbortError, setHeaders, validateEndpoint, buildEndpointOption, + moderateText, } = require('~/server/middleware'); +const router = express.Router(); +router.use(moderateText); router.post('/abort', handleAbort()); -router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => { - let { - text, - generation, - endpointOption, - conversationId, - responseMessageId, - isContinued = false, - parentMessageId = null, - overrideParentMessageId = null, - } = req.body; - console.log('edit log'); - console.dir({ text, generation, isContinued, conversationId, endpointOption }, { depth: null }); - let metadata; - let userMessage; - let promptTokens; - let lastSavedTimestamp = 0; - let saveDelay = 100; - const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model }); - const userMessageId = parentMessageId; - const user = req.user.id; - - const addMetadata = (data) => (metadata = data); - const getReqData = (data = {}) => { - for (let key in data) { - if (key === 'userMessage') { - userMessage = data[key]; - } else if (key === 'responseMessageId') { - responseMessageId = data[key]; - } else if (key === 'promptTokens') { - promptTokens = data[key]; - } - } - }; - - const { onProgress: progressCallback, getPartialText } = createOnProgress({ - generation, - onProgress: ({ text: partialText }) => { - const currentTimestamp = Date.now(); - - if (currentTimestamp - lastSavedTimestamp > saveDelay) { - lastSavedTimestamp = currentTimestamp; - saveMessage({ - messageId: responseMessageId, - sender, - conversationId, - parentMessageId: overrideParentMessageId || userMessageId, - text: partialText, - model: endpointOption.modelOptions.model, - unfinished: true, - cancelled: false, - isEdited: true, - error: false, - user, - }); - } - - if (saveDelay < 500) { - saveDelay = 500; - } - }, - }); - - const getAbortData = () => ({ - sender, - conversationId, - messageId: responseMessageId, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: getPartialText(), - userMessage, - promptTokens, - }); - - const { abortController, onStart } = createAbortController(req, res, getAbortData); - - try { - const { client } = await initializeClient({ req, res, endpointOption }); - - let response = await client.sendMessage(text, { - user, - generation, - isContinued, - isEdited: true, - conversationId, - parentMessageId, - responseMessageId, - overrideParentMessageId, - getReqData, - onStart, - addMetadata, - abortController, - onProgress: progressCallback.call(null, { - res, - text, - parentMessageId: overrideParentMessageId || userMessageId, - }), - }); - - if (metadata) { - response = { ...response, ...metadata }; - } - - await saveMessage({ ...response, user }); - - sendMessage(res, { - title: await getConvoTitle(user, conversationId), - final: true, - conversation: await getConvo(user, conversationId), - requestMessage: userMessage, - responseMessage: response, - }); - res.end(); - } catch (error) { - const partialText = getPartialText(); - handleAbortError(res, req, error, { - partialText, - conversationId, - sender, - messageId: responseMessageId, - parentMessageId: userMessageId ?? parentMessageId, - }); - } +router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => { + await EditController(req, res, next, initializeClient); }); module.exports = router; diff --git a/api/server/routes/files/avatar.js b/api/server/routes/files/avatar.js new file mode 100644 index 00000000000..a7bb07c0f95 --- /dev/null +++ b/api/server/routes/files/avatar.js @@ -0,0 +1,34 @@ +const express = require('express'); +const multer = require('multer'); + +const uploadAvatar = require('~/server/services/Files/images/avatar/uploadAvatar'); +const { requireJwtAuth } = require('~/server/middleware/'); +const User = require('~/models/User'); + +const upload = multer(); +const router = express.Router(); + +router.post('/', requireJwtAuth, upload.single('input'), async (req, res) => { + try { + const userId = req.user.id; + const { manual } = req.body; + const input = req.file.buffer; + if (!userId) { + throw new Error('User ID is undefined'); + } + + // TODO: do not use Model directly, instead use a service method that uses the model + const user = await User.findById(userId).lean(); + + if (!user) { + throw new Error('User not found'); + } + const url = await uploadAvatar(userId, input, manual); + + res.json({ url }); + } catch (error) { + res.status(500).json({ message: 'An error occurred while uploading the profile picture' }); + } +}); + +module.exports = router; diff --git a/api/server/routes/files/files.js b/api/server/routes/files/files.js index b1f4622eb35..d9df1bdd759 100644 --- a/api/server/routes/files/files.js +++ b/api/server/routes/files/files.js @@ -1,8 +1,9 @@ const { z } = require('zod'); +const path = require('path'); const fs = require('fs').promises; const express = require('express'); const { deleteFiles } = require('~/models'); -const path = require('path'); +const { logger } = require('~/config'); const router = express.Router(); @@ -55,7 +56,7 @@ router.delete('/', async (req, res) => { await Promise.all(promises); res.status(200).json({ message: 'Files deleted successfully' }); } catch (error) { - console.error('Error deleting files:', error); + logger.error('[/files] Error deleting files:', error); res.status(400).json({ message: 'Error in request', error: error.message }); } }); diff --git a/api/server/routes/files/images.js b/api/server/routes/files/images.js index b8aaa6bc4d5..f88b7f2c7a2 100644 --- a/api/server/routes/files/images.js +++ b/api/server/routes/files/images.js @@ -3,6 +3,7 @@ const fs = require('fs').promises; const express = require('express'); const upload = require('./multer'); const { localStrategy } = require('~/server/services/Files'); +const { logger } = require('~/config'); const router = express.Router(); @@ -35,11 +36,11 @@ router.post('/', upload.single('file'), async (req, res) => { metadata.file_id = req.file_id; await localStrategy({ req, res, file, metadata }); } catch (error) { - console.error('Error processing file:', error); + logger.error('[/files/images] Error processing file:', error); try { await fs.unlink(file.path); } catch (error) { - console.error('Error deleting file:', error); + logger.error('[/files/images] Error deleting file:', error); } res.status(500).json({ message: 'Error processing file' }); } @@ -49,7 +50,7 @@ router.post('/', upload.single('file'), async (req, res) => { // try { // // await fs.unlink(file.path); // } catch (error) { - // console.error('Error deleting file:', error); + // logger.error('[/files/images] Error deleting file:', error); // } // } diff --git a/api/server/routes/files/index.js b/api/server/routes/files/index.js index 34c7dc62e3a..74b200c8066 100644 --- a/api/server/routes/files/index.js +++ b/api/server/routes/files/index.js @@ -18,5 +18,6 @@ router.use(uaParser); router.use('/', files); router.use('/images', images); +router.use('/images/avatar', require('./avatar')); module.exports = router; diff --git a/api/server/routes/oauth.js b/api/server/routes/oauth.js index e4085393448..816fc7200f3 100644 --- a/api/server/routes/oauth.js +++ b/api/server/routes/oauth.js @@ -1,8 +1,10 @@ const passport = require('passport'); const express = require('express'); const router = express.Router(); -const { setAuthTokens } = require('../services/AuthService'); -const { loginLimiter, checkBan } = require('../middleware'); +const { setAuthTokens } = require('~/server/services/AuthService'); +const { loginLimiter, checkBan } = require('~/server/middleware'); +const { logger } = require('~/config'); + const domains = { client: process.env.DOMAIN_CLIENT, server: process.env.DOMAIN_SERVER, @@ -19,7 +21,7 @@ const oauthHandler = async (req, res) => { await setAuthTokens(req.user._id, res); res.redirect(domains.client); } catch (err) { - console.error('Error in setting authentication tokens:', err); + logger.error('Error in setting authentication tokens:', err); } }; diff --git a/api/server/routes/presets.js b/api/server/routes/presets.js index e21d2df9d30..76aaed698cd 100644 --- a/api/server/routes/presets.js +++ b/api/server/routes/presets.js @@ -1,8 +1,10 @@ const express = require('express'); -const router = express.Router(); -const { getPresets, savePreset, deletePresets } = require('../../models'); const crypto = require('crypto'); -const requireJwtAuth = require('../middleware/requireJwtAuth'); +const { getPresets, savePreset, deletePresets } = require('~/models'); +const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); +const { logger } = require('~/config'); + +const router = express.Router(); router.get('/', requireJwtAuth, async (req, res) => { const presets = (await getPresets(req.user.id)).map((preset) => preset); @@ -18,7 +20,7 @@ router.post('/', requireJwtAuth, async (req, res) => { const preset = await savePreset(req.user.id, update); res.status(201).send(preset); } catch (error) { - console.error(error); + logger.error('[/presets] error saving preset', error); res.status(500).send(error); } }); @@ -31,13 +33,13 @@ router.post('/delete', requireJwtAuth, async (req, res) => { filter = { presetId }; } - console.log('delete preset filter', filter); + logger.debug('[/presets/delete] delete preset filter', filter); try { const deleteCount = await deletePresets(req.user.id, filter); res.status(201).send(deleteCount); } catch (error) { - console.error(error); + logger.error('[/presets/delete] error deleting presets', error); res.status(500).send(error); } }); diff --git a/api/server/routes/search.js b/api/server/routes/search.js index 98720a2ae5c..2197b38ce49 100644 --- a/api/server/routes/search.js +++ b/api/server/routes/search.js @@ -1,14 +1,16 @@ const Keyv = require('keyv'); const express = require('express'); -const router = express.Router(); const { MeiliSearch } = require('meilisearch'); -const { Message } = require('../../models/Message'); -const { Conversation, getConvosQueried } = require('../../models/Conversation'); -const { reduceHits } = require('../../lib/utils/reduceHits'); -const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc'); -const requireJwtAuth = require('../middleware/requireJwtAuth'); -const keyvRedis = require('../../cache/keyvRedis'); -const { isEnabled } = require('../utils'); +const { Conversation, getConvosQueried } = require('~/models/Conversation'); +const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); +const { cleanUpPrimaryKeyValue } = require('~/lib/utils/misc'); +const { reduceHits } = require('~/lib/utils/reduceHits'); +const { isEnabled } = require('~/server/utils'); +const { Message } = require('~/models/Message'); +const keyvRedis = require('~/cache/keyvRedis'); +const { logger } = require('~/config'); + +const router = express.Router(); const expiration = 60 * 1000; const cache = isEnabled(process.env.USE_REDIS) @@ -31,7 +33,7 @@ router.get('/', async function (req, res) { const key = `${user}:search:${q}`; const cached = await cache.get(key); if (cached) { - console.log('cache hit', key); + logger.debug('[/search] cache hit: ' + key); const { pages, pageSize, messages } = cached; res .status(200) @@ -39,7 +41,6 @@ router.get('/', async function (req, res) { return; } - // const message = await Message.meiliSearch(q); const messages = ( await Message.meiliSearch( q, @@ -61,8 +62,8 @@ router.get('/', async function (req, res) { const titles = (await Conversation.meiliSearch(q)).hits; const sortedHits = reduceHits(messages, titles); // debugging: - // console.log('user:', user, 'message hits:', messages.length, 'convo hits:', titles.length); - // console.log('sorted hits:', sortedHits.length); + // logger.debug('user:', user, 'message hits:', messages.length, 'convo hits:', titles.length); + // logger.debug('sorted hits:', sortedHits.length); const result = await getConvosQueried(user, sortedHits, pageNumber); const activeMessages = []; @@ -86,10 +87,10 @@ router.get('/', async function (req, res) { } delete result.convoMap; // for debugging - // console.log(result, messages.length); + // logger.debug(result, messages.length); res.status(200).send(result); } catch (error) { - console.log(error); + logger.error('[/search] Error while searching messages & conversations', error); res.status(500).send({ message: 'Error searching' }); } }); @@ -114,11 +115,9 @@ router.get('/enable', async function (req, res) { }); const { status } = await client.health(); - // console.log(`Meilisearch: ${status}`); result = status === 'available' && !!process.env.SEARCH; return res.send(result); } catch (error) { - // console.error(error); return res.send(false); } }); diff --git a/api/server/routes/tokenizer.js b/api/server/routes/tokenizer.js index fd66c20e620..581f82bf2ad 100644 --- a/api/server/routes/tokenizer.js +++ b/api/server/routes/tokenizer.js @@ -1,7 +1,8 @@ const express = require('express'); const router = express.Router(); -const { countTokens } = require('../utils'); -const requireJwtAuth = require('../middleware/requireJwtAuth'); +const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); +const { countTokens } = require('~/server/utils'); +const { logger } = require('~/config'); router.post('/', requireJwtAuth, async (req, res) => { try { @@ -9,7 +10,7 @@ router.post('/', requireJwtAuth, async (req, res) => { const count = await countTokens(arg?.text ?? arg); res.send({ count }); } catch (e) { - console.error(e); + logger.error('[/tokenizer] Error counting tokens', e); res.status(500).send(e.message); } }); diff --git a/api/server/services/AuthService.js b/api/server/services/AuthService.js index 363042a753c..a60ae370efe 100644 --- a/api/server/services/AuthService.js +++ b/api/server/services/AuthService.js @@ -1,10 +1,12 @@ const crypto = require('crypto'); const bcrypt = require('bcryptjs'); -const User = require('../../models/User'); -const Session = require('../../models/Session'); -const Token = require('../../models/schema/tokenSchema'); -const { registerSchema, errorsToString } = require('../../strategies/validators'); -const { sendEmail } = require('../utils'); +const { registerSchema, errorsToString } = require('~/strategies/validators'); +const Token = require('~/models/schema/tokenSchema'); +const { sendEmail } = require('~/server/utils'); +const Session = require('~/models/Session'); +const { logger } = require('~/config'); +const User = require('~/models/User'); + const domains = { client: process.env.DOMAIN_CLIENT, server: process.env.DOMAIN_SERVER, @@ -29,7 +31,7 @@ const logoutUser = async (userId, refreshToken) => { try { await Session.deleteOne({ _id: session._id }); } catch (deleteErr) { - console.error(deleteErr); + logger.error('[logoutUser] Failed to delete session.', deleteErr); return { status: 500, message: 'Failed to delete session.' }; } } @@ -50,7 +52,7 @@ const registerUser = async (user) => { const { error } = registerSchema.safeParse(user); if (error) { const errorMessage = errorsToString(error.errors); - console.info( + logger.info( 'Route: register - Validation Error', { name: 'Request params:', value: user }, { name: 'Validation error:', value: errorMessage }, @@ -65,7 +67,7 @@ const registerUser = async (user) => { const existingUser = await User.findOne({ email }).lean(); if (existingUser) { - console.info( + logger.info( 'Register User - Email in use', { name: 'Request params:', value: user }, { name: 'Existing user:', value: existingUser }, @@ -229,7 +231,7 @@ const setAuthTokens = async (userId, res, sessionId = null) => { return token; } catch (error) { - console.log('Error in setting authentication tokens:', error); + logger.error('[setAuthTokens] Error in setting authentication tokens:', error); throw error; } }; diff --git a/api/server/services/Config/loadAsyncEndpoints.js b/api/server/services/Config/loadAsyncEndpoints.js index c06e5f1c92e..9e92f487fad 100644 --- a/api/server/services/Config/loadAsyncEndpoints.js +++ b/api/server/services/Config/loadAsyncEndpoints.js @@ -8,9 +8,9 @@ const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, go */ async function loadAsyncEndpoints() { let i = 0; - let key, googleUserProvides; + let serviceKey, googleUserProvides; try { - key = require('~/data/auth.json'); + serviceKey = require('~/data/auth.json'); } catch (e) { if (i === 0) { i++; @@ -33,7 +33,7 @@ async function loadAsyncEndpoints() { } const plugins = transformToolsToMap(tools); - const google = key || googleUserProvides ? { userProvide: googleUserProvides } : false; + const google = serviceKey || googleKey ? { userProvide: googleUserProvides } : false; const gptPlugins = openAIApiKey || azureOpenAIApiKey diff --git a/api/server/services/Config/loadDefaultModels.js b/api/server/services/Config/loadDefaultModels.js index be140476034..665aa714790 100644 --- a/api/server/services/Config/loadDefaultModels.js +++ b/api/server/services/Config/loadDefaultModels.js @@ -1,16 +1,18 @@ +const { EModelEndpoint } = require('librechat-data-provider'); +const { useAzurePlugins } = require('~/server/services/Config/EndpointService').config; const { getOpenAIModels, - getChatGPTBrowserModels, + getGoogleModels, getAnthropicModels, + getChatGPTBrowserModels, } = require('~/server/services/ModelService'); -const { EModelEndpoint } = require('librechat-data-provider'); -const { useAzurePlugins } = require('~/server/services/Config/EndpointService').config; const fitlerAssistantModels = (str) => { return /gpt-4|gpt-3\\.5/i.test(str) && !/vision|instruct/i.test(str); }; async function loadDefaultModels() { + const google = getGoogleModels(); const openAI = await getOpenAIModels(); const anthropic = getAnthropicModels(); const chatGPTBrowser = getChatGPTBrowserModels(); @@ -19,24 +21,13 @@ async function loadDefaultModels() { return { [EModelEndpoint.openAI]: openAI, + [EModelEndpoint.google]: google, + [EModelEndpoint.anthropic]: anthropic, + [EModelEndpoint.gptPlugins]: gptPlugins, [EModelEndpoint.azureOpenAI]: azureOpenAI, - [EModelEndpoint.assistant]: openAI.filter(fitlerAssistantModels), - [EModelEndpoint.google]: [ - 'chat-bison', - 'chat-bison-32k', - 'codechat-bison', - 'codechat-bison-32k', - 'text-bison', - 'text-bison-32k', - 'text-unicorn', - 'code-gecko', - 'code-bison', - 'code-bison-32k', - ], [EModelEndpoint.bingAI]: ['BingAI', 'Sydney'], [EModelEndpoint.chatGPTBrowser]: chatGPTBrowser, - [EModelEndpoint.gptPlugins]: gptPlugins, - [EModelEndpoint.anthropic]: anthropic, + [EModelEndpoint.assistant]: openAI.filter(fitlerAssistantModels), }; } diff --git a/api/server/services/Endpoints/google/initializeClient.js b/api/server/services/Endpoints/google/initializeClient.js index 8370d83ab5c..4e97c82ab60 100644 --- a/api/server/services/Endpoints/google/initializeClient.js +++ b/api/server/services/Endpoints/google/initializeClient.js @@ -1,5 +1,5 @@ const { GoogleClient } = require('~/app'); -const { EModelEndpoint } = require('librechat-data-provider'); +const { EModelEndpoint, AuthKeys } = require('librechat-data-provider'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); const initializeClient = async ({ req, res, endpointOption }) => { @@ -11,14 +11,26 @@ const initializeClient = async ({ req, res, endpointOption }) => { if (expiresAt && isUserProvided) { checkUserKeyExpiry( expiresAt, - 'Your Google key has expired. Please provide your JSON credentials again.', + 'Your Google Credentials have expired. Please provide your Service Account JSON Key or Generative Language API Key again.', ); userKey = await getUserKey({ userId: req.user.id, name: EModelEndpoint.google }); } - const apiKey = isUserProvided ? userKey : require('~/data/auth.json'); + let serviceKey = {}; + try { + serviceKey = require('~/data/auth.json'); + } catch (e) { + // Do nothing + } + + const credentials = isUserProvided + ? userKey + : { + [AuthKeys.GOOGLE_SERVICE_KEY]: serviceKey, + [AuthKeys.GOOGLE_API_KEY]: GOOGLE_KEY, + }; - const client = new GoogleClient(apiKey, { + const client = new GoogleClient(credentials, { req, res, reverseProxyUrl: GOOGLE_REVERSE_PROXY ?? null, @@ -28,7 +40,7 @@ const initializeClient = async ({ req, res, endpointOption }) => { return { client, - apiKey, + credentials, }; }; diff --git a/api/server/services/Endpoints/google/initializeClient.spec.js b/api/server/services/Endpoints/google/initializeClient.spec.js new file mode 100644 index 00000000000..8587c71e2d8 --- /dev/null +++ b/api/server/services/Endpoints/google/initializeClient.spec.js @@ -0,0 +1,84 @@ +const initializeClient = require('./initializeClient'); +const { GoogleClient } = require('~/app'); +const { checkUserKeyExpiry, getUserKey } = require('../../UserService'); + +jest.mock('../../UserService', () => ({ + checkUserKeyExpiry: jest.fn().mockImplementation((expiresAt, errorMessage) => { + if (new Date(expiresAt) < new Date()) { + throw new Error(errorMessage); + } + }), + getUserKey: jest.fn().mockImplementation(() => ({})), +})); + +describe('google/initializeClient', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should initialize GoogleClient with user-provided credentials', async () => { + process.env.GOOGLE_KEY = 'user_provided'; + process.env.GOOGLE_REVERSE_PROXY = 'http://reverse.proxy'; + process.env.PROXY = 'http://proxy'; + + const expiresAt = new Date(Date.now() + 60000).toISOString(); + + const req = { + body: { key: expiresAt }, + user: { id: '123' }, + }; + const res = {}; + const endpointOption = { modelOptions: { model: 'default-model' } }; + + const { client, credentials } = await initializeClient({ req, res, endpointOption }); + + expect(getUserKey).toHaveBeenCalledWith({ userId: '123', name: 'google' }); + expect(client).toBeInstanceOf(GoogleClient); + expect(client.options.reverseProxyUrl).toBe('http://reverse.proxy'); + expect(client.options.proxy).toBe('http://proxy'); + expect(credentials).toEqual({}); + }); + + test('should initialize GoogleClient with service key credentials', async () => { + process.env.GOOGLE_KEY = 'service_key'; + process.env.GOOGLE_REVERSE_PROXY = 'http://reverse.proxy'; + process.env.PROXY = 'http://proxy'; + + const req = { + body: { key: null }, + user: { id: '123' }, + }; + const res = {}; + const endpointOption = { modelOptions: { model: 'default-model' } }; + + const { client, credentials } = await initializeClient({ req, res, endpointOption }); + + expect(client).toBeInstanceOf(GoogleClient); + expect(client.options.reverseProxyUrl).toBe('http://reverse.proxy'); + expect(client.options.proxy).toBe('http://proxy'); + expect(credentials).toEqual({ + GOOGLE_SERVICE_KEY: {}, + GOOGLE_API_KEY: 'service_key', + }); + }); + + test('should handle expired user-provided key', async () => { + process.env.GOOGLE_KEY = 'user_provided'; + + const expiresAt = new Date(Date.now() - 10000).toISOString(); // Expired + const req = { + body: { key: expiresAt }, + user: { id: '123' }, + }; + const res = {}; + const endpointOption = { modelOptions: { model: 'default-model' } }; + + checkUserKeyExpiry.mockImplementation((expiresAt, errorMessage) => { + throw new Error(errorMessage); + }); + + await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow( + /Your Google Credentials have expired/, + ); + }); +}); diff --git a/api/server/services/Endpoints/openAI/addTitle.js b/api/server/services/Endpoints/openAI/addTitle.js index 3197374a68c..f630638643f 100644 --- a/api/server/services/Endpoints/openAI/addTitle.js +++ b/api/server/services/Endpoints/openAI/addTitle.js @@ -7,8 +7,8 @@ const addTitle = async (req, { text, response, client }) => { return; } - // If the request was aborted, don't generate the title. - if (client.abortController.signal.aborted) { + // If the request was aborted and is not azure, don't generate the title. + if (!client.azure && client.abortController.signal.aborted) { return; } diff --git a/api/server/services/Files/Firebase/images.js b/api/server/services/Files/Firebase/images.js new file mode 100644 index 00000000000..e04902c02fe --- /dev/null +++ b/api/server/services/Files/Firebase/images.js @@ -0,0 +1,45 @@ +const fetch = require('node-fetch'); +const { ref, uploadBytes, getDownloadURL } = require('firebase/storage'); +const { getFirebaseStorage } = require('./initialize'); + +async function saveImageToFirebaseStorage(userId, imageUrl, imageName) { + const storage = getFirebaseStorage(); + if (!storage) { + console.error('Firebase is not initialized. Cannot save image to Firebase Storage.'); + return null; + } + + const storageRef = ref(storage, `images/${userId.toString()}/${imageName}`); + + try { + // Upload image to Firebase Storage using the image URL + await uploadBytes(storageRef, await fetch(imageUrl).then((response) => response.buffer())); + return imageName; + } catch (error) { + console.error('Error uploading image to Firebase Storage:', error.message); + return null; + } +} + +async function getFirebaseStorageImageUrl(imageName) { + const storage = getFirebaseStorage(); + if (!storage) { + console.error('Firebase is not initialized. Cannot get image URL from Firebase Storage.'); + return null; + } + + const storageRef = ref(storage, `images/${imageName}`); + + try { + // Get the download URL for the image from Firebase Storage + return `![generated image](${await getDownloadURL(storageRef)})`; + } catch (error) { + console.error('Error fetching image URL from Firebase Storage:', error.message); + return null; + } +} + +module.exports = { + saveImageToFirebaseStorage, + getFirebaseStorageImageUrl, +}; diff --git a/api/server/services/Files/Firebase/index.js b/api/server/services/Files/Firebase/index.js new file mode 100644 index 00000000000..905bf660d4f --- /dev/null +++ b/api/server/services/Files/Firebase/index.js @@ -0,0 +1,7 @@ +const images = require('./images'); +const initialize = require('./initialize'); + +module.exports = { + ...images, + ...initialize, +}; diff --git a/api/server/services/Files/Firebase/initialize.js b/api/server/services/Files/Firebase/initialize.js new file mode 100644 index 00000000000..5dc1f937915 --- /dev/null +++ b/api/server/services/Files/Firebase/initialize.js @@ -0,0 +1,42 @@ +const firebase = require('firebase/app'); +const { getStorage } = require('firebase/storage'); +const { logger } = require('~/config'); + +let i = 0; +let firebaseApp = null; + +const initializeFirebase = () => { + // Return existing instance if already initialized + if (firebaseApp) { + return firebaseApp; + } + + const firebaseConfig = { + apiKey: process.env.FIREBASE_API_KEY, + authDomain: process.env.FIREBASE_AUTH_DOMAIN, + projectId: process.env.FIREBASE_PROJECT_ID, + storageBucket: process.env.FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.FIREBASE_APP_ID, + }; + + if (Object.values(firebaseConfig).some((value) => !value)) { + i === 0 && + logger.info( + '[Optional] Firebase configuration missing or incomplete. Firebase will not be initialized.', + ); + i++; + return null; + } + + firebaseApp = firebase.initializeApp(firebaseConfig); + logger.info('Firebase initialized'); + return firebaseApp; +}; + +const getFirebaseStorage = () => { + const app = initializeFirebase(); + return app ? getStorage(app) : null; +}; + +module.exports = { initializeFirebase, getFirebaseStorage }; diff --git a/api/server/services/Files/images/avatar/firebaseStrategy.js b/api/server/services/Files/images/avatar/firebaseStrategy.js new file mode 100644 index 00000000000..9c000b43ecc --- /dev/null +++ b/api/server/services/Files/images/avatar/firebaseStrategy.js @@ -0,0 +1,29 @@ +const { ref, uploadBytes, getDownloadURL } = require('firebase/storage'); +const { getFirebaseStorage } = require('~/server/services/Files/Firebase/initialize'); +const { logger } = require('~/config'); + +async function firebaseStrategy(userId, webPBuffer, oldUser, manual) { + try { + const storage = getFirebaseStorage(); + if (!storage) { + throw new Error('Firebase is not initialized.'); + } + const avatarRef = ref(storage, `images/${userId.toString()}/avatar`); + + await uploadBytes(avatarRef, webPBuffer); + const urlFirebase = await getDownloadURL(avatarRef); + const isManual = manual === 'true'; + + const url = `${urlFirebase}?manual=${isManual}`; + if (isManual) { + oldUser.avatar = url; + await oldUser.save(); + } + return url; + } catch (error) { + logger.error('Error uploading profile picture:', error); + throw error; + } +} + +module.exports = firebaseStrategy; diff --git a/api/server/services/Files/images/avatar/localStrategy.js b/api/server/services/Files/images/avatar/localStrategy.js new file mode 100644 index 00000000000..021beda7d13 --- /dev/null +++ b/api/server/services/Files/images/avatar/localStrategy.js @@ -0,0 +1,32 @@ +const fs = require('fs').promises; +const path = require('path'); + +async function localStrategy(userId, webPBuffer, oldUser, manual) { + const userDir = path.resolve( + __dirname, + '..', + '..', + '..', + '..', + '..', + '..', + 'client', + 'public', + 'images', + userId, + ); + let avatarPath = path.join(userDir, 'avatar.png'); + const urlRoute = `/images/${userId}/avatar.png`; + await fs.mkdir(userDir, { recursive: true }); + await fs.writeFile(avatarPath, webPBuffer); + const isManual = manual === 'true'; + let url = `${urlRoute}?manual=${isManual}×tamp=${new Date().getTime()}`; + if (isManual) { + oldUser.avatar = url; + await oldUser.save(); + } + + return url; +} + +module.exports = localStrategy; diff --git a/api/server/services/Files/images/avatar/uploadAvatar.js b/api/server/services/Files/images/avatar/uploadAvatar.js new file mode 100644 index 00000000000..0726df9a4dd --- /dev/null +++ b/api/server/services/Files/images/avatar/uploadAvatar.js @@ -0,0 +1,63 @@ +const sharp = require('sharp'); +const fetch = require('node-fetch'); +const fs = require('fs').promises; +const User = require('~/models/User'); +const { getFirebaseStorage } = require('~/server/services/Files/Firebase/initialize'); +const firebaseStrategy = require('./firebaseStrategy'); +const localStrategy = require('./localStrategy'); +const { logger } = require('~/config'); + +async function convertToWebP(inputBuffer) { + return sharp(inputBuffer).resize({ width: 150 }).toFormat('webp').toBuffer(); +} + +async function uploadAvatar(userId, input, manual) { + try { + if (userId === undefined) { + throw new Error('User ID is undefined'); + } + const _id = userId; + // TODO: remove direct use of Model, `User` + const oldUser = await User.findOne({ _id }); + let imageBuffer; + if (typeof input === 'string') { + const response = await fetch(input); + + if (!response.ok) { + throw new Error(`Failed to fetch image from URL. Status: ${response.status}`); + } + imageBuffer = await response.buffer(); + } else if (input instanceof Buffer) { + imageBuffer = input; + } else if (typeof input === 'object' && input instanceof File) { + const fileContent = await fs.readFile(input.path); + imageBuffer = Buffer.from(fileContent); + } else { + throw new Error('Invalid input type. Expected URL, Buffer, or File.'); + } + const { width, height } = await sharp(imageBuffer).metadata(); + const minSize = Math.min(width, height); + const squaredBuffer = await sharp(imageBuffer) + .extract({ + left: Math.floor((width - minSize) / 2), + top: Math.floor((height - minSize) / 2), + width: minSize, + height: minSize, + }) + .toBuffer(); + const webPBuffer = await convertToWebP(squaredBuffer); + const storage = getFirebaseStorage(); + if (storage) { + const url = await firebaseStrategy(userId, webPBuffer, oldUser, manual); + return url; + } + + const url = await localStrategy(userId, webPBuffer, oldUser, manual); + return url; + } catch (error) { + logger.error('Error uploading the avatar:', error); + throw error; + } +} + +module.exports = uploadAvatar; diff --git a/api/server/services/Files/images/encode.js b/api/server/services/Files/images/encode.js index 90c14c051cb..30428ffabc1 100644 --- a/api/server/services/Files/images/encode.js +++ b/api/server/services/Files/images/encode.js @@ -1,7 +1,13 @@ const fs = require('fs'); const path = require('path'); +const { EModelEndpoint } = require('librechat-data-provider'); const { updateFile } = require('~/models'); +/** + * Encodes an image file to base64. + * @param {string} imagePath - The path to the image file. + * @returns {Promise} A promise that resolves with the base64 encoded image data. + */ function encodeImage(imagePath) { return new Promise((resolve, reject) => { fs.readFile(imagePath, (err, data) => { @@ -14,6 +20,12 @@ function encodeImage(imagePath) { }); } +/** + * Updates the file and encodes the image. + * @param {Object} req - The request object. + * @param {Object} file - The file object. + * @returns {Promise<[MongoFile, string]>} - A promise that resolves to an array of results from updateFile and encodeImage. + */ async function updateAndEncode(req, file) { const { publicPath, imageOutput } = req.app.locals.config; const userPath = path.join(imageOutput, req.user.id); @@ -29,7 +41,14 @@ async function updateAndEncode(req, file) { return await Promise.all(promises); } -async function encodeAndFormat(req, files) { +/** + * Encodes and formats the given files. + * @param {Express.Request} req - The request object. + * @param {Array} files - The array of files to encode and format. + * @param {EModelEndpoint} [endpoint] - Optional: The endpoint for the image. + * @returns {Promise} - A promise that resolves to the result object containing the encoded images and file details. + */ +async function encodeAndFormat(req, files, endpoint) { const promises = []; for (let file of files) { promises.push(updateAndEncode(req, file)); @@ -46,13 +65,19 @@ async function encodeAndFormat(req, files) { }; for (const [file, base64] of encodedImages) { - result.image_urls.push({ + const imagePart = { type: 'image_url', image_url: { url: `data:image/webp;base64,${base64}`, detail, }, - }); + }; + + if (endpoint && endpoint === EModelEndpoint.google) { + imagePart.image_url = imagePart.image_url.url; + } + + result.image_urls.push(imagePart); result.files.push({ file_id: file.file_id, diff --git a/api/server/services/Files/images/index.js b/api/server/services/Files/images/index.js index d5b818e937d..fa49eb95356 100644 --- a/api/server/services/Files/images/index.js +++ b/api/server/services/Files/images/index.js @@ -1,11 +1,15 @@ const convert = require('./convert'); const encode = require('./encode'); +const parse = require('./parse'); const resize = require('./resize'); const validate = require('./validate'); +const uploadAvatar = require('./avatar/uploadAvatar'); module.exports = { ...convert, ...encode, + ...parse, ...resize, ...validate, + uploadAvatar, }; diff --git a/api/server/services/Files/images/parse.js b/api/server/services/Files/images/parse.js new file mode 100644 index 00000000000..5a1113c97e4 --- /dev/null +++ b/api/server/services/Files/images/parse.js @@ -0,0 +1,27 @@ +const URL = require('url').URL; +const path = require('path'); + +const imageExtensionRegex = /\.(jpg|jpeg|png|gif|bmp|tiff|svg)$/i; + +/** + * Extracts the image basename from a given URL. + * + * @param {string} urlString - The URL string from which the image basename is to be extracted. + * @returns {string} The basename of the image file from the URL. + * Returns an empty string if the URL does not contain a valid image basename. + */ +function getImageBasename(urlString) { + try { + const url = new URL(urlString); + const basename = path.basename(url.pathname); + + return imageExtensionRegex.test(basename) ? basename : ''; + } catch (error) { + // If URL parsing fails, return an empty string + return ''; + } +} + +module.exports = { + getImageBasename, +}; diff --git a/api/server/services/Files/save.js b/api/server/services/Files/save.js index d598bf9b9fb..08f6a0d5cc0 100644 --- a/api/server/services/Files/save.js +++ b/api/server/services/Files/save.js @@ -1,5 +1,6 @@ const fs = require('fs'); const path = require('path'); +const { logger } = require('~/config'); /** * Saves a file to a specified output path with a new filename. @@ -24,7 +25,7 @@ async function saveFile(file, outputPath, outputFilename) { return outputFilePath; } catch (error) { - console.error('Error while saving the file:', error); + logger.error('[saveFile] Error while saving the file:', error); throw error; } } diff --git a/api/server/services/ModelService.js b/api/server/services/ModelService.js index 009e9c65937..08c9ae71d29 100644 --- a/api/server/services/ModelService.js +++ b/api/server/services/ModelService.js @@ -1,18 +1,28 @@ -const HttpsProxyAgent = require('https-proxy-agent'); -const axios = require('axios'); const Keyv = require('keyv'); +const axios = require('axios'); +const HttpsProxyAgent = require('https-proxy-agent'); +const { EModelEndpoint, defaultModels } = require('librechat-data-provider'); const { isEnabled } = require('~/server/utils'); -const { extractBaseURL } = require('~/utils'); const keyvRedis = require('~/cache/keyvRedis'); +const { extractBaseURL } = require('~/utils'); +const { logger } = require('~/config'); + // const { getAzureCredentials, genAzureChatCompletion } = require('~/utils/'); + const { openAIApiKey, userProvidedOpenAI } = require('./Config/EndpointService').config; const modelsCache = isEnabled(process.env.USE_REDIS) ? new Keyv({ store: keyvRedis }) : new Keyv({ namespace: 'models' }); -const { OPENROUTER_API_KEY, OPENAI_REVERSE_PROXY, CHATGPT_MODELS, ANTHROPIC_MODELS, PROXY } = - process.env ?? {}; +const { + OPENROUTER_API_KEY, + OPENAI_REVERSE_PROXY, + CHATGPT_MODELS, + ANTHROPIC_MODELS, + GOOGLE_MODELS, + PROXY, +} = process.env ?? {}; const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _models = []) => { let models = _models.slice() ?? []; @@ -54,9 +64,9 @@ const fetchOpenAIModels = async (opts = { azure: false, plugins: false }, _model const res = await axios.get(`${basePath}${opts.azure ? '' : '/models'}`, payload); models = res.data.data.map((item) => item.id); - // console.log(`Fetched ${models.length} models from ${opts.azure ? 'Azure ' : ''}OpenAI API`); + // logger.debug(`Fetched ${models.length} models from ${opts.azure ? 'Azure ' : ''}OpenAI API`); } catch (err) { - console.log(`Failed to fetch models from ${opts.azure ? 'Azure ' : ''}OpenAI API`); + logger.error(`Failed to fetch models from ${opts.azure ? 'Azure ' : ''}OpenAI API`, err); } } @@ -114,15 +124,7 @@ const getChatGPTBrowserModels = () => { }; const getAnthropicModels = () => { - let models = [ - 'claude-2.1', - 'claude-2', - 'claude-1.2', - 'claude-1', - 'claude-1-100k', - 'claude-instant-1', - 'claude-instant-1-100k', - ]; + let models = defaultModels[EModelEndpoint.anthropic]; if (ANTHROPIC_MODELS) { models = String(ANTHROPIC_MODELS).split(','); } @@ -130,8 +132,18 @@ const getAnthropicModels = () => { return models; }; +const getGoogleModels = () => { + let models = defaultModels[EModelEndpoint.google]; + if (GOOGLE_MODELS) { + models = String(GOOGLE_MODELS).split(','); + } + + return models; +}; + module.exports = { getOpenAIModels, getChatGPTBrowserModels, getAnthropicModels, + getGoogleModels, }; diff --git a/api/server/services/PluginService.js b/api/server/services/PluginService.js index 8e8643cbfeb..1eaa6eedab5 100644 --- a/api/server/services/PluginService.js +++ b/api/server/services/PluginService.js @@ -1,5 +1,6 @@ -const PluginAuth = require('../../models/schema/pluginAuthSchema'); -const { encrypt, decrypt } = require('../utils/'); +const PluginAuth = require('~/models/schema/pluginAuthSchema'); +const { encrypt, decrypt } = require('~/server/utils/'); +const { logger } = require('~/config'); const getUserPluginAuthValue = async (user, authField) => { try { @@ -11,7 +12,7 @@ const getUserPluginAuthValue = async (user, authField) => { const decryptedValue = decrypt(pluginAuth.value); return decryptedValue; } catch (err) { - console.log(err); + logger.error('[getUserPluginAuthValue]', err); return err; } }; @@ -36,7 +37,7 @@ const getUserPluginAuthValue = async (user, authField) => { // return pluginAuth; // } catch (err) { -// console.log(err); +// logger.error('[getUserPluginAuthValue]', err); // return err; // } // }; @@ -62,7 +63,7 @@ const updateUserPluginAuth = async (userId, authField, pluginKey, value) => { return newPluginAuth; } } catch (err) { - console.log(err); + logger.error('[updateUserPluginAuth]', err); return err; } }; @@ -72,7 +73,7 @@ const deleteUserPluginAuth = async (userId, authField) => { const response = await PluginAuth.deleteOne({ userId, authField }); return response; } catch (err) { - console.log(err); + logger.error('[deleteUserPluginAuth]', err); return err; } }; diff --git a/api/server/services/Runs/RunMananger.js b/api/server/services/Runs/RunMananger.js index adc0e1819d4..67a3624c187 100644 --- a/api/server/services/Runs/RunMananger.js +++ b/api/server/services/Runs/RunMananger.js @@ -1,3 +1,5 @@ +const { logger } = require('~/config'); + /** * @typedef {import('openai').OpenAI} OpenAI * @typedef {import('../AssistantService').RunStep} RunStep @@ -84,8 +86,12 @@ class RunManager { return await this.handlers['final']({ step, runStatus, stepsByStatus: this.stepsByStatus }); } - console.log(`Default handler for ${step.id} with status \`${runStatus}\``); - console.dir({ step, runStatus, final, isLast }, { depth: null }); + logger.debug(`[RunManager] Default handler for ${step.id} with status \`${runStatus}\``, { + step, + runStatus, + final, + isLast, + }); return step; } } diff --git a/api/server/services/UserService.js b/api/server/services/UserService.js index c3a25f3b92d..4a9d3abe7b3 100644 --- a/api/server/services/UserService.js +++ b/api/server/services/UserService.js @@ -1,5 +1,6 @@ -const { User, Key } = require('../../models'); -const { encrypt, decrypt } = require('../utils'); +const { User, Key } = require('~/models'); +const { encrypt, decrypt } = require('~/server/utils'); +const { logger } = require('~/config'); const updateUserPluginsService = async (user, pluginKey, action) => { try { @@ -15,7 +16,7 @@ const updateUserPluginsService = async (user, pluginKey, action) => { ); } } catch (err) { - console.log(err); + logger.error('[updateUserPluginsService]', err); return err; } }; diff --git a/api/server/utils/countTokens.js b/api/server/utils/countTokens.js index cc40fdd7cf1..34c070aa8c2 100644 --- a/api/server/utils/countTokens.js +++ b/api/server/utils/countTokens.js @@ -1,18 +1,18 @@ const { Tiktoken } = require('tiktoken/lite'); -const { load } = require('tiktoken/load'); -const registry = require('tiktoken/registry.json'); -const models = require('tiktoken/model_to_encoding.json'); +const p50k_base = require('tiktoken/encoders/p50k_base.json'); +const cl100k_base = require('tiktoken/encoders/cl100k_base.json'); +const logger = require('~/config/winston'); const countTokens = async (text = '', modelName = 'gpt-3.5-turbo') => { let encoder = null; try { - const model = await load(registry[models[modelName]]); + const model = modelName.includes('text-davinci-003') ? p50k_base : cl100k_base; encoder = new Tiktoken(model.bpe_ranks, model.special_tokens, model.pat_str); const tokens = encoder.encode(text); encoder.free(); return tokens.length; } catch (e) { - console.error(e); + logger.error('[countTokens]', e); if (encoder) { encoder.free(); } diff --git a/api/server/utils/crypto.js b/api/server/utils/crypto.js index efa89de4fcc..9b5fed67c6b 100644 --- a/api/server/utils/crypto.js +++ b/api/server/utils/crypto.js @@ -1,3 +1,5 @@ +require('dotenv').config(); + const crypto = require('crypto'); const key = Buffer.from(process.env.CREDS_KEY, 'hex'); const iv = Buffer.from(process.env.CREDS_IV, 'hex'); diff --git a/api/server/utils/handleText.js b/api/server/utils/handleText.js index 3ae18e98c5e..4cd1b7ce994 100644 --- a/api/server/utils/handleText.js +++ b/api/server/utils/handleText.js @@ -1,6 +1,6 @@ const partialRight = require('lodash/partialRight'); -const { getCitations, citeText } = require('./citations'); const { sendMessage } = require('./streamResponse'); +const { getCitations, citeText } = require('./citations'); const cursor = ''; const citationRegex = /\[\^\d+?\^]/g; @@ -138,21 +138,31 @@ function formatAction(action) { } /** - * Checks if the given string value is truthy by comparing it to the string 'true' (case-insensitive). + * Checks if the given value is truthy by being either the boolean `true` or a string + * that case-insensitively matches 'true'. * * @function - * @param {string|null|undefined} value - The string value to check. - * @returns {boolean} Returns `true` if the value is a case-insensitive match for the string 'true', otherwise returns `false`. + * @param {string|boolean|null|undefined} value - The value to check. + * @returns {boolean} Returns `true` if the value is the boolean `true` or a case-insensitive + * match for the string 'true', otherwise returns `false`. * @example * * isEnabled("True"); // returns true * isEnabled("TRUE"); // returns true + * isEnabled(true); // returns true * isEnabled("false"); // returns false + * isEnabled(false); // returns false * isEnabled(null); // returns false * isEnabled(); // returns false */ function isEnabled(value) { - return value?.toLowerCase()?.trim() === 'true'; + if (typeof value === 'boolean') { + return value; + } + if (typeof value === 'string') { + return value.toLowerCase().trim() === 'true'; + } + return false; } module.exports = { diff --git a/api/server/utils/handleText.spec.js b/api/server/utils/handleText.spec.js new file mode 100644 index 00000000000..ea440a89a57 --- /dev/null +++ b/api/server/utils/handleText.spec.js @@ -0,0 +1,51 @@ +const { isEnabled } = require('./handleText'); + +describe('isEnabled', () => { + test('should return true when input is "true"', () => { + expect(isEnabled('true')).toBe(true); + }); + + test('should return true when input is "TRUE"', () => { + expect(isEnabled('TRUE')).toBe(true); + }); + + test('should return true when input is true', () => { + expect(isEnabled(true)).toBe(true); + }); + + test('should return false when input is "false"', () => { + expect(isEnabled('false')).toBe(false); + }); + + test('should return false when input is false', () => { + expect(isEnabled(false)).toBe(false); + }); + + test('should return false when input is null', () => { + expect(isEnabled(null)).toBe(false); + }); + + test('should return false when input is undefined', () => { + expect(isEnabled()).toBe(false); + }); + + test('should return false when input is an empty string', () => { + expect(isEnabled('')).toBe(false); + }); + + test('should return false when input is a whitespace string', () => { + expect(isEnabled(' ')).toBe(false); + }); + + test('should return false when input is a number', () => { + expect(isEnabled(123)).toBe(false); + }); + + test('should return false when input is an object', () => { + expect(isEnabled({})).toBe(false); + }); + + test('should return false when input is an array', () => { + expect(isEnabled([])).toBe(false); + }); +}); diff --git a/api/server/utils/math.js b/api/server/utils/math.js index 12c12c8ccdd..3cd0929890b 100644 --- a/api/server/utils/math.js +++ b/api/server/utils/math.js @@ -38,8 +38,7 @@ function math(str, fallbackValue) { if (fallback) { return fallbackValue; } - console.error('str', str); - throw new Error(`str did not evaluate to a number but to a ${typeof value}`); + throw new Error(`[math] str did not evaluate to a number but to a ${typeof value}`); } return value; diff --git a/api/server/utils/sendEmail.js b/api/server/utils/sendEmail.js index d230a90db85..2f85f89dcd1 100644 --- a/api/server/utils/sendEmail.js +++ b/api/server/utils/sendEmail.js @@ -1,7 +1,8 @@ -const nodemailer = require('nodemailer'); -const handlebars = require('handlebars'); const fs = require('fs'); const path = require('path'); +const nodemailer = require('nodemailer'); +const handlebars = require('handlebars'); +const logger = require('~/config/winston'); const sendEmail = async (email, subject, payload, template) => { try { @@ -58,15 +59,15 @@ const sendEmail = async (email, subject, payload, template) => { // Send email transporter.sendMail(options(), (error, info) => { if (error) { - console.log(error); + logger.error('[sendEmail]', error); return error; } else { - console.log(info); + logger.debug('[sendEmail]', info); return info; } }); } catch (error) { - console.log(error); + logger.error('[sendEmail]', error); return error; } }; diff --git a/api/server/utils/streamResponse.js b/api/server/utils/streamResponse.js index 2aaf9f6531b..1933839fac2 100644 --- a/api/server/utils/streamResponse.js +++ b/api/server/utils/streamResponse.js @@ -1,5 +1,6 @@ const crypto = require('crypto'); -const { saveMessage } = require('../../models/Message'); +const { saveMessage, getMessages } = require('~/models/Message'); +const { getConvo } = require('~/models/Conversation'); /** * Sends error data in Server Sent Events format and ends the response. @@ -15,7 +16,7 @@ const handleError = (res, message) => { * Sends message data in Server Sent Events format. * @param {object} res - - The server response. * @param {string} message - The message to be sent. - * @param {string} event - [Optional] The type of event. Default is 'message'. + * @param {'message' | 'error' | 'cancel'} event - [Optional] The type of event. Default is 'message'. */ const sendMessage = (res, message, event = 'message') => { if (message.length === 0) { @@ -32,19 +33,27 @@ const sendMessage = (res, message, event = 'message') => { * @param {function} callback - [Optional] The callback function to be executed. */ const sendError = async (res, options, callback) => { - const { user, sender, conversationId, messageId, parentMessageId, text, shouldSaveMessage } = - options; + const { + user, + sender, + conversationId, + messageId, + parentMessageId, + text, + shouldSaveMessage, + overrideProps = {}, + } = options; const errorMessage = { sender, messageId: messageId ?? crypto.randomUUID(), conversationId, parentMessageId, unfinished: false, - cancelled: false, error: true, final: true, text, isCreatedByUser: false, + ...overrideProps, }; if (callback && typeof callback === 'function') { await callback(); @@ -54,6 +63,17 @@ const sendError = async (res, options, callback) => { await saveMessage({ ...errorMessage, user }); } + if (!errorMessage.error) { + const requestMessage = { messageId: parentMessageId, conversationId }; + const query = await getMessages(requestMessage); + return sendMessage(res, { + final: true, + requestMessage: query?.[0] ? query[0] : requestMessage, + responseMessage: errorMessage, + conversation: await getConvo(user, conversationId), + }); + } + handleError(res, errorMessage); }; diff --git a/api/strategies/discordStrategy.js b/api/strategies/discordStrategy.js index 9a83c5b9ff1..994554200cd 100644 --- a/api/strategies/discordStrategy.js +++ b/api/strategies/discordStrategy.js @@ -1,50 +1,72 @@ const { Strategy: DiscordStrategy } = require('passport-discord'); -const User = require('../models/User'); +const { logger } = require('~/config'); +const User = require('~/models/User'); +const { useFirebase, uploadAvatar } = require('~/server/services/Files/images'); const discordLogin = async (accessToken, refreshToken, profile, cb) => { try { const email = profile.email; const discordId = profile.id; - const oldUser = await User.findOne({ - email, - }); + const oldUser = await User.findOne({ email }); const ALLOW_SOCIAL_REGISTRATION = process.env.ALLOW_SOCIAL_REGISTRATION?.toLowerCase() === 'true'; - let avatarURL; + let avatarUrl; + if (profile.avatar) { const format = profile.avatar.startsWith('a_') ? 'gif' : 'png'; - avatarURL = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`; + avatarUrl = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`; } else { const defaultAvatarNum = Number(profile.discriminator) % 5; - avatarURL = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNum}.png`; + avatarUrl = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNum}.png`; } if (oldUser) { - oldUser.avatar = avatarURL; - await oldUser.save(); + await handleExistingUser(oldUser, avatarUrl, useFirebase); return cb(null, oldUser); - } else if (ALLOW_SOCIAL_REGISTRATION) { - const newUser = await new User({ - provider: 'discord', - discordId, - username: profile.username, - email, - name: profile.global_name, - avatar: avatarURL, - }).save(); + } + if (ALLOW_SOCIAL_REGISTRATION) { + const newUser = await createNewUser(profile, discordId, email, avatarUrl, useFirebase); return cb(null, newUser); } - - return cb(null, false, { - message: 'User not found.', - }); } catch (err) { - console.error(err); + logger.error('[discordLogin]', err); return cb(err); } }; +const handleExistingUser = async (oldUser, avatarUrl, useFirebase) => { + if (!useFirebase && !oldUser.avatar.includes('?manual=true')) { + oldUser.avatar = avatarUrl; + await oldUser.save(); + } else if (useFirebase && !oldUser.avatar.includes('?manual=true')) { + const userId = oldUser._id; + const newavatarUrl = await uploadAvatar(userId, avatarUrl); + oldUser.avatar = newavatarUrl; + await oldUser.save(); + } +}; + +const createNewUser = async (profile, discordId, email, avatarUrl, useFirebase) => { + const newUser = await new User({ + provider: 'discord', + discordId, + username: profile.username, + email, + name: profile.global_name, + avatar: avatarUrl, + }).save(); + + if (useFirebase) { + const userId = newUser._id; + const newavatarUrl = await uploadAvatar(userId, avatarUrl); + newUser.avatar = newavatarUrl; + await newUser.save(); + } + + return newUser; +}; + module.exports = () => new DiscordStrategy( { diff --git a/api/strategies/facebookStrategy.js b/api/strategies/facebookStrategy.js index b757f0a7cdd..b8915b2cc4b 100644 --- a/api/strategies/facebookStrategy.js +++ b/api/strategies/facebookStrategy.js @@ -1,42 +1,64 @@ const FacebookStrategy = require('passport-facebook').Strategy; -const User = require('../models/User'); +const { logger } = require('~/config'); +const User = require('~/models/User'); +const { useFirebase, uploadAvatar } = require('~/server/services/Files/images'); const facebookLogin = async (accessToken, refreshToken, profile, cb) => { try { const email = profile.emails[0]?.value; const facebookId = profile.id; - const oldUser = await User.findOne({ - email, - }); + const oldUser = await User.findOne({ email }); const ALLOW_SOCIAL_REGISTRATION = process.env.ALLOW_SOCIAL_REGISTRATION?.toLowerCase() === 'true'; + const avatarUrl = profile.photos[0]?.value; if (oldUser) { - oldUser.avatar = profile.photo; - await oldUser.save(); + await handleExistingUser(oldUser, avatarUrl, useFirebase); return cb(null, oldUser); - } else if (ALLOW_SOCIAL_REGISTRATION) { - const newUser = await new User({ - provider: 'facebook', - facebookId, - username: profile.displayName, - email, - name: profile.name?.givenName + ' ' + profile.name?.familyName, - avatar: profile.photos[0]?.value, - }).save(); + } + if (ALLOW_SOCIAL_REGISTRATION) { + const newUser = await createNewUser(profile, facebookId, email, avatarUrl, useFirebase); return cb(null, newUser); } - - return cb(null, false, { - message: 'User not found.', - }); } catch (err) { - console.error(err); + logger.error('[facebookLogin]', err); return cb(err); } }; +const handleExistingUser = async (oldUser, avatarUrl, useFirebase) => { + if (!useFirebase && !oldUser.avatar.includes('?manual=true')) { + oldUser.avatar = avatarUrl; + await oldUser.save(); + } else if (useFirebase && !oldUser.avatar.includes('?manual=true')) { + const userId = oldUser._id; + const newavatarUrl = await uploadAvatar(userId, avatarUrl); + oldUser.avatar = newavatarUrl; + await oldUser.save(); + } +}; + +const createNewUser = async (profile, facebookId, email, avatarUrl, useFirebase) => { + const newUser = await new User({ + provider: 'facebook', + facebookId, + username: profile.displayName, + email, + name: profile.name?.givenName + ' ' + profile.name?.familyName, + avatar: avatarUrl, + }).save(); + + if (useFirebase) { + const userId = newUser._id; + const newavatarUrl = await uploadAvatar(userId, avatarUrl); + newUser.avatar = newavatarUrl; + await newUser.save(); + } + + return newUser; +}; + module.exports = () => new FacebookStrategy( { diff --git a/api/strategies/githubStrategy.js b/api/strategies/githubStrategy.js index 2c8087203a3..c8480d50c13 100644 --- a/api/strategies/githubStrategy.js +++ b/api/strategies/githubStrategy.js @@ -1,5 +1,7 @@ const { Strategy: GitHubStrategy } = require('passport-github2'); -const User = require('../models/User'); +const { logger } = require('~/config'); +const User = require('~/models/User'); +const { useFirebase, uploadAvatar } = require('~/server/services/Files/images'); const githubLogin = async (accessToken, refreshToken, profile, cb) => { try { @@ -8,32 +10,56 @@ const githubLogin = async (accessToken, refreshToken, profile, cb) => { const oldUser = await User.findOne({ email }); const ALLOW_SOCIAL_REGISTRATION = process.env.ALLOW_SOCIAL_REGISTRATION?.toLowerCase() === 'true'; + const avatarUrl = profile.photos[0].value; if (oldUser) { - oldUser.avatar = profile.photos[0].value; - await oldUser.save(); + await handleExistingUser(oldUser, avatarUrl, useFirebase); return cb(null, oldUser); - } else if (ALLOW_SOCIAL_REGISTRATION) { - const newUser = await new User({ - provider: 'github', - githubId, - username: profile.username, - email, - emailVerified: profile.emails[0].verified, - name: profile.displayName, - avatar: profile.photos[0].value, - }).save(); + } + if (ALLOW_SOCIAL_REGISTRATION) { + const newUser = await createNewUser(profile, githubId, email, avatarUrl, useFirebase); return cb(null, newUser); } - - return cb(null, false, { message: 'User not found.' }); } catch (err) { - console.error(err); + logger.error('[githubLogin]', err); return cb(err); } }; +const handleExistingUser = async (oldUser, avatarUrl, useFirebase) => { + if (!useFirebase && !oldUser.avatar.includes('?manual=true')) { + oldUser.avatar = avatarUrl; + await oldUser.save(); + } else if (useFirebase && !oldUser.avatar.includes('?manual=true')) { + const userId = oldUser._id; + const avatarURL = await uploadAvatar(userId, avatarUrl); + oldUser.avatar = avatarURL; + await oldUser.save(); + } +}; + +const createNewUser = async (profile, githubId, email, avatarUrl, useFirebase) => { + const newUser = await new User({ + provider: 'github', + githubId, + username: profile.username, + email, + emailVerified: profile.emails[0].verified, + name: profile.displayName, + avatar: avatarUrl, + }).save(); + + if (useFirebase) { + const userId = newUser._id; + const avatarURL = await uploadAvatar(userId, avatarUrl); + newUser.avatar = avatarURL; + await newUser.save(); + } + + return newUser; +}; + module.exports = () => new GitHubStrategy( { diff --git a/api/strategies/googleStrategy.js b/api/strategies/googleStrategy.js index c41142f5cec..d013cc8e8fd 100644 --- a/api/strategies/googleStrategy.js +++ b/api/strategies/googleStrategy.js @@ -1,5 +1,7 @@ const { Strategy: GoogleStrategy } = require('passport-google-oauth20'); -const User = require('../models/User'); +const { logger } = require('~/config'); +const User = require('~/models/User'); +const { useFirebase, uploadAvatar } = require('~/server/services/Files/images'); const googleLogin = async (accessToken, refreshToken, profile, cb) => { try { @@ -8,32 +10,56 @@ const googleLogin = async (accessToken, refreshToken, profile, cb) => { const oldUser = await User.findOne({ email }); const ALLOW_SOCIAL_REGISTRATION = process.env.ALLOW_SOCIAL_REGISTRATION?.toLowerCase() === 'true'; + const avatarUrl = profile.photos[0].value; if (oldUser) { - oldUser.avatar = profile.photos[0].value; - await oldUser.save(); + await handleExistingUser(oldUser, avatarUrl, useFirebase); return cb(null, oldUser); - } else if (ALLOW_SOCIAL_REGISTRATION) { - const newUser = await new User({ - provider: 'google', - googleId, - username: profile.name.givenName, - email, - emailVerified: profile.emails[0].verified, - name: `${profile.name.givenName} ${profile.name.familyName}`, - avatar: profile.photos[0].value, - }).save(); + } + if (ALLOW_SOCIAL_REGISTRATION) { + const newUser = await createNewUser(profile, googleId, email, avatarUrl, useFirebase); return cb(null, newUser); } - - return cb(null, false, { message: 'User not found.' }); } catch (err) { - console.error(err); + logger.error('[googleLogin]', err); return cb(err); } }; +const handleExistingUser = async (oldUser, avatarUrl, useFirebase) => { + if ((!useFirebase && !oldUser.avatar.includes('?manual=true')) || oldUser.avatar === null) { + oldUser.avatar = avatarUrl; + await oldUser.save(); + } else if (useFirebase && !oldUser.avatar.includes('?manual=true')) { + const userId = oldUser._id; + const avatarURL = await uploadAvatar(userId, avatarUrl); + oldUser.avatar = avatarURL; + await oldUser.save(); + } +}; + +const createNewUser = async (profile, googleId, email, avatarUrl, useFirebase) => { + const newUser = await new User({ + provider: 'google', + googleId, + username: profile.name.givenName, + email, + emailVerified: profile.emails[0].verified, + name: `${profile.name.givenName} ${profile.name.familyName}`, + avatar: avatarUrl, + }).save(); + + if (useFirebase) { + const userId = newUser._id; + const avatarURL = await uploadAvatar(userId, avatarUrl); + newUser.avatar = avatarURL; + await newUser.save(); + } + + return newUser; +}; + module.exports = () => new GoogleStrategy( { diff --git a/api/strategies/joseStrategy.js b/api/strategies/joseStrategy.js index a5ee5ee3ae5..83cad23ddfd 100644 --- a/api/strategies/joseStrategy.js +++ b/api/strategies/joseStrategy.js @@ -1,9 +1,11 @@ -/* const jose = require('jose'); -* No longer using this strategy as Bun now supports JWTs natively. +/* +const jose = require('jose'); +const { logger } = require('~/config'); +// No longer using this strategy as Bun now supports JWTs natively. const passportCustom = require('passport-custom'); const CustomStrategy = passportCustom.Strategy; -const User = require('../models/User'); +const User = require('~/models/User'); const joseLogin = async () => new CustomStrategy(async (req, done) => { @@ -23,15 +25,15 @@ const joseLogin = async () => if (user) { done(null, user); } else { - console.log('JoseJwtStrategy => no user found'); + logger.debug('JoseJwtStrategy => no user found'); done(null, false, { message: 'No user found' }); } } catch (err) { if (err?.code === 'ERR_JWT_EXPIRED') { - console.error('JoseJwtStrategy => token expired'); + logger.error('JoseJwtStrategy => token expired'); } else { - console.error('JoseJwtStrategy => error'); - console.error(err); + logger.error('JoseJwtStrategy => error'); + logger.error(err); } done(null, false, { message: 'Invalid token' }); } diff --git a/api/strategies/jwtStrategy.js b/api/strategies/jwtStrategy.js index d27124d21b2..8079ac3bcee 100644 --- a/api/strategies/jwtStrategy.js +++ b/api/strategies/jwtStrategy.js @@ -1,5 +1,6 @@ const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); -const User = require('../models/User'); +const { logger } = require('~/config'); +const User = require('~/models/User'); // JWT strategy const jwtLogin = async () => @@ -10,11 +11,11 @@ const jwtLogin = async () => }, async (payload, done) => { try { - const user = await User.findById(payload.id); + const user = await User.findById(payload?.id); if (user) { done(null, user); } else { - console.log('JwtStrategy => no user found'); + logger.warn('[jwtLogin] JwtStrategy => no user found: ' + payload?.id); done(null, false); } } catch (err) { diff --git a/api/strategies/openidStrategy.js b/api/strategies/openidStrategy.js index 8b9b14108f4..7219f24ba41 100644 --- a/api/strategies/openidStrategy.js +++ b/api/strategies/openidStrategy.js @@ -1,16 +1,16 @@ -const passport = require('passport'); -const { Issuer, Strategy: OpenIDStrategy } = require('openid-client'); -const axios = require('axios'); const fs = require('fs'); const path = require('path'); - -const User = require('../models/User'); +const axios = require('axios'); +const passport = require('passport'); +const { Issuer, Strategy: OpenIDStrategy } = require('openid-client'); +const { logger } = require('~/config'); +const User = require('~/models/User'); let crypto; try { crypto = require('node:crypto'); } catch (err) { - console.error('crypto support is disabled!'); + logger.error('[openidStrategy] crypto support is disabled!', err); } const downloadImage = async (url, imagePath, accessToken) => { @@ -29,7 +29,9 @@ const downloadImage = async (url, imagePath, accessToken) => { return `/images/openid/${fileName}`; } catch (error) { - console.error(`Error downloading image at URL "${url}": ${error}`); + logger.error( + `[openidStrategy] downloadImage: Error downloading image at URL "${url}": ${error}`, + ); return ''; } }; @@ -130,7 +132,7 @@ async function setupOpenId() { passport.use('openid', openidLogin); } catch (err) { - console.error(err); + logger.error('[openidStrategy]', err); } } diff --git a/api/test/__mocks__/auth.mock.json b/api/test/__mocks__/auth.mock.json new file mode 100644 index 00000000000..2b99c4c4081 --- /dev/null +++ b/api/test/__mocks__/auth.mock.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "", + "private_key_id": "", + "private_key": "", + "client_email": "", + "client_id": "", + "auth_uri": "", + "token_uri": "", + "auth_provider_x509_cert_url": "", + "client_x509_cert_url": "", + "universe_domain": "" +} diff --git a/api/test/__mocks__/fetchEventSource.js b/api/test/__mocks__/fetchEventSource.js new file mode 100644 index 00000000000..8f6d3cc5753 --- /dev/null +++ b/api/test/__mocks__/fetchEventSource.js @@ -0,0 +1,27 @@ +jest.mock('@waylaidwanderer/fetch-event-source', () => ({ + fetchEventSource: jest + .fn() + .mockImplementation((url, { onopen, onmessage, onclose, onerror, error }) => { + // Simulating the onopen event + onopen && onopen({ status: 200 }); + + // Simulating a few onmessage events + onmessage && + onmessage({ data: JSON.stringify({ message: 'First message' }), event: 'message' }); + onmessage && + onmessage({ data: JSON.stringify({ message: 'Second message' }), event: 'message' }); + onmessage && + onmessage({ data: JSON.stringify({ message: 'Third message' }), event: 'message' }); + + // Simulate the onclose event + onclose && onclose(); + + if (error) { + // Simulate the onerror event + onerror && onerror({ status: 500 }); + } + + // Return a Promise that resolves to simulate async behavior + return Promise.resolve(); + }), +})); diff --git a/api/test/__mocks__/logger.js b/api/test/__mocks__/logger.js new file mode 100644 index 00000000000..455ada0de0d --- /dev/null +++ b/api/test/__mocks__/logger.js @@ -0,0 +1,10 @@ +jest.mock('~/config', () => { + return { + logger: { + info: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + }, + }; +}); diff --git a/api/typedefs.js b/api/typedefs.js index c12254e32d0..1ab9f645718 100644 --- a/api/typedefs.js +++ b/api/typedefs.js @@ -111,6 +111,32 @@ * @memberof typedefs */ +/** + * @exports UserMessageContent + * @typedef {Object} UserMessageContent + * @property {Object[]} content - The content of the message in an array of text and/or images. + * @property {string} content[].type - The type of content, either 'text' or 'image_file'. + * @property {Object} [content[].text] - The text content, present if type is 'text'. + * @property {string} content[].text.value - The data that makes up the text. + * @property {Object} [content[].image_url] - The image file content, present if type is 'image_file'. + * @property {string} content[].image_url.url - The File ID of the image in the message content. + * @property {'auto' | 'low' | 'high'} content[].image_url.detail: 'auto' - the quality to use for the image, either 'auto', 'low', or 'high'. + * @memberof typedefs + */ + +/** + * Represents a message payload with various potential properties, + * including roles, sender information, and content. + * + * @typedef {Object} PayloadMessage + * @property {string} [role] - The role of the message sender (e.g., 'user', 'assistant'). + * @property {string} [name] - The name associated with the message. + * @property {string} [sender] - The sender of the message. + * @property {string} [text] - The text content of the message. + * @property {(string|Array)} [content] - The content of the message, which could be a string or an array of the 'content' property from the Message type. + * @memberof typedefs + */ + /** * @exports FunctionTool * @typedef {Object} FunctionTool diff --git a/api/utils/azureUtils.js b/api/utils/azureUtils.js index b1d8d3c8edb..58b8fcde3fa 100644 --- a/api/utils/azureUtils.js +++ b/api/utils/azureUtils.js @@ -6,7 +6,7 @@ * @property {string} azureOpenAIApiVersion - The Azure OpenAI API version. */ -const { isEnabled } = require('../server/utils'); +const { isEnabled } = require('~/server/utils'); /** * Sanitizes the model name to be used in the URL by removing or replacing disallowed characters. @@ -26,7 +26,9 @@ const sanitizeModelName = (modelName) => { * @returns {string} The complete endpoint URL for the Azure OpenAI API. */ const genAzureEndpoint = ({ azureOpenAIApiInstanceName, azureOpenAIApiDeploymentName }) => { - console.log(`https://${azureOpenAIApiInstanceName}/openai/deployments/${azureOpenAIApiDeploymentName}`); + console.log( + `https://${azureOpenAIApiInstanceName}/openai/deployments/${azureOpenAIApiDeploymentName}`, + ); return `https://${azureOpenAIApiInstanceName}/openai/deployments/${azureOpenAIApiDeploymentName}`; }; @@ -56,7 +58,9 @@ const genAzureChatCompletion = ( throw new Error('Either a model name or a deployment name must be provided.'); } console.log('genAzureChatCompletion'); - console.log(`https://${azureOpenAIApiInstanceName}/openai/deployments/${deploymentSegment}/chat/completions?api-version=${azureOpenAIApiVersion}`); + console.log( + `https://${azureOpenAIApiInstanceName}/openai/deployments/${deploymentSegment}/chat/completions?api-version=${azureOpenAIApiVersion}`, + ); return `https://${azureOpenAIApiInstanceName}/openai/deployments/${deploymentSegment}/chat/completions?api-version=${azureOpenAIApiVersion}`; }; diff --git a/api/utils/findMessageContent.js b/api/utils/findMessageContent.js index c5064350310..6ee5166348b 100644 --- a/api/utils/findMessageContent.js +++ b/api/utils/findMessageContent.js @@ -1,3 +1,5 @@ +const { logger } = require('~/config'); + function findContent(obj) { if (obj && typeof obj === 'object') { if ('kwargs' in obj && 'content' in obj.kwargs) { @@ -21,7 +23,7 @@ function findMessageContent(message) { try { jsonObjectOrArray = JSON.parse(jsonString); } catch (error) { - console.error('Failed to parse JSON:', error); + logger.error('[findMessageContent] Failed to parse JSON:', error); return null; } diff --git a/api/utils/tokens.js b/api/utils/tokens.js index b5db7bc9888..cda4755717d 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -56,11 +56,12 @@ const maxTokensMap = { 'gpt-4-1106': 127995, // -5 from max }, [EModelEndpoint.google]: { - /* Max I/O is 32k combined, so -1000 to leave room for response */ - 'text-bison-32k': 31000, - 'chat-bison-32k': 31000, - 'code-bison-32k': 31000, - 'codechat-bison-32k': 31000, + /* Max I/O is combined so we subtract the amount from max response tokens for actual total */ + gemini: 32750, // -10 from max + 'text-bison-32k': 32758, // -10 from max + 'chat-bison-32k': 32758, // -10 from max + 'code-bison-32k': 32758, // -10 from max + 'codechat-bison-32k': 32758, /* Codey, -5 from max: 6144 */ 'code-': 6139, 'codechat-': 6139, diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index a10cddd060d..2cb7985d312 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -114,6 +114,9 @@ describe('getModelMaxTokens', () => { }); test('should return correct tokens for partial match - Google models', () => { + expect(getModelMaxTokens('gemini-pro', EModelEndpoint.google)).toBe( + maxTokensMap[EModelEndpoint.google]['gemini'], + ); expect(getModelMaxTokens('code-', EModelEndpoint.google)).toBe( maxTokensMap[EModelEndpoint.google]['code-'], ); diff --git a/client/index.html b/client/index.html index 6d6c1dbf596..dae28cb7057 100644 --- a/client/index.html +++ b/client/index.html @@ -3,6 +3,7 @@ + LibreChat { - + diff --git a/client/src/common/types.ts b/client/src/common/types.ts index 807e2dc9424..2daf2d8ba0f 100644 --- a/client/src/common/types.ts +++ b/client/src/common/types.ts @@ -27,6 +27,7 @@ export type TShowToast = { severity?: NotificationSeverity; showIcon?: boolean; duration?: number; + status?: 'error' | 'success' | 'warning' | 'info'; }; export type TBaseSettingsProps = { diff --git a/client/src/components/Chat/ChatView.tsx b/client/src/components/Chat/ChatView.tsx index 5ce30951395..d582f4b3e9d 100644 --- a/client/src/components/Chat/ChatView.tsx +++ b/client/src/components/Chat/ChatView.tsx @@ -6,10 +6,10 @@ import { useChatHelpers, useSSE } from '~/hooks'; // import GenerationButtons from './Input/GenerationButtons'; import MessagesView from './Messages/MessagesView'; // import OptionsBar from './Input/OptionsBar'; +import { Spinner } from '~/components/svg'; import { ChatContext } from '~/Providers'; import Presentation from './Presentation'; import ChatForm from './Input/ChatForm'; -import { Spinner } from '~/components'; import { buildTree } from '~/utils'; import Landing from './Landing'; import Header from './Header'; diff --git a/client/src/components/Chat/Footer.tsx b/client/src/components/Chat/Footer.tsx index be9b0c839ae..ec5e6e65f02 100644 --- a/client/src/components/Chat/Footer.tsx +++ b/client/src/components/Chat/Footer.tsx @@ -17,7 +17,7 @@ export default function Footer() { rel="noreferrer" className="underline" > - {config?.appTitle || 'LibreChat'} v0.6.1 + {config?.appTitle || 'LibreChat'} v0.6.5 {' - '} {localize('com_ui_new_footer')} diff --git a/client/src/components/Chat/Input/HeaderOptions.tsx b/client/src/components/Chat/Input/HeaderOptions.tsx index eecb095b3e1..20415d1d496 100644 --- a/client/src/components/Chat/Input/HeaderOptions.tsx +++ b/client/src/components/Chat/Input/HeaderOptions.tsx @@ -93,7 +93,7 @@ export default function OptionsBar() { visible={showPopover} saveAsPreset={saveAsPreset} closePopover={() => setShowPopover(false)} - PopoverButtons={} + PopoverButtons={} >
setOptionSettings((prev) => ({ ...prev, showExamples: !prev.showExamples })); @@ -32,7 +42,7 @@ export default function PopoverButtons({ [EModelEndpoint.google]: [ { label: (showExamples ? 'Hide' : 'Show') + ' Examples', - buttonClass: isCodeChat ? 'disabled' : '', + buttonClass: isGenerativeModel || isTextModel ? 'disabled' : '', handler: triggerExamples, icon: , }, @@ -47,11 +57,15 @@ export default function PopoverButtons({ ], }; - const endpointButtons = buttons[endpoint]; + const endpointButtons = buttons[endpoint ?? '']; if (!endpointButtons) { return null; } + if (endpoint === EModelEndpoint.google && !showExamplesButton) { + return null; + } + return (
{endpointButtons.map((button, index) => ( diff --git a/client/src/components/Chat/Menus/EndpointsMenu.tsx b/client/src/components/Chat/Menus/EndpointsMenu.tsx index 5a3a61b7134..a4b5ed439ae 100644 --- a/client/src/components/Chat/Menus/EndpointsMenu.tsx +++ b/client/src/components/Chat/Menus/EndpointsMenu.tsx @@ -1,5 +1,5 @@ -import { Content, Portal, Root } from '@radix-ui/react-popover'; import { alternateName } from 'librechat-data-provider'; +import { Content, Portal, Root } from '@radix-ui/react-popover'; import { useGetEndpointsQuery } from 'librechat-data-provider/react-query'; import type { FC } from 'react'; import EndpointItems from './Endpoints/MenuItems'; @@ -14,9 +14,14 @@ const EndpointsMenu: FC = () => { const { conversation } = useChatContext(); const selected = conversation?.endpoint ?? ''; + + if (!selected) { + console.warn('No endpoint selected'); + return null; + } return ( - +
{imageFiles && - imageFiles.map((file, i) => ( + imageFiles.map((file) => ( ( - + ); // Content Component diff --git a/client/src/components/Endpoints/Icon.tsx b/client/src/components/Endpoints/Icon.tsx index fc03752ce40..23e2845eabf 100644 --- a/client/src/components/Endpoints/Icon.tsx +++ b/client/src/components/Endpoints/Icon.tsx @@ -6,6 +6,7 @@ import { AzureMinimalIcon, PaLMIcon, CodeyIcon, + GeminiIcon, } from '~/components/svg'; import { useAuthContext } from '~/hooks/AuthContext'; import { IconProps } from '~/common'; @@ -59,12 +60,18 @@ const Icon: React.FC = (props) => { name: 'Plugins', }, [EModelEndpoint.google]: { - icon: model?.includes('code') ? ( + icon: model?.toLowerCase()?.includes('code') ? ( + ) : model?.toLowerCase()?.includes('gemini') ? ( + ) : ( ), - name: model?.includes('code') ? 'Codey' : 'PaLM2', + name: model?.toLowerCase()?.includes('code') + ? 'Codey' + : model?.toLowerCase()?.includes('gemini') + ? 'Gemini' + : 'PaLM2', }, [EModelEndpoint.anthropic]: { icon: , diff --git a/client/src/components/Endpoints/Settings/Google.tsx b/client/src/components/Endpoints/Settings/Google.tsx index a580f1355d3..abcf5144117 100644 --- a/client/src/components/Endpoints/Settings/Google.tsx +++ b/client/src/components/Endpoints/Settings/Google.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useEffect } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { EModelEndpoint, endpointSettings } from 'librechat-data-provider'; import type { TModelSelectProps } from '~/common'; @@ -18,11 +18,32 @@ import { useLocalize } from '~/hooks'; export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) { const localize = useLocalize(); + const google = endpointSettings[EModelEndpoint.google]; + const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } = + conversation ?? {}; + + const isGeminiPro = model?.toLowerCase()?.includes('gemini-pro'); + + const maxOutputTokensMax = isGeminiPro + ? google.maxOutputTokens.maxGeminiPro + : google.maxOutputTokens.max; + const maxOutputTokensDefault = isGeminiPro + ? google.maxOutputTokens.defaultGeminiPro + : google.maxOutputTokens.default; + + useEffect( + () => { + if (model) { + setOption('maxOutputTokens')(Math.min(Number(maxOutputTokens) ?? 0, maxOutputTokensMax)); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [model], + ); + if (!conversation) { return null; } - const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } = - conversation; const setModel = setOption('model'); const setModelLabel = setOption('modelLabel'); @@ -32,8 +53,9 @@ export default function Settings({ conversation, setOption, models, readonly }: const setTopK = setOption('topK'); const setMaxOutputTokens = setOption('maxOutputTokens'); - const isTextModel = !model?.includes('chat') && /code|text/.test(model ?? ''); - const google = endpointSettings[EModelEndpoint.google]; + const isGenerativeModel = model?.toLowerCase()?.includes('gemini'); + const isChatModel = !isGenerativeModel && model?.toLowerCase()?.includes('chat'); + const isTextModel = !isGenerativeModel && !isChatModel && /code|text/.test(model ?? ''); return (
@@ -216,15 +238,15 @@ export default function Settings({ conversation, setOption, models, readonly }: setMaxOutputTokens(value ?? google.maxOutputTokens.default)} - max={google.maxOutputTokens.max} + onChange={(value) => setMaxOutputTokens(value ?? maxOutputTokensDefault)} + max={maxOutputTokensMax} min={google.maxOutputTokens.min} step={google.maxOutputTokens.step} controls={false} @@ -239,10 +261,10 @@ export default function Settings({ conversation, setOption, models, readonly }:
setMaxOutputTokens(value[0])} - doubleClickHandler={() => setMaxOutputTokens(google.maxOutputTokens.default)} - max={google.maxOutputTokens.max} + doubleClickHandler={() => setMaxOutputTokens(maxOutputTokensDefault)} + max={maxOutputTokensMax} min={google.maxOutputTokens.min} step={google.maxOutputTokens.step} className="flex h-4 w-full" diff --git a/client/src/components/Endpoints/Settings/MultiView/GoogleSettings.tsx b/client/src/components/Endpoints/Settings/MultiView/GoogleSettings.tsx index 19cce9bd74e..da786478428 100644 --- a/client/src/components/Endpoints/Settings/MultiView/GoogleSettings.tsx +++ b/client/src/components/Endpoints/Settings/MultiView/GoogleSettings.tsx @@ -12,9 +12,12 @@ export default function GoogleView({ conversation, models, isPreset = false }) { return null; } - const { examples } = conversation; - const { showExamples, isCodeChat } = optionSettings; - return showExamples && !isCodeChat ? ( + const { examples, model } = conversation; + const isGenerativeModel = model?.toLowerCase()?.includes('gemini'); + const isChatModel = !isGenerativeModel && model?.toLowerCase()?.includes('chat'); + const isTextModel = !isGenerativeModel && !isChatModel && /code|text/.test(model ?? ''); + const { showExamples } = optionSettings; + return showExamples && isChatModel && !isTextModel ? ( ) => void; className?: string; + containerClassName?: string; successText?: string; invalidText?: string; validator?: ((data: Record) => boolean) | null; @@ -16,6 +17,7 @@ type FileUploadProps = { const FileUpload: React.FC = ({ onFileSelected, className = '', + containerClassName = '', successText = null, invalidText = null, validator = null, @@ -66,6 +68,7 @@ const FileUpload: React.FC = ({ className={cn( 'mr-1 flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium font-normal transition-colors hover:bg-slate-200 hover:text-green-700 dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-green-500', statusColor, + containerClassName, )} > diff --git a/client/src/components/Input/Footer.tsx b/client/src/components/Input/Footer.tsx index f8bf6a8416f..c15f80d6c92 100644 --- a/client/src/components/Input/Footer.tsx +++ b/client/src/components/Input/Footer.tsx @@ -17,7 +17,7 @@ export default function Footer() { rel="noreferrer" className="underline" > - {config?.appTitle || 'LibreChat'} v0.6.1 + {config?.appTitle || 'LibreChat'} v0.6.5 {' - '}. {localize('com_ui_pay_per_call')} diff --git a/client/src/components/Input/SetKeyDialog/GoogleConfig.tsx b/client/src/components/Input/SetKeyDialog/GoogleConfig.tsx index f716c03e6cd..558c79e575c 100644 --- a/client/src/components/Input/SetKeyDialog/GoogleConfig.tsx +++ b/client/src/components/Input/SetKeyDialog/GoogleConfig.tsx @@ -1,8 +1,11 @@ import React from 'react'; import { object, string } from 'zod'; +import { AuthKeys } from 'librechat-data-provider'; import type { TConfigProps } from '~/common'; -import FileUpload from '../EndpointMenu/FileUpload'; -import { useLocalize } from '~/hooks'; +import FileUpload from '~/components/Input/EndpointMenu/FileUpload'; +import { useLocalize, useMultipleKeys } from '~/hooks'; +import InputWithLabel from './InputWithLabel'; +import { Label } from '~/components/ui'; const CredentialsSchema = object({ client_email: string().email().min(3), @@ -15,20 +18,43 @@ const validateCredentials = (credentials: Record) => { return result.success; }; -const GoogleConfig = ({ setUserKey }: Pick) => { +const GoogleConfig = ({ userKey, setUserKey }: Pick) => { const localize = useLocalize(); + const { getMultiKey, setMultiKey } = useMultipleKeys(setUserKey); + return ( - { - setUserKey(JSON.stringify(data)); - }} - /> + <> +
+ +
+ {localize('com_endpoint_config_google_cloud_platform')} +
+
+
+ { + setMultiKey(AuthKeys.GOOGLE_SERVICE_KEY, JSON.stringify(data), userKey); + }} + /> + + setMultiKey(AuthKeys.GOOGLE_API_KEY, e.target.value ?? '', userKey) + } + label={localize('com_endpoint_config_google_api_key')} + subLabel={localize('com_endpoint_config_google_gemini_api')} + /> + ); }; diff --git a/client/src/components/Input/SetKeyDialog/HelpText.tsx b/client/src/components/Input/SetKeyDialog/HelpText.tsx index e32123d664d..38cb463aad2 100644 --- a/client/src/components/Input/SetKeyDialog/HelpText.tsx +++ b/client/src/components/Input/SetKeyDialog/HelpText.tsx @@ -55,28 +55,45 @@ function HelpText({ endpoint }: { endpoint: string }) { ), [EModelEndpoint.google]: ( - - {localize('com_endpoint_config_key_google_need_to')}{' '} - - {localize('com_endpoint_config_key_google_vertex_ai')} - {' '} - {localize('com_endpoint_config_key_google_vertex_api')}{' '} - - {localize('com_endpoint_config_key_google_service_account')} - - {'. '} - {localize('com_endpoint_config_key_google_vertex_api_role')} - + <> + + {localize('com_endpoint_config_google_service_key')} + {': '} + {localize('com_endpoint_config_key_google_need_to')}{' '} + + {localize('com_endpoint_config_key_google_vertex_ai')} + {' '} + {localize('com_endpoint_config_key_google_vertex_api')}{' '} + + {localize('com_endpoint_config_key_google_service_account')} + + {'. '} + {localize('com_endpoint_config_key_google_vertex_api_role')} + + + {localize('com_endpoint_config_google_api_key')} + {': '} + {localize('com_endpoint_config_google_api_info')}{' '} + + {localize('com_endpoint_config_click_here')} + {' '} + + ), }; diff --git a/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx b/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx index 6d42bc4eb4b..270e954ea75 100644 --- a/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx +++ b/client/src/components/Input/SetKeyDialog/InputWithLabel.tsx @@ -1,23 +1,29 @@ import React, { ChangeEvent, FC } from 'react'; -import { Input, Label } from '~/components'; import { cn, defaultTextPropsLabel, removeFocusOutlines } from '~/utils/'; +import { Input, Label } from '~/components/ui'; import { useLocalize } from '~/hooks'; interface InputWithLabelProps { value: string; onChange: (event: ChangeEvent) => void; label: string; + subLabel?: string; id: string; } -const InputWithLabel: FC = ({ value, onChange, label, id }) => { +const InputWithLabel: FC = ({ value, onChange, label, subLabel, id }) => { const localize = useLocalize(); return ( <> -
diff --git a/client/src/components/Nav/SettingsTabs/Account/Account.tsx b/client/src/components/Nav/SettingsTabs/Account/Account.tsx new file mode 100644 index 00000000000..a7651ddca60 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/Account.tsx @@ -0,0 +1,18 @@ +import * as Tabs from '@radix-ui/react-tabs'; +import Avatar from './Avatar'; +import React from 'react'; + +function Account() { + return ( + +
+
+ +
+
+
+
+ ); +} + +export default React.memo(Account); diff --git a/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx b/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx new file mode 100644 index 00000000000..64635f0a59d --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx @@ -0,0 +1,145 @@ +import { FileImage } from 'lucide-react'; +import { useSetRecoilState } from 'recoil'; +import { useState, useEffect } from 'react'; +import type { TUser } from 'librechat-data-provider'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui'; +import { useUploadAvatarMutation } from '~/data-provider'; +import { useToastContext } from '~/Providers'; +import { Spinner } from '~/components/svg'; +import { useLocalize } from '~/hooks'; +import { cn } from '~/utils/'; +import store from '~/store'; + +const sizeLimit = 2 * 1024 * 1024; // 2MB + +function Avatar() { + const setUser = useSetRecoilState(store.user); + const [input, setinput] = useState(null); + const [isDialogOpen, setDialogOpen] = useState(false); + const [previewUrl, setPreviewUrl] = useState(null); + + const localize = useLocalize(); + const { showToast } = useToastContext(); + + const { mutate: uploadAvatar, isLoading: isUploading } = useUploadAvatarMutation({ + onSuccess: (data) => { + showToast({ message: localize('com_ui_upload_success') }); + setDialogOpen(false); + + setUser((prev) => ({ ...prev, avatar: data.url } as TUser)); + }, + onError: (error) => { + console.error('Error:', error); + showToast({ message: localize('com_ui_upload_error'), status: 'error' }); + }, + }); + + useEffect(() => { + if (input) { + const reader = new FileReader(); + reader.onloadend = () => { + setPreviewUrl(reader.result as string); + }; + reader.readAsDataURL(input); + } else { + setPreviewUrl(null); + } + }, [input]); + + const handleFileChange = (event: React.ChangeEvent): void => { + const file = event.target.files?.[0]; + + if (file && file.size <= sizeLimit) { + setinput(file); + setDialogOpen(true); + } else { + showToast({ + message: localize('com_ui_upload_invalid'), + status: 'error', + }); + } + }; + + const handleUpload = () => { + if (!input) { + console.error('No file selected'); + return; + } + + const formData = new FormData(); + formData.append('input', input, input.name); + formData.append('manual', 'true'); + + uploadAvatar(formData); + }; + + return ( + <> +
+ {localize('com_nav_profile_picture')} + +
+ + setDialogOpen(false)}> + + + + {localize('com_ui_preview')} + + +
+ {previewUrl && ( + Preview + )} + +
+
+
+ + ); +} + +export default Avatar; diff --git a/client/src/components/Nav/SettingsTabs/Data.tsx b/client/src/components/Nav/SettingsTabs/Data/Data.tsx similarity index 98% rename from client/src/components/Nav/SettingsTabs/Data.tsx rename to client/src/components/Nav/SettingsTabs/Data/Data.tsx index 1786095eeda..d7db9969412 100644 --- a/client/src/components/Nav/SettingsTabs/Data.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/Data.tsx @@ -5,7 +5,7 @@ import { } from 'librechat-data-provider/react-query'; import React, { useState, useCallback, useRef } from 'react'; import { useOnClickOutside } from '~/hooks'; -import DangerButton from './DangerButton'; +import DangerButton from '../DangerButton'; export const RevokeKeysButton = ({ showText = true, diff --git a/client/src/components/Nav/SettingsTabs/AutoScrollSwitch.spec.tsx b/client/src/components/Nav/SettingsTabs/General/AutoScrollSwitch.spec.tsx similarity index 100% rename from client/src/components/Nav/SettingsTabs/AutoScrollSwitch.spec.tsx rename to client/src/components/Nav/SettingsTabs/General/AutoScrollSwitch.spec.tsx diff --git a/client/src/components/Nav/SettingsTabs/AutoScrollSwitch.tsx b/client/src/components/Nav/SettingsTabs/General/AutoScrollSwitch.tsx similarity index 100% rename from client/src/components/Nav/SettingsTabs/AutoScrollSwitch.tsx rename to client/src/components/Nav/SettingsTabs/General/AutoScrollSwitch.tsx diff --git a/client/src/components/Nav/SettingsTabs/ClearChatsButton.spec.tsx b/client/src/components/Nav/SettingsTabs/General/ClearChatsButton.spec.tsx similarity index 100% rename from client/src/components/Nav/SettingsTabs/ClearChatsButton.spec.tsx rename to client/src/components/Nav/SettingsTabs/General/ClearChatsButton.spec.tsx diff --git a/client/src/components/Nav/SettingsTabs/General.tsx b/client/src/components/Nav/SettingsTabs/General/General.tsx similarity index 99% rename from client/src/components/Nav/SettingsTabs/General.tsx rename to client/src/components/Nav/SettingsTabs/General/General.tsx index 343b36fbecd..48ecd6be006 100644 --- a/client/src/components/Nav/SettingsTabs/General.tsx +++ b/client/src/components/Nav/SettingsTabs/General/General.tsx @@ -12,7 +12,7 @@ import { } from '~/hooks'; import type { TDangerButtonProps } from '~/common'; import AutoScrollSwitch from './AutoScrollSwitch'; -import DangerButton from './DangerButton'; +import DangerButton from '../DangerButton'; import store from '~/store'; import { Dropdown } from '~/components/ui'; diff --git a/client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx b/client/src/components/Nav/SettingsTabs/General/LangSelector.spec.tsx similarity index 100% rename from client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx rename to client/src/components/Nav/SettingsTabs/General/LangSelector.spec.tsx diff --git a/client/src/components/Nav/SettingsTabs/ThemeSelector.spec.tsx b/client/src/components/Nav/SettingsTabs/General/ThemeSelector.spec.tsx similarity index 100% rename from client/src/components/Nav/SettingsTabs/ThemeSelector.spec.tsx rename to client/src/components/Nav/SettingsTabs/General/ThemeSelector.spec.tsx diff --git a/client/src/components/Nav/SettingsTabs/index.ts b/client/src/components/Nav/SettingsTabs/index.ts index 939c90f3b33..73174aa7984 100644 --- a/client/src/components/Nav/SettingsTabs/index.ts +++ b/client/src/components/Nav/SettingsTabs/index.ts @@ -1,4 +1,5 @@ -export { default as General } from './General'; -export { ClearChatsButton } from './General'; -export { default as Data } from './Data'; -export { RevokeKeysButton } from './Data'; +export { default as General } from './General/General'; +export { ClearChatsButton } from './General/General'; +export { default as Data } from './Data/Data'; +export { RevokeKeysButton } from './Data/Data'; +export { default as Account } from './Account/Account'; diff --git a/client/src/components/svg/GearIcon.tsx b/client/src/components/svg/GearIcon.tsx index e5ed475d521..98cc2fab982 100644 --- a/client/src/components/svg/GearIcon.tsx +++ b/client/src/components/svg/GearIcon.tsx @@ -1,14 +1,18 @@ import React from 'react'; -export default function GearIcon() { +interface GearIconProps { + className?: string; +} + +const GearIcon: React.FC = ({ className = '' }) => { return ( ); -} +}; + +export default GearIcon; diff --git a/client/src/components/svg/GeminiIcon.tsx b/client/src/components/svg/GeminiIcon.tsx new file mode 100644 index 00000000000..783b587fe7b --- /dev/null +++ b/client/src/components/svg/GeminiIcon.tsx @@ -0,0 +1,36 @@ +export default function GeminiIcon({ + size = 25, + className = '', +}: { + size?: number; + className?: string; +}) { + return ( + + + + + + + + + + ); +} diff --git a/client/src/components/svg/UserIcon.tsx b/client/src/components/svg/UserIcon.tsx index 8f15fadcaf6..e8535e45eb9 100644 --- a/client/src/components/svg/UserIcon.tsx +++ b/client/src/components/svg/UserIcon.tsx @@ -1,20 +1,17 @@ -import React from 'react'; - export default function UserIcon() { return ( - + ); diff --git a/client/src/components/svg/index.ts b/client/src/components/svg/index.ts index 9f8fa359dcc..3ad62c93eea 100644 --- a/client/src/components/svg/index.ts +++ b/client/src/components/svg/index.ts @@ -36,6 +36,8 @@ export { default as BingAIMinimalIcon } from './BingAIMinimalIcon'; export { default as PaLMinimalIcon } from './PaLMinimalIcon'; export { default as PaLMIcon } from './PaLMIcon'; export { default as CodeyIcon } from './CodeyIcon'; +export { default as GeminiIcon } from './GeminiIcon'; export { default as GoogleMinimalIcon } from './GoogleMinimalIcon'; export { default as AnthropicMinimalIcon } from './AnthropicMinimalIcon'; export { default as SendMessageIcon } from './SendMessageIcon'; +export { default as UserIcon } from './UserIcon'; diff --git a/client/src/data-provider/mutations.ts b/client/src/data-provider/mutations.ts index c38ca46c9e6..236c66a9479 100644 --- a/client/src/data-provider/mutations.ts +++ b/client/src/data-provider/mutations.ts @@ -12,6 +12,8 @@ import type { PresetDeleteResponse, LogoutOptions, TPreset, + UploadAvatarOptions, + AvatarUploadResponse, } from 'librechat-data-provider'; import { dataService, MutationKeys } from 'librechat-data-provider'; @@ -99,3 +101,18 @@ export const useLogoutUserMutation = ( }, }); }; + +/* Avatar upload */ +export const useUploadAvatarMutation = ( + options?: UploadAvatarOptions, +): UseMutationResult< + AvatarUploadResponse, // response data + unknown, // error + FormData, // request + unknown // context +> => { + return useMutation([MutationKeys.avatarUpload], { + mutationFn: (variables: FormData) => dataService.uploadAvatar(variables), + ...(options || {}), + }); +}; diff --git a/client/src/hooks/AuthContext.tsx b/client/src/hooks/AuthContext.tsx index 8cd63833bde..711f433ef2d 100644 --- a/client/src/hooks/AuthContext.tsx +++ b/client/src/hooks/AuthContext.tsx @@ -7,6 +7,7 @@ import { createContext, useContext, } from 'react'; +import { useRecoilState } from 'recoil'; import { TUser, TLoginResponse, setTokenHeader, TLoginUser } from 'librechat-data-provider'; import { useGetUserQuery, @@ -17,6 +18,7 @@ import { useNavigate } from 'react-router-dom'; import { TAuthConfig, TUserContext, TAuthContext, TResError } from '~/common'; import { useLogoutUserMutation } from '~/data-provider'; import useTimeout from './useTimeout'; +import store from '~/store'; const AuthContext = createContext(undefined); @@ -27,11 +29,13 @@ const AuthContextProvider = ({ authConfig?: TAuthConfig; children: ReactNode; }) => { - const navigate = useNavigate(); - const [user, setUser] = useState(undefined); + const [user, setUser] = useRecoilState(store.user); const [token, setToken] = useState(undefined); const [error, setError] = useState(undefined); const [isAuthenticated, setIsAuthenticated] = useState(false); + + const navigate = useNavigate(); + const setUserContext = useCallback( (userContext: TUserContext) => { const { token, isAuthenticated, user, redirect } = userContext; @@ -46,7 +50,7 @@ const AuthContextProvider = ({ navigate(redirect, { replace: true }); } }, - [navigate], + [navigate, setUser], ); const doSetError = useTimeout({ callback: (error) => setError(error as string | undefined) }); diff --git a/client/src/hooks/Input/index.ts b/client/src/hooks/Input/index.ts index 240b67f5c58..2a4b51e593d 100644 --- a/client/src/hooks/Input/index.ts +++ b/client/src/hooks/Input/index.ts @@ -2,3 +2,4 @@ export { default as useUserKey } from './useUserKey'; export { default as useDebounce } from './useDebounce'; export { default as useTextarea } from './useTextarea'; export { default as useRequiresKey } from './useRequiresKey'; +export { default as useMultipleKeys } from './useMultipleKeys'; diff --git a/client/src/hooks/Input/useMultipleKeys.ts b/client/src/hooks/Input/useMultipleKeys.ts new file mode 100644 index 00000000000..b322bb7003a --- /dev/null +++ b/client/src/hooks/Input/useMultipleKeys.ts @@ -0,0 +1,24 @@ +import { isJson } from '~/utils/json'; + +export default function useMultipleKeys(setUserKey: React.Dispatch>) { + function getMultiKey(name: string, userKey: string) { + if (isJson(userKey)) { + const newKey = JSON.parse(userKey); + return newKey[name]; + } else { + return ''; + } + } + + function setMultiKey(name: string, value: number | string | boolean, userKey: string) { + let newKey = {}; + if (isJson(userKey)) { + newKey = JSON.parse(userKey); + } + newKey[name] = value; + + setUserKey(JSON.stringify(newKey)); + } + + return { getMultiKey, setMultiKey }; +} diff --git a/client/src/hooks/Input/useUserKey.ts b/client/src/hooks/Input/useUserKey.ts index 0d68287176a..fd99bf5a3a6 100644 --- a/client/src/hooks/Input/useUserKey.ts +++ b/client/src/hooks/Input/useUserKey.ts @@ -1,4 +1,5 @@ import { useMemo, useCallback } from 'react'; +import { EModelEndpoint } from 'librechat-data-provider'; import { useUserKeyQuery, useGetEndpointsQuery, @@ -10,16 +11,16 @@ const useUserKey = (endpoint: string) => { const config = endpointsConfig?.[endpoint]; const { azure } = config ?? {}; - let keyEndpoint = endpoint; + let keyName = endpoint; if (azure) { - keyEndpoint = 'azureOpenAI'; - } else if (keyEndpoint === 'gptPlugins') { - keyEndpoint = 'openAI'; + keyName = EModelEndpoint.azureOpenAI; + } else if (keyName === EModelEndpoint.gptPlugins) { + keyName = EModelEndpoint.openAI; } const updateKey = useUpdateUserKeysMutation(); - const checkUserKey = useUserKeyQuery(keyEndpoint); + const checkUserKey = useUserKeyQuery(keyName); const getExpiry = useCallback(() => { if (checkUserKey.data) { return checkUserKey.data.expiresAt; @@ -40,15 +41,15 @@ const useUserKey = (endpoint: string) => { }, [getExpiry]); const saveUserKey = useCallback( - (value: string, expiresAt: number) => { + (userKey: string, expiresAt: number) => { const dateStr = new Date(expiresAt).toISOString(); updateKey.mutate({ - name: keyEndpoint, - value, + name: keyName, + value: userKey, expiresAt: dateStr, }); }, - [updateKey, keyEndpoint], + [updateKey, keyName], ); return useMemo( diff --git a/client/src/hooks/useChatHelpers.ts b/client/src/hooks/useChatHelpers.ts index d5cc37a1594..bb702cb98f8 100644 --- a/client/src/hooks/useChatHelpers.ts +++ b/client/src/hooks/useChatHelpers.ts @@ -217,7 +217,6 @@ export default function useChatHelpers(index = 0, paramId: string | undefined) { messageId: responseMessageId ?? `${isRegenerate ? messageId : fakeMessageId}_`, conversationId, unfinished: false, - submitting: true, isCreatedByUser: false, isEdited: isEditOrContinue, error: false, diff --git a/client/src/hooks/useSSE.ts b/client/src/hooks/useSSE.ts index d5e0742e9db..e5258511c7f 100644 --- a/client/src/hooks/useSSE.ts +++ b/client/src/hooks/useSSE.ts @@ -64,7 +64,6 @@ export default function useSSE(submission: TSubmission | null, index = 0) { messageId: message?.overrideParentMessageId + '_', plugin: plugin ?? null, plugins: plugins ?? [], - submitting: true, // unfinished: true }, ]); @@ -79,7 +78,6 @@ export default function useSSE(submission: TSubmission | null, index = 0) { messageId: message?.messageId + '_', plugin: plugin ?? null, plugins: plugins ?? [], - submitting: true, // unfinished: true }, ]); @@ -136,7 +134,6 @@ export default function useSSE(submission: TSubmission | null, index = 0) { ...initialResponse, parentMessageId: message?.overrideParentMessageId ?? null, messageId: message?.overrideParentMessageId + '_', - submitting: true, }, ]); } else { @@ -147,7 +144,6 @@ export default function useSSE(submission: TSubmission | null, index = 0) { ...initialResponse, parentMessageId: message?.messageId, messageId: message?.messageId + '_', - submitting: true, }, ]); } @@ -172,7 +168,7 @@ export default function useSSE(submission: TSubmission | null, index = 0) { const finalHandler = (data: TResData, submission: TSubmission) => { const { requestMessage, responseMessage, conversation } = data; - const { messages, isRegenerate = false } = submission; + const { messages, conversation: submissionConvo, isRegenerate = false } = submission; // update the messages if (isRegenerate) { @@ -199,6 +195,11 @@ export default function useSSE(submission: TSubmission | null, index = 0) { ...conversation, }; + // Revert to previous model if the model was auto-switched by backend due to message attachments + if (conversation.model?.includes('vision') && !submissionConvo.model?.includes('vision')) { + update.model = submissionConvo?.model; + } + setStorage(update); return update; }); @@ -233,6 +234,7 @@ export default function useSSE(submission: TSubmission | null, index = 0) { const abortConversation = (conversationId = '', submission: TSubmission) => { console.log(submission); const { endpoint } = submission?.conversation || {}; + let res: Response; fetch(`/api/ask/${endpoint}/abort`, { method: 'POST', @@ -244,9 +246,15 @@ export default function useSSE(submission: TSubmission | null, index = 0) { abortKey: conversationId, }), }) - .then((response) => response.json()) + .then((response) => { + res = response; + return response.json(); + }) .then((data) => { console.log('aborted', data); + if (res.status === 404) { + return setIsSubmitting(false); + } cancelHandler(data, submission); }) .catch((error) => { diff --git a/client/src/hooks/useToast.ts b/client/src/hooks/useToast.ts index 716526768d8..92f7bbfe17b 100644 --- a/client/src/hooks/useToast.ts +++ b/client/src/hooks/useToast.ts @@ -25,6 +25,7 @@ export default function useToast(showDelay = 100) { severity = NotificationSeverity.SUCCESS, showIcon = true, duration = 3000, // default duration for the toast to be visible + status, }: TShowToast) => { // Clear existing timeouts if (showTimerRef.current !== null) { @@ -36,7 +37,12 @@ export default function useToast(showDelay = 100) { // Timeout to show the toast showTimerRef.current = window.setTimeout(() => { - setToast({ open: true, message, severity, showIcon }); + setToast({ + open: true, + message, + severity: (status as NotificationSeverity) ?? severity, + showIcon, + }); // Hides the toast after the specified duration hideTimerRef.current = window.setTimeout(() => { setToast((prevToast) => ({ ...prevToast, open: false })); diff --git a/client/src/localization/Translation.tsx b/client/src/localization/Translation.tsx index e9826af8f41..e353be293bc 100644 --- a/client/src/localization/Translation.tsx +++ b/client/src/localization/Translation.tsx @@ -57,8 +57,7 @@ if (!String.prototype.format) { // input: language code in string // returns an object of translated strings in the language export const getTranslations = (langCode: string) => { - const language = languageMap[langCode] || English; - return language; + return languageMap[langCode] || English; }; // input: language code in string & phrase key in string diff --git a/client/src/localization/languages/Ar.tsx b/client/src/localization/languages/Ar.tsx index 85d8f5de5fb..424f9f2b0cd 100644 --- a/client/src/localization/languages/Ar.tsx +++ b/client/src/localization/languages/Ar.tsx @@ -262,5 +262,4 @@ export default { com_nav_search_placeholder: 'بحث في الرسائل', com_nav_setting_general: 'عام', com_nav_setting_data: 'تحكم في البيانات', - com_nav_language: 'اللغة', }; diff --git a/client/src/localization/languages/Br.tsx b/client/src/localization/languages/Br.tsx index 830b9e8fca9..8c8a0eaa80c 100644 --- a/client/src/localization/languages/Br.tsx +++ b/client/src/localization/languages/Br.tsx @@ -6,16 +6,16 @@ export default { com_ui_example_quantum_computing: 'Explique a computação quântica em termos simples', com_ui_example_10_year_old_b_day: 'Tem alguma ideia criativa para o aniversário de uma criança de 10 anos?', - com_ui_example_http_in_js: 'Como fazer uma requisição HTTP em Javascript?', + com_ui_example_http_in_js: 'Como faço uma solicitação HTTP em Javascript?', com_ui_capabilities: 'Capacidades', - com_ui_capability_remember: 'Lembra do que o usuário disse anteriormente na conversa', + com_ui_capability_remember: 'Lembra o que o usuário disse anteriormente na conversa', com_ui_capability_correction: 'Permite que o usuário forneça correções de acompanhamento', com_ui_capability_decline_requests: 'Treinado para recusar pedidos inadequados', com_ui_limitations: 'Limitações', com_ui_limitation_incorrect_info: 'Pode ocasionalmente gerar informações incorretas', com_ui_limitation_harmful_biased: 'Pode ocasionalmente produzir instruções prejudiciais ou conteúdo tendencioso', - com_ui_limitation_limited_2021: 'Conhecimento limitado do mundo e dos eventos após 2021', + com_ui_limitation_limited_2021: 'Conhecimento limitado do mundo e eventos após 2021', com_ui_input: 'Entrada', com_ui_close: 'Fechar', com_ui_model: 'Modelo', @@ -30,10 +30,12 @@ export default { com_ui_of: 'de', com_ui_entries: 'Entradas', com_ui_pay_per_call: 'Todas as conversas de IA em um só lugar. Pague por chamada e não por mês', + com_ui_new_footer: 'Todas as conversas de IA em um só lugar.', com_ui_enter: 'Entrar', com_ui_submit: 'Enviar', com_ui_upload_success: 'Arquivo carregado com sucesso', - com_ui_upload_invalid: 'Arquivo inválido para upload', + com_ui_upload_error: 'Houve um erro ao carregar seu arquivo', + com_ui_upload_invalid: 'Arquivo inválido para upload. Deve ser uma imagem que não exceda 2 MB', com_ui_cancel: 'Cancelar', com_ui_save: 'Salvar', com_ui_copy_to_clipboard: 'Copiar para a área de transferência', @@ -48,47 +50,51 @@ export default { com_ui_revoke_info: 'Revogar todas as credenciais fornecidas pelo usuário', com_ui_confirm_action: 'Confirmar Ação', com_ui_chats: 'chats', - com_ui_delete: 'Deletar', - com_ui_delete_conversation: 'Deletar chat?', - com_ui_delete_conversation_confirm: 'Isso irá deletar', + com_ui_delete: 'Excluir', + com_ui_delete_conversation: 'Excluir chat?', + com_ui_delete_conversation_confirm: 'Isso irá excluir', + com_ui_preview: 'Visualizar', + com_ui_upload: 'Carregar', + com_ui_connect: 'Conectar', com_auth_error_login: 'Não foi possível fazer login com as informações fornecidas. Por favor, verifique suas credenciais e tente novamente.', com_auth_error_login_rl: - 'Muitas tentativas de login em um curto espaço de tempo. Por favor, tente novamente mais tarde.', - com_auth_error_login_ban: 'Sua conta foi temporariamente banida por violações do nosso serviço.', + 'Muitas tentativas de login em um curto período de tempo. Por favor, tente novamente mais tarde.', + com_auth_error_login_ban: + 'Sua conta foi temporariamente banida devido a violações de nosso serviço.', com_auth_error_login_server: - 'Houve um erro interno do servidor. Por favor, espere alguns momentos e tente novamente.', + 'Houve um erro interno do servidor. Por favor, aguarde alguns momentos e tente novamente.', com_auth_no_account: 'Não tem uma conta?', com_auth_sign_up: 'Inscrever-se', com_auth_sign_in: 'Entrar', - com_auth_google_login: 'Entrar com Google', - com_auth_facebook_login: 'Entrar com Facebook', - com_auth_github_login: 'Entrar com Github', - com_auth_discord_login: 'Entrar com Discord', + com_auth_google_login: 'Login com Google', + com_auth_facebook_login: 'Login com Facebook', + com_auth_github_login: 'Login com Github', + com_auth_discord_login: 'Login com Discord', com_auth_email: 'Email', com_auth_email_required: 'Email é obrigatório', - com_auth_email_min_length: 'Email deve ter pelo menos 6 caracteres', - com_auth_email_max_length: 'Email não deve ser maior que 120 caracteres', - com_auth_email_pattern: 'Você deve digitar um endereço de email válido', + com_auth_email_min_length: 'O email deve ter pelo menos 6 caracteres', + com_auth_email_max_length: 'O email não deve ter mais de 120 caracteres', + com_auth_email_pattern: 'Você deve inserir um endereço de email válido', com_auth_email_address: 'Endereço de email', com_auth_password: 'Senha', com_auth_password_required: 'Senha é obrigatória', - com_auth_password_min_length: 'Senha deve ter pelo menos 8 caracteres', - com_auth_password_max_length: 'Senha deve ter menos de 128 caracteres', + com_auth_password_min_length: 'A senha deve ter pelo menos 8 caracteres', + com_auth_password_max_length: 'A senha deve ter menos de 128 caracteres', com_auth_password_forgot: 'Esqueceu a senha?', - com_auth_password_confirm: 'Confirmar senha', - com_auth_password_not_match: 'Senhas não coincidem', + com_auth_password_confirm: 'Confirme a senha', + com_auth_password_not_match: 'As senhas não coincidem', com_auth_continue: 'Continuar', com_auth_create_account: 'Crie sua conta', com_auth_error_create: 'Houve um erro ao tentar registrar sua conta. Por favor, tente novamente.', com_auth_full_name: 'Nome completo', com_auth_name_required: 'Nome é obrigatório', - com_auth_name_min_length: 'Nome deve ter pelo menos 3 caracteres', - com_auth_name_max_length: 'Nome deve ter menos de 80 caracteres', + com_auth_name_min_length: 'O nome deve ter pelo menos 3 caracteres', + com_auth_name_max_length: 'O nome deve ter menos de 80 caracteres', com_auth_username: 'Nome de usuário (opcional)', com_auth_username_required: 'Nome de usuário é obrigatório', - com_auth_username_min_length: 'Nome de usuário deve ter pelo menos 2 caracteres', - com_auth_username_max_length: 'Nome de usuário deve ter menos de 20 caracteres', + com_auth_username_min_length: 'O nome de usuário deve ter pelo menos 2 caracteres', + com_auth_username_max_length: 'O nome de usuário deve ter menos de 20 caracteres', com_auth_already_have_account: 'Já tem uma conta?', com_auth_login: 'Login', com_auth_reset_password: 'Redefina sua senha', @@ -99,8 +105,8 @@ export default { com_auth_reset_password_email_sent: 'Um email foi enviado para você com mais instruções para redefinir sua senha.', com_auth_error_reset_password: - 'Houve um problema ao redefinir sua senha. Não foi encontrado nenhum usuário com o endereço de email fornecido. Por favor, tente novamente.', - com_auth_reset_password_success: 'Redefinição de Senha com Sucesso', + 'Houve um problema para redefinir sua senha. Não foi encontrado nenhum usuário com o endereço de email fornecido. Por favor, tente novamente.', + com_auth_reset_password_success: 'Redefinição de Senha Bem-sucedida', com_auth_login_with_new_password: 'Agora você pode fazer login com sua nova senha.', com_auth_error_invalid_reset_token: 'Este token de redefinição de senha não é mais válido.', com_auth_click_here: 'Clique aqui', @@ -108,14 +114,16 @@ export default { com_auth_submit_registration: 'Enviar registro', com_auth_welcome_back: 'Bem-vindo de volta', com_endpoint_open_menu: 'Abrir Menu', - com_endpoint_bing_enable_sydney: 'Ativar Sydney', - com_endpoint_bing_to_enable_sydney: 'Para ativar Sydney', + com_endpoint_bing_enable_sydney: 'Habilitar Sydney', + com_endpoint_bing_to_enable_sydney: 'Para habilitar Sydney', com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: - 'Bing pode usar até 7k tokens para "contexto", que ele pode referenciar para a conversa. O limite específico não é conhecido, mas pode gerar erros se exceder 7k tokens', + 'O Bing pode usar até 7k tokens para \'contexto\', que ele pode referenciar para a conversa. O limite específico não é conhecido, mas pode ocorrer erros ao exceder 7k tokens', com_endpoint_bing_system_message_placeholder: - 'AVISO: O uso indevido deste recurso pode fazer com que você seja BANIDO de usar o Bing! Clique em "Mensagem do Sistema" para obter instruções completas e a mensagem padrão se omitida, que é o preset "Sydney" que é considerado seguro.', + 'AVISO: O uso indevido deste recurso pode fazer com que você seja BANIDO de usar o Bing! Clique em \'System Message\' para obter instruções completas e a mensagem padrão se omitida, que é o preset \'Sydney\' que é considerado seguro.', com_endpoint_system_message: 'Mensagem do Sistema', + com_endpoint_message: 'Mensagem', + com_endpoint_message_not_appendable: 'Edite sua mensagem ou Regenere.', com_endpoint_default_blank: 'padrão: em branco', com_endpoint_default_false: 'padrão: falso', com_endpoint_default_creative: 'padrão: criativo', @@ -128,46 +136,46 @@ export default { com_endpoint_google_temp: 'Valores mais altos = mais aleatório, enquanto valores mais baixos = mais focado e determinístico. Recomendamos alterar isso ou Top P, mas não ambos.', com_endpoint_google_topp: - 'Top-p muda como o modelo seleciona tokens para a saída. Tokens são selecionados dos mais K (veja o parâmetro topK) prováveis para os menos até que a soma de suas probabilidades seja igual ao valor de top-p.', + 'Top-p muda como o modelo seleciona tokens para saída. Os tokens são selecionados do mais K (veja o parâmetro topK) provável ao menos até que a soma de suas probabilidades seja igual ao valor de top-p.', com_endpoint_google_topk: - 'Top-k muda como o modelo seleciona tokens para a saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).', + 'Top-k muda como o modelo seleciona tokens para saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).', com_endpoint_google_maxoutputtokens: 'Número máximo de tokens que podem ser gerados na resposta. Especifique um valor menor para respostas mais curtas e um valor maior para respostas mais longas.', com_endpoint_google_custom_name_placeholder: 'Defina um nome personalizado para o Google', com_endpoint_prompt_prefix_placeholder: - 'Defina instruções ou contexto personalizados. Ignorado se vazio.', + 'Defina instruções personalizadas ou contexto. Ignorado se vazio.', com_endpoint_custom_name: 'Nome Personalizado', com_endpoint_prompt_prefix: 'Prefixo do Prompt', com_endpoint_temperature: 'Temperatura', com_endpoint_default: 'padrão', com_endpoint_top_p: 'Top P', com_endpoint_top_k: 'Top K', - com_endpoint_max_output_tokens: 'Tokens Máximos de Saída', + com_endpoint_max_output_tokens: 'Max Output Tokens', com_endpoint_openai_temp: 'Valores mais altos = mais aleatório, enquanto valores mais baixos = mais focado e determinístico. Recomendamos alterar isso ou Top P, mas não ambos.', com_endpoint_openai_max: 'Os tokens máximos para gerar. O comprimento total dos tokens de entrada e dos tokens gerados é limitado pelo comprimento do contexto do modelo.', com_endpoint_openai_topp: - 'Uma alternativa à amostragem com temperatura, chamada de amostragem de núcleo, onde o modelo considera os resultados dos tokens com massa de probabilidade top_p. Então 0.1 significa que apenas os tokens que compõem os 10% de massa de probabilidade mais alta são considerados. Recomendamos alterar isso ou temperatura, mas não ambos.', + 'Uma alternativa para amostragem com temperatura, chamada amostragem de núcleo, onde o modelo considera os resultados dos tokens com massa de probabilidade top_p. Então 0.1 significa que apenas os tokens que compõem a massa de probabilidade dos 10% principais são considerados. Recomendamos alterar isso ou a temperatura, mas não ambos.', com_endpoint_openai_freq: 'Número entre -2.0 e 2.0. Valores positivos penalizam novos tokens com base em sua frequência existente no texto até agora, diminuindo a probabilidade do modelo de repetir a mesma linha literalmente.', com_endpoint_openai_pres: 'Número entre -2.0 e 2.0. Valores positivos penalizam novos tokens com base em se eles aparecem no texto até agora, aumentando a probabilidade do modelo de falar sobre novos tópicos.', - com_endpoint_openai_custom_name_placeholder: 'Defina um nome personalizado para o ChatGPT', + com_endpoint_openai_custom_name_placeholder: 'Defina um nome personalizado para ChatGPT', com_endpoint_openai_prompt_prefix_placeholder: 'Defina instruções personalizadas para incluir na Mensagem do Sistema. Padrão: nenhum', com_endpoint_anthropic_temp: 'Varia de 0 a 1. Use temp mais próximo de 0 para analítico / múltipla escolha, e mais próximo de 1 para tarefas criativas e gerativas. Recomendamos alterar isso ou Top P, mas não ambos.', com_endpoint_anthropic_topp: - 'Top-p muda como o modelo seleciona tokens para a saída. Tokens são selecionados dos mais K (veja o parâmetro topK) prováveis para os menos até que a soma de suas probabilidades seja igual ao valor de top-p.', + 'Top-p muda como o modelo seleciona tokens para saída. Os tokens são selecionados do mais K (veja o parâmetro topK) provável ao menos até que a soma de suas probabilidades seja igual ao valor de top-p.', com_endpoint_anthropic_topk: - 'Top-k muda como o modelo seleciona tokens para a saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).', + 'Top-k muda como o modelo seleciona tokens para saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).', com_endpoint_anthropic_maxoutputtokens: 'Número máximo de tokens que podem ser gerados na resposta. Especifique um valor menor para respostas mais curtas e um valor maior para respostas mais longas.', - com_endpoint_anthropic_custom_name_placeholder: 'Defina um nome personalizado para o Anthropic', + com_endpoint_anthropic_custom_name_placeholder: 'Defina um nome personalizado para Anthropic', com_endpoint_frequency_penalty: 'Penalidade de Frequência', com_endpoint_presence_penalty: 'Penalidade de Presença', - com_endpoint_plug_use_functions: 'Usar Funções', + com_endpoint_plug_use_functions: 'Use Funções', com_endpoint_plug_skip_completion: 'Pular Conclusão', com_endpoint_disabled_with_tools: 'desativado com ferramentas', com_endpoint_disabled_with_tools_placeholder: 'Desativado com Ferramentas Selecionadas', @@ -176,61 +184,89 @@ export default { com_endpoint_import: 'Importar', com_endpoint_set_custom_name: 'Defina um nome personalizado, caso você possa encontrar este preset', + com_endpoint_preset_delete_confirm: 'Você tem certeza de que deseja excluir este preset?', + com_endpoint_preset_clear_all_confirm: + 'Você tem certeza de que deseja excluir todos os seus presets?', + com_endpoint_preset_import: 'Preset Importado!', + com_endpoint_preset_import_error: + 'Houve um erro ao importar seu preset. Por favor, tente novamente.', + com_endpoint_preset_save_error: 'Houve um erro ao salvar seu preset. Por favor, tente novamente.', + com_endpoint_preset_delete_error: + 'Houve um erro ao excluir seu preset. Por favor, tente novamente.', + com_endpoint_preset_default_removed: 'não é mais o preset padrão.', + com_endpoint_preset_default_item: 'Padrão:', + com_endpoint_preset_default_none: 'Nenhum preset padrão ativo.', + com_endpoint_preset_title: 'Preset', + com_endpoint_preset_saved: 'Salvo!', + com_endpoint_preset_default: 'é agora o preset padrão.', com_endpoint_preset: 'preset', com_endpoint_presets: 'presets', + com_endpoint_preset_selected: 'Preset Ativo!', + com_endpoint_preset_selected_title: 'Ativo!', com_endpoint_preset_name: 'Nome do Preset', com_endpoint_new_topic: 'Novo Tópico', com_endpoint: 'Endpoint', - com_endpoint_hide: 'Ocultar', + com_endpoint_hide: 'Esconder', com_endpoint_show: 'Mostrar', com_endpoint_examples: ' Presets', com_endpoint_completion: 'Conclusão', com_endpoint_agent: 'Agente', - com_endpoint_show_what_settings: 'Mostrar Configurações de {0}', + com_endpoint_show_what_settings: 'Mostrar {0} Configurações', com_endpoint_save: 'Salvar', com_endpoint_export: 'Exportar', com_endpoint_save_as_preset: 'Salvar como Preset', com_endpoint_presets_clear_warning: - 'Tem certeza de que deseja limpar todos os presets? Isso é irreversível.', + 'Você tem certeza de que deseja limpar todos os presets? Isso é irreversível.', com_endpoint_not_implemented: 'Não implementado', com_endpoint_no_presets: 'Ainda não há presets, use o botão de configurações para criar um', com_endpoint_not_available: 'Nenhum endpoint disponível', com_endpoint_view_options: 'Ver Opções', com_endpoint_save_convo_as_preset: 'Salvar Conversa como Preset', com_endpoint_my_preset: 'Meu Preset', - com_endpoint_agent_model: 'Modelo de Agente (Recomendado: GPT-3.5)', + com_endpoint_agent_model: 'Modelo do Agente (Recomendado: GPT-3.5)', com_endpoint_completion_model: 'Modelo de Conclusão (Recomendado: GPT-4)', - com_endpoint_func_hover: 'Ativar o uso de Plugins como Funções OpenAI', + com_endpoint_func_hover: 'Habilitar uso de Plugins como Funções OpenAI', com_endpoint_skip_hover: - 'Ativar a omissão da etapa de conclusão, que revisa a resposta final e as etapas geradas', - com_endpoint_config_key: 'Definir Chave de API', - com_endpoint_config_key_for: 'Definir Chave de API para', + 'Habilitar a etapa de conclusão de pulo, que revisa a resposta final e as etapas geradas', + com_endpoint_config_key: 'Definir Chave API', + com_endpoint_config_placeholder: 'Defina sua Chave no menu Cabeçalho para conversar.', + com_endpoint_config_key_for: 'Definir Chave API para', com_endpoint_config_key_name: 'Chave', - com_endpoint_config_value: 'Digite o valor para', - com_endpoint_config_key_name_placeholder: 'Defina a chave de API primeiro', + com_endpoint_config_value: 'Insira valor para', + com_endpoint_config_key_name_placeholder: 'Defina a chave API primeiro', com_endpoint_config_key_encryption: 'Sua chave será criptografada e excluída em', com_endpoint_config_key_expiry: 'o tempo de expiração', + com_endpoint_config_click_here: 'Clique Aqui', + com_endpoint_config_google_service_key: 'Chave da Conta de Serviço do Google', + com_endpoint_config_google_cloud_platform: '(do Google Cloud Platform)', + com_endpoint_config_google_api_key: 'Chave API do Google', + com_endpoint_config_google_gemini_api: '(API Gemini)', + com_endpoint_config_google_api_info: + 'Para obter sua chave API de Linguagem Generativa (para Gemini),', com_endpoint_config_key_import_json_key: 'Importar Chave JSON da Conta de Serviço.', com_endpoint_config_key_import_json_key_success: - 'Chave JSON da Conta de Serviço Importada com Sucesso', + 'Chave JSON da Conta de Serviço importada com sucesso', com_endpoint_config_key_import_json_key_invalid: - 'Chave JSON da Conta de Serviço Inválida, Você importou o arquivo correto?', + 'Chave JSON da Conta de Serviço inválida, você importou o arquivo correto?', com_endpoint_config_key_get_edge_key: 'Para obter seu token de acesso para o Bing, faça login em', com_endpoint_config_key_get_edge_key_dev_tool: 'Use as ferramentas de desenvolvimento ou uma extensão enquanto estiver logado no site para copiar o conteúdo do cookie _U. Se isso falhar, siga estas', com_endpoint_config_key_edge_instructions: 'instruções', - com_endpoint_config_key_edge_full_key_string: 'para fornecer as strings completas dos cookies.', + com_endpoint_config_key_edge_full_key_string: 'para fornecer as strings completas do cookie.', com_endpoint_config_key_chatgpt: - 'Para obter seu token de acesso para o ChatGPT "Versão Gratuita", faça login em', - com_endpoint_config_key_chatgpt_then_visit: 'depois visite', + 'Para obter seu token de acesso para o ChatGPT \'Versão Gratuita\', faça login em', + com_endpoint_config_key_chatgpt_then_visit: 'então visite', com_endpoint_config_key_chatgpt_copy_token: 'Copie o token de acesso.', com_endpoint_config_key_google_need_to: 'Você precisa', - com_endpoint_config_key_google_vertex_ai: 'Ativar o Vertex AI', - com_endpoint_config_key_google_vertex_api: 'API no Google Cloud, depois', + com_endpoint_config_key_google_vertex_ai: 'Habilitar Vertex AI', + com_endpoint_config_key_google_vertex_api: 'API no Google Cloud, então', + com_endpoint_config_key_google_service_account: 'Crie uma Conta de Serviço', com_endpoint_config_key_google_vertex_api_role: - 'Certifique-se de clicar em "Criar e Continuar" para dar pelo menos a função de "Usuário do Vertex AI". Por último, crie uma chave JSON para importar aqui.', - com_nav_welcome_message: 'Como eu posso ajudar hoje?', - com_nav_auto_scroll: 'Auto-rolar para o mais recente ao abrir', + 'Certifique-se de clicar em \'Criar e Continuar\' para dar pelo menos a função \'Usuário do Vertex AI\'. Por último, crie uma chave JSON para importar aqui.', + com_nav_welcome_message: 'Como posso ajudá-lo hoje?', + com_nav_auto_scroll: 'Auto-rolagem para o Mais Novo ao Abrir', + com_nav_profile_picture: 'Foto de Perfil', + com_nav_change_picture: 'Mudar foto', com_nav_plugin_store: 'Loja de plugins', com_nav_plugin_search: 'Pesquisar plugins', com_nav_plugin_auth_error: @@ -259,9 +295,13 @@ export default { com_nav_clear_conversation: 'Limpar conversas', com_nav_clear_conversation_confirm_message: 'Tem certeza de que deseja limpar todas as conversas? Isso é irreversível.', - com_nav_help_faq: 'Ajuda e FAQ', + com_nav_help_faq: 'Ajuda & FAQ', com_nav_settings: 'Configurações', com_nav_search_placeholder: 'Pesquisar mensagens', com_nav_setting_general: 'Geral', com_nav_setting_data: 'Controles de dados', + com_nav_setting_account: 'Conta', + com_nav_language: 'Idioma', + com_nav_lang_auto: 'Detecção automática', + com_nav_lang_brazilian_portuguese: 'Português Brasileiro', }; diff --git a/client/src/localization/languages/De.tsx b/client/src/localization/languages/De.tsx index 7abf98e87c0..ec6fa99d015 100644 --- a/client/src/localization/languages/De.tsx +++ b/client/src/localization/languages/De.tsx @@ -201,5 +201,4 @@ export default { com_nav_settings: 'Einstellungen', com_nav_search_placeholder: 'Durchsuche Nachrichten', com_nav_setting_general: 'Generell', - com_nav_lang_german: 'Deutsch', }; diff --git a/client/src/localization/languages/Eng.tsx b/client/src/localization/languages/Eng.tsx index 8b3db4a56d2..8a431707bcb 100644 --- a/client/src/localization/languages/Eng.tsx +++ b/client/src/localization/languages/Eng.tsx @@ -33,7 +33,8 @@ export default { com_ui_enter: 'Enter', com_ui_submit: 'Submit', com_ui_upload_success: 'Successfully uploaded file', - com_ui_upload_invalid: 'Invalid file for upload', + com_ui_upload_error: 'There was an error uploading your file', + com_ui_upload_invalid: 'Invalid file for upload. Must be an image not exceeding 2 MB', com_ui_cancel: 'Cancel', com_ui_save: 'Save', com_ui_copy_to_clipboard: 'Copy to clipboard', @@ -51,6 +52,9 @@ export default { com_ui_delete: 'Delete', com_ui_delete_conversation: 'Delete chat?', com_ui_delete_conversation_confirm: 'This will delete', + com_ui_preview: 'Preview', + com_ui_upload: 'Upload', + com_ui_connect: 'Connect', com_auth_error_login: 'Unable to login with the information provided. Please check your credentials and try again.', com_auth_error_login_rl: @@ -227,6 +231,12 @@ export default { com_endpoint_config_key_name_placeholder: 'Set API key first', com_endpoint_config_key_encryption: 'Your key will be encrypted and deleted at', com_endpoint_config_key_expiry: 'the expiry time', + com_endpoint_config_click_here: 'Click Here', + com_endpoint_config_google_service_key: 'Google Service Account Key', + com_endpoint_config_google_cloud_platform: '(from Google Cloud Platform)', + com_endpoint_config_google_api_key: 'Google API Key', + com_endpoint_config_google_gemini_api: '(Gemini API)', + com_endpoint_config_google_api_info: 'To get your Generative Language API key (for Gemini),', com_endpoint_config_key_import_json_key: 'Import Service Account JSON Key.', com_endpoint_config_key_import_json_key_success: 'Successfully Imported Service Account JSON Key', com_endpoint_config_key_import_json_key_invalid: @@ -247,6 +257,8 @@ export default { 'Make sure to click \'Create and Continue\' to give at least the \'Vertex AI User\' role. Lastly, create a JSON key to import here.', com_nav_welcome_message: 'How can I help you today?', com_nav_auto_scroll: 'Auto-scroll to Newest on Open', + com_nav_profile_picture: 'Profile Picture', + com_nav_change_picture: 'Change picture', com_nav_plugin_store: 'Plugin store', com_nav_plugin_search: 'Search plugins', com_nav_plugin_auth_error: @@ -280,6 +292,7 @@ export default { com_nav_search_placeholder: 'Search messages', com_nav_setting_general: 'General', com_nav_setting_data: 'Data controls', + com_nav_setting_account: 'Account', com_nav_language: 'Language', com_nav_lang_auto: 'Auto detect', com_nav_lang_english: 'English', diff --git a/client/src/localization/languages/Fr.tsx b/client/src/localization/languages/Fr.tsx index a97aea8c478..db1072e2aa9 100644 --- a/client/src/localization/languages/Fr.tsx +++ b/client/src/localization/languages/Fr.tsx @@ -28,14 +28,17 @@ export default { com_ui_prompt_templates: 'Modèles de prompt', com_ui_hide_prompt_templates: 'Masquer les modèles de prompt', com_ui_showing: 'Affichage', - com_ui_of: 'de', - com_ui_entries: 'Entrées', + com_ui_of: 'des', + com_ui_entries: 'entrées', com_ui_pay_per_call: 'Toutes les conversations IA au même endroit. Payez à la demande et non par mois', + com_ui_new_footer: 'Toutes les conversations IA au même endroit.', com_ui_enter: 'Entrer', com_ui_submit: 'Soumettre', - com_ui_upload_success: 'Fichier téléchargé avec succès', - com_ui_upload_invalid: 'Fichier invalide pour le téléchargement', + com_ui_upload_success: 'Fichier téléversé avec succès', + com_ui_upload_error: 'Une erreur s\'est produite lors du téléversement de votre fichier', + com_ui_upload_invalid: + 'Fichier invalide pour le téléversement. Doit être une image ne dépassant pas 2 Mo', com_ui_cancel: 'Annuler', com_ui_save: 'Sauvegarder', com_ui_copy_to_clipboard: 'Copier dans le presse-papier', @@ -54,6 +57,9 @@ export default { com_ui_delete: 'Supprimer', com_ui_delete_conversation: 'Supprimer la discussions?', com_ui_delete_conversation_confirm: 'Cela supprimera', + com_ui_preview: 'Aperçu', + com_ui_upload: 'Téléverser', + com_ui_connect: 'Connecter', com_auth_error_login: 'Impossible de se connecter avec les informations fournies. Veuillez vérifier vos identifiants et réessayer.', com_auth_error_login_rl: @@ -62,7 +68,7 @@ export default { 'Votre compte a été temporairement banni en raison de violations de notre service.', com_auth_error_login_server: 'Une erreur interne du serveur s\'est produite. Veuillez patienter quelques instants et réessayer.', - com_auth_no_account: 'Vous n\'avez pas de compte ?', + com_auth_no_account: 'Vous n\'avez pas de compte?', com_auth_sign_up: 'S\'inscrire', com_auth_sign_in: 'Se connecter', com_auth_google_login: 'Se connecter avec Google', @@ -79,7 +85,7 @@ export default { com_auth_password_required: 'Le mot de passe est obligatoire', com_auth_password_min_length: 'Le mot de passe doit comporter au moins 8 caractères', com_auth_password_max_length: 'Le mot de passe doit être inférieur à 128 caractères', - com_auth_password_forgot: 'Mot de passe oublié ?', + com_auth_password_forgot: 'Mot de passe oublié?', com_auth_password_confirm: 'Confirmer le mot de passe', com_auth_password_not_match: 'Les mots de passe ne correspondent pas', com_auth_continue: 'Continuer', @@ -121,8 +127,10 @@ export default { com_endpoint_bing_context_placeholder: 'Bing peut utiliser jusqu\'à 7k jetons pour le "contexte", qu\'il peut référencer pour la conversation. La limite spécifique n\'est pas connue mais peut entraîner des erreurs dépassant les 7k jetons', com_endpoint_bing_system_message_placeholder: - 'AVERTISSEMENT : L\'abus de cette fonctionnalité peut vous faire BANNIR de l\'utilisation de Bing ! Cliquez sur "Message système" pour obtenir les instructions complètes et le message par défaut si omis, qui est le préréglage "Sydney" qui est considéré comme sûr.', + 'AVERTISSEMENT : L\'abus de cette fonctionnalité peut vous faire BANNIR de l\'utilisation de Bing! Cliquez sur "Message système" pour obtenir les instructions complètes et le message par défaut si omis, qui est le préréglage "Sydney" qui est considéré comme sûr.', com_endpoint_system_message: 'Message système', + com_endpoint_message: 'Message', + com_endpoint_message_not_appendable: 'Editer votre message ou regénerer.', com_endpoint_default_blank: 'par défaut : vide', com_endpoint_default_false: 'par défaut : faux', com_endpoint_default_creative: 'par défaut : créatif', @@ -183,8 +191,25 @@ export default { com_endpoint_import: 'Importer', com_endpoint_set_custom_name: 'Définir un nom personnalisé, au cas où vous trouveriez ce préréglage', + com_endpoint_preset_delete_confirm: 'Êtes-vous sûr de vouloir supprimer ce préréglage?', + com_endpoint_preset_clear_all_confirm: 'Êtes-vous sûr de vouloir supprimer tous vos préréglages?', + com_endpoint_preset_import: 'Préréglage importé!', + com_endpoint_preset_import_error: + 'Il y a eu une erreur lors de l\'importation de votre préréglage. Veuillez réessayer.', + com_endpoint_preset_save_error: + 'Il y a eu une erreur lors de la sauvegarde de votre préréglage. Veuillez réessayer.', + com_endpoint_preset_delete_error: + 'Il y a eu une erreur lors de la suppression de votre préréglage. Veuillez réessayer.', + com_endpoint_preset_default_removed: 'n\'est plus le préréglage par défaut.', + com_endpoint_preset_default_item: 'Par défaut :', + com_endpoint_preset_default_none: 'Aucun préréglage par défaut actif.', + com_endpoint_preset_title: 'Préréglage', + com_endpoint_preset_saved: 'Enregistré!', + com_endpoint_preset_default: 'est maintenant le préréglage par défaut.', com_endpoint_preset: 'préréglage', com_endpoint_presets: 'préréglages', + com_endpoint_preset_selected: 'Préréglage actif!', + com_endpoint_preset_selected_title: 'Actif!', com_endpoint_preset_name: 'Nom du préréglage', com_endpoint_new_topic: 'Nouveau sujet', com_endpoint: 'Endpoint', @@ -198,9 +223,10 @@ export default { com_endpoint_export: 'Exporter', com_endpoint_save_as_preset: 'Enregistrer comme préréglage', com_endpoint_presets_clear_warning: - 'Etes-vous sûr de vouloir effacer tous les préréglages ? Cette action est irréversible.', + 'Etes-vous sûr de vouloir effacer tous les préréglages? Cette action est irréversible.', com_endpoint_not_implemented: 'Non implémenté', - com_endpoint_no_presets: 'Aucun préréglage', + com_endpoint_no_presets: + 'Aucun préréglage pour l\'instant, utilisez le bouton paramètres pour en créer un', com_endpoint_not_available: 'Aucun endpoint disponible', com_endpoint_view_options: 'Voir les options', com_endpoint_save_convo_as_preset: 'Enregistrer la conversation comme préréglage', @@ -211,12 +237,20 @@ export default { com_endpoint_skip_hover: 'Activer le saut de l\'étape de complétion, qui examine la réponse finale et les étapes générées', com_endpoint_config_key: 'Définir la clé API', + com_endpoint_config_placeholder: 'Définissez votre clé dans le menu En-tête pour discuter.', com_endpoint_config_key_for: 'Définir la clé API pour', com_endpoint_config_key_name: 'Clé', com_endpoint_config_value: 'Entrez la valeur pour', com_endpoint_config_key_name_placeholder: 'Définir d\'abord la clé API', com_endpoint_config_key_encryption: 'Votre clé sera cryptée et supprimée à', - com_endpoint_config_key_expiry: 'l\'heure d\'expiration', + com_endpoint_config_key_expiry: 'le délai d\'expiration', + com_endpoint_config_click_here: 'Cliquez ici', + com_endpoint_config_google_service_key: 'Clé de compte de service Google', + com_endpoint_config_google_cloud_platform: '(de Google Cloud Platform)', + com_endpoint_config_google_api_key: 'Clé API Google', + com_endpoint_config_google_gemini_api: '(API Gemini)', + com_endpoint_config_google_api_info: + 'Pour obtenir votre clé API de langage génératif (pour Gemini),', com_endpoint_config_key_import_json_key: 'Importez la clé JSON du compte de service.', com_endpoint_config_key_import_json_key_success: 'Clé JSON du compte de service importé avec succès', @@ -238,7 +272,10 @@ export default { com_endpoint_config_key_google_service_account: 'Créer un compte de service', com_endpoint_config_key_google_vertex_api_role: 'Assurez-vous de cliquer \'Créer et continuer\' pour donner au moins le role \'Utilisateur de Vertex AI\'. Finalement, créez une clé JSON à importer ici.', + com_nav_welcome_message: 'Comment puis-je vous aider aujourd\'hui?', com_nav_auto_scroll: 'Défilement automatique jusqu\'au plus récent à l\'ouverture', + com_nav_profile_picture: 'Photo de profil', + com_nav_change_picture: 'Changer de photo', com_nav_plugin_store: 'Boutique de plugins', com_nav_plugin_search: 'Rechercher des plugins', com_nav_plugin_auth_error: @@ -272,4 +309,5 @@ export default { com_nav_search_placeholder: 'Rechercher des messages', com_nav_setting_general: 'Général', com_nav_setting_data: 'Contrôles des données', + com_nav_setting_account: 'Compte', }; diff --git a/client/src/localization/languages/It.tsx b/client/src/localization/languages/It.tsx index 9c274f54133..175b7820dbb 100644 --- a/client/src/localization/languages/It.tsx +++ b/client/src/localization/languages/It.tsx @@ -3,17 +3,18 @@ export default { com_ui_examples: 'Esempi', com_ui_new_chat: 'Nuova Chat', - com_ui_example_quantum_computing: 'Spiega il computing quantistico in termini semplici', - com_ui_example_10_year_old_b_day: 'Hai idee creative per il compleanno di un bambino di 10 anni?', + com_ui_example_quantum_computing: 'Spiega l\'informatica quantistica in termini semplici', + com_ui_example_10_year_old_b_day: + 'Hai qualche idea creativa per il compleanno di un bambino di 10 anni?', com_ui_example_http_in_js: 'Come faccio una richiesta HTTP in Javascript?', com_ui_capabilities: 'Funzionalità', - com_ui_capability_remember: 'Ricorda cosa ha detto l\'utente precedentemente nella conversazione', - com_ui_capability_correction: 'Permette all\'utente di fornire correzioni di follow-up', + com_ui_capability_remember: 'Ricorda cosa ha detto l\'utente prima nella conversazione', + com_ui_capability_correction: 'Permette all\'utente di fornire correzioni successive', com_ui_capability_decline_requests: 'Addestrato a rifiutare richieste inappropriate', com_ui_limitations: 'Limitazioni', - com_ui_limitation_incorrect_info: 'Può occasionalmente generare informazioni scorrette', + com_ui_limitation_incorrect_info: 'Può occasionalmente generare informazioni errate', com_ui_limitation_harmful_biased: - 'Può occasionalmente produrre istruzioni dannose o contenuti faziosi', + 'Può occasionalmente produrre istruzioni dannose o contenuti di parte', com_ui_limitation_limited_2021: 'Conoscenza limitata del mondo e degli eventi dopo il 2021', com_ui_input: 'Input', com_ui_close: 'Chiudi', @@ -21,19 +22,19 @@ export default { com_ui_select_model: 'Seleziona un modello', com_ui_use_prompt: 'Usa prompt', com_ui_prev: 'Prec', - com_ui_next: 'Succ', - com_ui_stop: 'Stop', + com_ui_next: 'Successivo', + com_ui_stop: 'Ferma', com_ui_prompt_templates: 'Modelli di prompt', com_ui_hide_prompt_templates: 'Nascondi modelli di prompt', com_ui_showing: 'Mostra', com_ui_of: 'di', com_ui_entries: 'Voci', com_ui_pay_per_call: - 'Tutte le conversazioni AI in un unico posto. Paga per chiamata e non per mese', - com_ui_new_footer: 'Tutte le conversazioni AI in un unico posto.', + 'Tutte le conversazioni IA in un unico luogo. Paga per chiamata, non per mese', + com_ui_new_footer: 'Tutte le conversazioni IA in un unico luogo.', com_ui_enter: 'Inserisci', com_ui_submit: 'Invia', - com_ui_upload_success: 'File caricato con successo', + com_ui_upload_success: 'File caricato correttamente', com_ui_upload_invalid: 'File non valido per il caricamento', com_ui_cancel: 'Annulla', com_ui_save: 'Salva', @@ -52,45 +53,49 @@ export default { com_ui_delete: 'Elimina', com_ui_delete_conversation: 'Eliminare la chat?', com_ui_delete_conversation_confirm: 'Questo eliminerà', + com_ui_preview: 'Anteprima', + com_ui_upload: 'Carica', + com_ui_connect: 'Connetti', com_auth_error_login: - 'Impossibile accedere con le informazioni fornite. Si prega di controllare le credenziali e riprovare.', + 'Impossibile accedere con le informazioni fornite. Per favore controlla le tue credenziali e riprova.', com_auth_error_login_rl: - 'Troppo tentativi di accesso in uno breve lasso di tempo. Per favore riprova più tardi.', + 'Troppi tentativi di accesso in breve tempo. Per favore riprova più tardi.', com_auth_error_login_ban: - 'Il tuo account è stato temporaneamente bloccato dovuto a violazioni del nostro servizio.', + 'Il tuo account è stato temporaneamente bloccato a causa della violazione del nostro servizio.', com_auth_error_login_server: - 'C\'è stato un errore interno del server. Si prega di attendere qualche istante e riprovare.', + 'Si è verificato un errore interno del server. Attendi qualche istante e riprova.', com_auth_no_account: 'Non hai un account?', - com_auth_sign_up: 'Iscriviti', + com_auth_sign_up: 'Registrati', com_auth_sign_in: 'Accedi', com_auth_google_login: 'Accedi con Google', com_auth_facebook_login: 'Accedi con Facebook', com_auth_github_login: 'Accedi con Github', com_auth_discord_login: 'Accedi con Discord', com_auth_email: 'Email', - com_auth_email_required: 'Email obbligatoria', - com_auth_email_min_length: 'L\'email deve avere almeno 6 caratteri', + com_auth_email_required: 'L\'email è obbligatoria', + com_auth_email_min_length: 'L\'email deve essere lunga almeno 6 caratteri', com_auth_email_max_length: 'L\'email non dovrebbe essere più lunga di 120 caratteri', com_auth_email_pattern: 'Devi inserire un indirizzo email valido', com_auth_email_address: 'Indirizzo email', com_auth_password: 'Password', com_auth_password_required: 'La password è obbligatoria', - com_auth_password_min_length: 'La password deve avere almeno 8 caratteri', - com_auth_password_max_length: 'La password deve essere più breve di 128 caratteri', + com_auth_password_min_length: 'La password deve essere lunga almeno 8 caratteri', + com_auth_password_max_length: 'La password deve essere più corta di 128 caratteri', com_auth_password_forgot: 'Password dimenticata?', com_auth_password_confirm: 'Conferma password', - com_auth_password_not_match: 'Le password non coincidono', + com_auth_password_not_match: 'Le password non corrispondono', com_auth_continue: 'Continua', com_auth_create_account: 'Crea il tuo account', - com_auth_error_create: 'C\'è stato un errore nel tentativo di registrarti. Per favore riprova.', + com_auth_error_create: + 'Si è verificato un errore nel tentativo di registrare il tuo account. Per favore riprova.', com_auth_full_name: 'Nome completo', com_auth_name_required: 'Il nome è obbligatorio', - com_auth_name_min_length: 'Il nome deve avere almeno 3 caratteri', - com_auth_name_max_length: 'Il nome deve essere più breve di 80 caratteri', - com_auth_username: 'Nome utente (opzionale)', + com_auth_name_min_length: 'Il nome deve essere lungo almeno 3 caratteri', + com_auth_name_max_length: 'Il nome deve essere più corto di 80 caratteri', + com_auth_username: 'Nome utente (facoltativo)', com_auth_username_required: 'Il nome utente è obbligatorio', - com_auth_username_min_length: 'Il nome utente deve avere almeno 2 caratteri', - com_auth_username_max_length: 'Il nome utente deve essere più breve di 20 caratteri', + com_auth_username_min_length: 'Il nome utente deve essere lungo almeno 2 caratteri', + com_auth_username_max_length: 'Il nome utente deve essere più corto di 20 caratteri', com_auth_already_have_account: 'Hai già un account?', com_auth_login: 'Accedi', com_auth_reset_password: 'Reimposta la tua password', @@ -99,42 +104,44 @@ export default { com_auth_to_reset_your_password: 'per reimpostare la tua password.', com_auth_reset_password_link_sent: 'Email inviata', com_auth_reset_password_email_sent: - 'Ti è stata inviata una email con ulteriori istruzioni per reimpostare la password.', + 'Ti è stata inviata un\'email con ulteriori istruzioni per reimpostare la tua password.', com_auth_error_reset_password: - 'C\'è stato un problema nel reimpostare la password. Non è stato trovato nessun utente con l\'indirizzo email fornito. Per favore riprova.', - com_auth_reset_password_success: 'Password reimpostata con successo', + 'Si è verificato un problema durante il reset della password. Non è stato trovato nessun utente con l\'indirizzo email fornito. Per favore riprova.', + com_auth_reset_password_success: 'Reset password avvenuto con successo', com_auth_login_with_new_password: 'Ora puoi accedere con la tua nuova password.', - com_auth_error_invalid_reset_token: 'Questo token di reimpostazione password non è più valido.', + com_auth_error_invalid_reset_token: 'Questo token di reset password non è più valido.', com_auth_click_here: 'Clicca qui', - com_auth_to_try_again: 'per provare di nuovo.', + com_auth_to_try_again: 'per riprovare.', com_auth_submit_registration: 'Invia registrazione', - com_auth_welcome_back: 'Ben tornato', - com_endpoint_open_menu: 'Apri menu', + com_auth_welcome_back: 'Bentornato', + com_endpoint_open_menu: 'Apri il menu', com_endpoint_bing_enable_sydney: 'Abilita Sydney', com_endpoint_bing_to_enable_sydney: 'Per abilitare Sydney', - com_endpoint_bing_jailbreak: 'Forza protezioni', + com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: - 'Bing può usare fino a 7k token per "contesto", che può referenziare per la conversazione. Il limite specifico non è noto ma potrebbe andare in errore superando i 7k token', + 'Bing può usare fino a 7k token per "contesto", che può fare riferimento per la conversazione. Il limite specifico non è noto ma può dare errori superando i 7k token', com_endpoint_bing_system_message_placeholder: - 'ATTENZIONE: L\'abuso di questa funzione può farti BANNARE dall\'uso di Bing! Clicca su \'Messaggio di sistema\' per le istruzioni complete e il messaggio predefinito se omesso, che è il preset \'Sydney\' che è considerato sicuro.', + 'ATTENZIONE: L\'abuso di questa funzione può farti BLOCCARE da Bing! Fai clic su "Messaggio di sistema" per le istruzioni complete e il messaggio predefinito se omesso, che è il preset "Sydney" considerato sicuro.', com_endpoint_system_message: 'Messaggio di sistema', - com_endpoint_default_blank: 'predefinito: vuoto', - com_endpoint_default_false: 'predefinito: falso', - com_endpoint_default_creative: 'predefinito: creativo', - com_endpoint_default_empty: 'predefinito: vuoto', - com_endpoint_default_with_num: 'predefinito: {0}', + com_endpoint_message: 'Messaggio', + com_endpoint_message_not_appendable: 'Modifica il tuo messaggio o Rigenera.', + com_endpoint_default_blank: 'default: vuoto', + com_endpoint_default_false: 'default: falso', + com_endpoint_default_creative: 'default: creativo', + com_endpoint_default_empty: 'default: vuoto', + com_endpoint_default_with_num: 'default: {0}', com_endpoint_context: 'Contesto', - com_endpoint_tone_style: 'Stile tono', + com_endpoint_tone_style: 'Stile del tono', com_endpoint_token_count: 'Conteggio token', com_endpoint_output: 'Output', com_endpoint_google_temp: - 'Valori più alti = più casuali, mentre valori più bassi = più mirati e deterministici. Consigliamo di modificare questo o Top P ma non entrambi.', + 'Valori più alti = più casuali, mentre valori più bassi = più focalizzati e deterministici. Si consiglia di modificare questo o Top P ma non entrambi.', com_endpoint_google_topp: - 'Top-p cambia come il modello seleziona i token per l\'output. I token sono selezionati dai K più probabili (vedi parametro topK) ai meno probabili finché la somma delle loro probabilità equivale al valore top-p.', + 'Top-p cambia come il modello seleziona i token per l\'output. I token vengono selezionati dai più probabili K (vedi parametro topK) ai meno probabili fino a che la somma delle loro probabilità eguaglia il valore top-p.', com_endpoint_google_topk: - 'Top-k cambia come il modello seleziona i token per l\'output. Un top-k di 1 significa che il token selezionato è il più probabile tra tutti i token nel vocabolario del modello (anche chiamato decodifica greedy), mentre un top-k di 3 significa che il token successivo è selezionato tra i 3 token più probabili (usando temperature).', + 'Top-k cambia come il modello seleziona i token per l\'output. Un top-k di 1 significa che il token selezionato è il più probabile tra tutti i token nel vocabolario del modello (chiamato anche decodifica avida), mentre un top-k di 3 significa che il token successivo viene selezionato tra i 3 token più probabili (usando la temperatura).', com_endpoint_google_maxoutputtokens: - 'Numero massimo di token che possono essere generati nella risposta. Specifica un valore più basso per risposte più corte e un valore più alto per risposte più lunghe.', + 'Numero massimo di token che possono essere generati nella risposta. Specifica un valore inferiore per risposte più brevi e un valore superiore per risposte più lunghe.', com_endpoint_google_custom_name_placeholder: 'Imposta un nome personalizzato per Google', com_endpoint_prompt_prefix_placeholder: 'Imposta istruzioni o contesto personalizzati. Ignorato se vuoto.', @@ -146,41 +153,57 @@ export default { com_endpoint_top_k: 'Top K', com_endpoint_max_output_tokens: 'Token output massimi', com_endpoint_openai_temp: - 'Valori più alti = più casuali, mentre valori più bassi = più mirati e deterministici. Consigliamo di modificare questo o Top P ma non entrambi.', + 'Valori più alti = più casuali, mentre valori più bassi = più focalizzati e deterministici. Si consiglia di modificare questo o Top P ma non entrambi.', com_endpoint_openai_max: - 'Il numero massimo di token da generare. La lunghezza totale di token di input e token generati è limitata dalla lunghezza di contesto del modello.', + 'Il numero massimo di token da generare. La lunghezza totale dei token di input e generati è limitata dalla lunghezza di contesto del modello.', com_endpoint_openai_topp: - 'Un\'alternativa al campionamento con temperatura, chiamata nucleus sampling, dove il modello considera i risultati dei token con la massa di probabilità top_p. Quindi 0.1 significa che vengono considerati solo i token che comprendono il 10% della massa di probabilità in cima.', + 'Un\'alternativa al campionamento con temperatura, chiamata nucleus sampling, dove il modello considera i risultati dei token con massa di probabilità top_p. Quindi 0.1 significa che vengono considerati solo i token che comprendono il 10% della massa di probabilità.', com_endpoint_openai_freq: - 'Numero tra -2.0 e 2.0. Valori positivi penalizzano nuovi token in base alla loro frequenza esistente nel testo finora, diminuendo la probabilità del modello di ripetere la stessa frase parola per parola.', + 'Numero tra -2.0 e 2.0. Valori positivi penalizzano i nuovi token in base alla loro frequenza esistente nel testo finora, diminuendo la probabilità del modello di ripetere la stessa riga verbatim.', com_endpoint_openai_pres: - 'Numero tra -2.0 e 2.0. Valori positivi penalizzano nuovi token in base al fatto che appaiano nel testo finora, aumentando la probabilità del modello di parlare di nuovi argomenti.', + 'Numero tra -2.0 e 2.0. Valori positivi penalizzano i nuovi token in base al fatto che appaiano nel testo finora, aumentando la probabilità del modello di parlare di nuovi argomenti.', com_endpoint_openai_custom_name_placeholder: 'Imposta un nome personalizzato per ChatGPT', com_endpoint_openai_prompt_prefix_placeholder: - 'Imposta istruzioni personalizzate da includere nel Messaggio di sistema. Predefinito: nessuna', + 'Imposta istruzioni personalizzate da includere nel Messaggio di sistema. Predefinito: nessuno', com_endpoint_anthropic_temp: - 'Intervallo da 0 a 1. Usa temperature più vicine a 0 per compiti analitici/a scelta multipla e più vicine a 1 per compiti creativi e generativi. Consigliamo di modificare questo o Top P ma non entrambi.', + 'Varia da 0 a 1. Usa una temp più vicina a 0 per compiti analitici / a scelta multipla, e più vicina a 1 per compiti creativi e generativi. Si consiglia di modificare questo o Top P ma non entrambi.', com_endpoint_anthropic_topp: - 'Top-p cambia come il modello seleziona i token per l\'output. I token sono selezionati dai K più probabili (vedi parametro topK) ai meno probabili finché la somma delle loro probabilità equivale al valore top-p.', + 'Top-p cambia come il modello seleziona i token per l\'output. I token vengono selezionati dai più probabili K (vedi parametro topK) ai meno probabili fino a che la somma delle loro probabilità eguaglia il valore top-p.', com_endpoint_anthropic_topk: - 'Top-k cambia come il modello seleziona i token per l\'output. Un top-k di 1 significa che il token selezionato è il più probabile tra tutti i token nel vocabolario del modello (anche chiamato decodifica greedy), mentre un top-k di 3 significa che il token successivo è selezionato tra i 3 token più probabili (usando temperature).', + 'Top-k cambia come il modello seleziona i token per l\'output. Un top-k di 1 significa che il token selezionato è il più probabile tra tutti i token nel vocabolario del modello (chiamato anche decodifica avida), mentre un top-k di 3 significa che il token successivo viene selezionato tra i 3 token più probabili (usando la temperatura).', com_endpoint_anthropic_maxoutputtokens: - 'Numero massimo di token che possono essere generati nella risposta. Specifica un valore più basso per risposte più corte e un valore più alto per risposte più lunghe.', + 'Numero massimo di token che possono essere generati nella risposta. Specifica un valore inferiore per risposte più brevi e un valore superiore per risposte più lunghe.', com_endpoint_anthropic_custom_name_placeholder: 'Imposta un nome personalizzato per Anthropic', - com_endpoint_frequency_penalty: 'Penalità frequenza', - com_endpoint_presence_penalty: 'Penalità presenza', - com_endpoint_plug_use_functions: 'Abilita uso Plugin come funzioni OpenAI', - com_endpoint_plug_skip_completion: 'Salta fase Completamento', + com_endpoint_frequency_penalty: 'Penalità di frequenza', + com_endpoint_presence_penalty: 'Penalità di presenza', + com_endpoint_plug_use_functions: 'Usa funzioni', + com_endpoint_plug_skip_completion: 'Salta completamento', com_endpoint_disabled_with_tools: 'disabilitato con strumenti', - com_endpoint_disabled_with_tools_placeholder: 'Disabilitato con Strumenti Selezionati', + com_endpoint_disabled_with_tools_placeholder: 'Disabilitato con strumenti selezionati', com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: - 'Imposta le istruzioni personalizzate da includere nel Messaggio di sistema. Predefinito: nessuna', + 'Imposta istruzioni personalizzate da includere nel Messaggio di sistema. Predefinito: nessuno', com_endpoint_import: 'Importa', com_endpoint_set_custom_name: 'Imposta un nome personalizzato, nel caso tu possa trovare questo preset', + com_endpoint_preset_delete_confirm: 'Sei sicuro di voler eliminare questo preset?', + com_endpoint_preset_clear_all_confirm: 'Sei sicuro di voler eliminare tutti i tuoi preset?', + com_endpoint_preset_import: 'Preset importato!', + com_endpoint_preset_import_error: + 'Si è verificato un errore durante l\'importazione del tuo preset. Per favore riprova.', + com_endpoint_preset_save_error: + 'Si è verificato un errore durante il salvataggio del tuo preset. Per favore riprova.', + com_endpoint_preset_delete_error: + 'Si è verificato un errore durante l\'eliminazione del tuo preset. Per favore riprova.', + com_endpoint_preset_default_removed: 'non è più il preset predefinito.', + com_endpoint_preset_default_item: 'Predefinito:', + com_endpoint_preset_default_none: 'Nessun preset predefinito attivo.', + com_endpoint_preset_title: 'Preset', + com_endpoint_preset_saved: 'Salvato!', + com_endpoint_preset_default: 'è ora il preset predefinito.', com_endpoint_preset: 'preset', com_endpoint_presets: 'preset', com_endpoint_preset_selected: 'Preset attivo!', + com_endpoint_preset_selected_title: 'Attivo!', com_endpoint_preset_name: 'Nome preset', com_endpoint_new_topic: 'Nuovo argomento', com_endpoint: 'Endpoint', @@ -194,59 +217,68 @@ export default { com_endpoint_export: 'Esporta', com_endpoint_save_as_preset: 'Salva come preset', com_endpoint_presets_clear_warning: - 'Sei sicuro di voler cancellare tutti i preset? Questa azione sarà irreversibile.', + 'Sei sicuro di voler cancellare tutti i preset? Questa operazione è irreversibile.', com_endpoint_not_implemented: 'Non implementato', - com_endpoint_no_presets: 'Nessun preset ancora', + com_endpoint_no_presets: 'Nessun preset ancora, usa il pulsante impostazioni per crearne uno', com_endpoint_not_available: 'Nessun endpoint disponibile', - com_endpoint_clear_all: 'Cancella tutto', - com_endpoint_view_options: 'Visualizza opzioni', + com_endpoint_view_options: 'Opzioni di visualizzazione', com_endpoint_save_convo_as_preset: 'Salva conversazione come preset', - com_endpoint_my_preset: 'Il mio preimpostato', - com_endpoint_agent_model: 'Modello Agente (Consigliato: GPT-3.5)', - com_endpoint_completion_model: 'Modello Completamento (Consigliato: GPT-4)', - com_endpoint_func_hover: 'Abilitare l\'uso dei Plugin come funzioni OpenAI', + com_endpoint_my_preset: 'Il mio preset', + com_endpoint_agent_model: 'Modello agente (Consigliato: GPT-3.5)', + com_endpoint_completion_model: 'Modello completamento (Consigliato: GPT-4)', + com_endpoint_func_hover: 'Abilita l\'uso di plugin come funzioni OpenAI', com_endpoint_skip_hover: - 'Abilita la possibilità di saltare la fase di completamento, che rivede la risposta finale e le fasi generate', - com_endpoint_config_key: 'Imposta Chiave API', - com_endpoint_config_key_for: 'Imposta Chiave API per', + 'Abilita il salto del passaggio di completamento, che rivede la risposta finale e i passaggi generati', + com_endpoint_config_key: 'Imposta chiave API', + com_endpoint_config_placeholder: 'Imposta la tua Chiave nel menu Header per chattare.', + com_endpoint_config_key_for: 'Imposta chiave API per', com_endpoint_config_key_name: 'Chiave', com_endpoint_config_value: 'Inserisci valore per', - com_endpoint_config_key_name_placeholder: 'Prima imposta una chiave API', + com_endpoint_config_key_name_placeholder: 'Imposta prima la chiave API', com_endpoint_config_key_encryption: 'La tua chiave verrà crittografata ed eliminata al', - com_endpoint_config_key_expiry: 'il tempo di scadenza', - com_endpoint_config_key_import_json_key: 'Importa la chiave JSON dell\'account di servizio', + com_endpoint_config_key_expiry: 'tempo di scadenza', + com_endpoint_config_click_here: 'Clicca qui', + com_endpoint_config_google_service_key: 'Chiave account servizio Google', + com_endpoint_config_google_cloud_platform: '(da Google Cloud Platform)', + com_endpoint_config_google_api_key: 'Chiave API Google', + com_endpoint_config_google_gemini_api: '(API Gemini)', + com_endpoint_config_google_api_info: + 'Per ottenere la tua chiave API Linguaggio Generativo (per Gemini),', + com_endpoint_config_key_import_json_key: 'Importa chiave JSON account di servizio.', com_endpoint_config_key_import_json_key_success: - 'Chiave JSON dell\'account di servizio importata correttamente', + 'Chiave JSON account di servizio importata con successo', com_endpoint_config_key_import_json_key_invalid: - 'Chiave JSON del Service Account non valida, hai importato il file corretto?', - com_endpoint_config_key_get_edge_key: 'Per ottenere il token di accesso per Bing, accedi a', + 'Chiave JSON account di servizio non valida, hai importato il file corretto?', + com_endpoint_config_key_get_edge_key: 'Per ottenere il tuo token di accesso per Bing, accedi a', com_endpoint_config_key_get_edge_key_dev_tool: - 'Usa gli strumenti di sviluppo o un\'estensione mentre sei loggato nel sito per copiare il contenuto del cookie _U. Se ciò fallisce, segui queste', - com_endpoint_config_key_edge_instructions: 'Istruzioni', - com_endpoint_config_key_edge_full_key_string: 'per fornire la stringa di cookie complete.', + 'Usa gli strumenti di sviluppo o un\'estensione mentre sei loggato nel sito per copiare il contenuto del cookie _U. Se questo non funziona, segui queste', + com_endpoint_config_key_edge_instructions: 'istruzioni', + com_endpoint_config_key_edge_full_key_string: 'per fornire le stringhe complete dei cookie.', com_endpoint_config_key_chatgpt: - 'Per ottenere il tuo token di accesso per ChatGPT \'Versione gratuita\', accedi a', + 'Per ottenere il tuo token di accesso Per ChatGPT "Versione gratuita", accedi a', com_endpoint_config_key_chatgpt_then_visit: 'poi visita', com_endpoint_config_key_chatgpt_copy_token: 'Copia token di accesso.', com_endpoint_config_key_google_need_to: 'Devi', com_endpoint_config_key_google_vertex_ai: 'Abilitare Vertex AI', com_endpoint_config_key_google_vertex_api: 'API su Google Cloud, poi', - com_endpoint_config_key_google_service_account: 'Crea un account di servizio', + com_endpoint_config_key_google_service_account: 'Creare un account di servizio', com_endpoint_config_key_google_vertex_api_role: - 'Assicurati di fare clic su \'Crea e continua\' per dare almeno il ruolo \'Utente Vertex AI\'. Infine, crea una chiave JSON da importare qui.', + 'Assicurati di fare clic su "Crea e continua" per dare almeno il ruolo "Vertex AI User". Infine, crea una chiave JSON da importare qui.', com_nav_welcome_message: 'Come posso aiutarti oggi?', - com_nav_auto_scroll: 'Scorrimento automatico', + com_nav_auto_scroll: 'Scorri automaticamente al Più recente all\'apertura', + com_nav_profile_picture: 'Immagine del profilo', + com_nav_change_picture: 'Cambia immagine', com_nav_plugin_store: 'Negozio dei plugin', com_nav_plugin_search: 'Cerca plugin', com_nav_plugin_auth_error: - 'Si è verificato un errore durante il tentativo di autenticare questo plugin. Per favore, riprova.', - com_nav_export_filename: 'Nome del file', + 'Si è verificato un errore durante il tentativo di autenticare questo plugin. Per favore riprova.', + com_nav_export_filename: 'Nome file', com_nav_export_filename_placeholder: 'Imposta il nome del file', com_nav_export_type: 'Tipo', - com_nav_export_include_endpoint_options: 'Includi opzioni dell\'endpoint', + com_nav_export_include_endpoint_options: 'Includi opzioni endpoint', com_nav_enabled: 'Abilitato', com_nav_not_supported: 'Non supportato', - com_nav_export_all_message_branches: 'Esporta tutti i rami dei messaggi', + com_nav_export_all_message_branches: 'Esporta tutti i rami di messaggio', com_nav_export_recursive_or_sequential: 'Ricorsivo o sequenziale?', com_nav_export_recursive: 'Ricorsivo', com_nav_export_conversation: 'Esporta conversazione', @@ -254,20 +286,19 @@ export default { com_nav_theme_system: 'Sistema', com_nav_theme_dark: 'Scuro', com_nav_theme_light: 'Chiaro', - com_nav_clear: 'Cancella', - com_nav_clear_all_chats: 'Cancella tutte le chat', - com_nav_confirm_clear: 'Conferma la cancellazione', - com_nav_close_sidebar: 'Chiudi la barra laterale', - com_nav_open_sidebar: 'Apri la barra laterale', + com_nav_clear_all_chats: 'Elimina tutte le chat', + com_nav_confirm_clear: 'Conferma eliminazione', + com_nav_close_sidebar: 'Chiudi sidebar', + com_nav_open_sidebar: 'Apri sidebar', com_nav_send_message: 'Invia messaggio', - com_nav_log_out: 'Esci', + com_nav_log_out: 'Disconnettiti', com_nav_user: 'UTENTE', - com_nav_clear_conversation: 'Cancella conversazioni', + com_nav_clear_conversation: 'Elimina conversazioni', com_nav_clear_conversation_confirm_message: - 'Sei sicuro di voler cancellare tutte le conversazioni? Questa azione è irreversibile.', - com_nav_help_faq: 'Aiuto & FAQ', + 'Sei sicuro di voler eliminare tutte le conversazioni? Questa operazione è irreversibile.', + com_nav_help_faq: 'Aiuto e FAQ', com_nav_settings: 'Impostazioni', com_nav_search_placeholder: 'Cerca messaggi', com_nav_setting_general: 'Generale', - com_nav_setting_data: 'Controllo dei dati', + com_nav_setting_data: 'Controlli dati', }; diff --git a/client/src/localization/languages/Ru.tsx b/client/src/localization/languages/Ru.tsx index f852f440504..58309d0764f 100644 --- a/client/src/localization/languages/Ru.tsx +++ b/client/src/localization/languages/Ru.tsx @@ -3,10 +3,10 @@ export default { com_ui_examples: 'Примеры', com_ui_new_chat: 'Создать чат', - com_ui_example_quantum_computing: 'Объясните квантовые вычисления простыми словами', + com_ui_example_quantum_computing: 'Объясни квантовые вычисления простыми словами', com_ui_example_10_year_old_b_day: - 'У вас есть креативные идеи для дня рождения 10-летнего ребенка?', - com_ui_example_http_in_js: 'Как сделать HTTP-запрос в JavaScript?', + 'У тебя есть креативные идеи для дня рождения 10-летнего ребенка?', + com_ui_example_http_in_js: 'Как мне сделать HTTP-запрос в JavaScript?', com_ui_capabilities: 'Возможности', com_ui_capability_remember: 'Запоминает, что пользователь говорил ранее в разговоре', com_ui_capability_correction: 'Позволяет пользователю вносить корректировки после ответа', @@ -18,37 +18,56 @@ export default { com_ui_limitation_limited_2021: 'Ограниченные знания о мире и событиях после 2021 года', com_ui_input: 'Ввод', com_ui_close: 'Закрыть', - com_ui_clear: 'Очистить', - com_ui_revoke: 'Отозвать', - com_ui_revoke_info: 'Отозвать все предоставленные пользователем учетные данные', com_ui_model: 'Модель', com_ui_select_model: 'Выберите модель', - com_ui_use_prompt: 'Использовать подсказку', + com_ui_use_prompt: 'Использовать промт', com_ui_prev: 'Предыдущий', com_ui_next: 'Следующий', + com_ui_stop: 'Остановить генерацию', + com_ui_prompt_templates: 'Шаблоны промтов', + com_ui_hide_prompt_templates: 'Скрыть шаблоны промтов', + com_ui_showing: 'Показано', + com_ui_of: 'из', + com_ui_entries: 'записей', + com_ui_pay_per_call: 'Все AI-разговоры в одном месте. Оплачивайте за вызовы, а не за месяц', + com_ui_new_footer: 'Все AI-разговоры в одном месте.', + com_ui_enter: 'Ввести', + com_ui_submit: 'Отправить', + com_ui_upload_success: 'Файл успешно загружен', + com_ui_upload_invalid: 'Недопустимый файл для загрузки', com_ui_cancel: 'Отмена', com_ui_save: 'Сохранить', - com_ui_submit: 'Отправить', + com_ui_copy_to_clipboard: 'Копировать в буфер обмена', + com_ui_copied_to_clipboard: 'Скопировано в буфер обмена', + com_ui_regenerate: 'Повторная генерация', + com_ui_continue: 'Продолжить', + com_ui_edit: 'Редактировать', + com_ui_success: 'Успешно', + com_ui_all: 'все', + com_ui_clear: 'Удалить', + com_ui_revoke: 'Отозвать', + com_ui_revoke_info: 'Отозвать все предоставленные пользователем учетные данные', com_ui_confirm_action: 'Подтвердить действие', + com_ui_chats: 'чаты', com_ui_delete: 'Удалить', + com_ui_preview: 'Предпросмотр', + com_ui_upload: 'Загрузить', + com_ui_connect: 'Подключить', com_ui_delete_conversation: 'Удалить чат?', com_ui_delete_conversation_confirm: 'Будет удален следующий чат: ', - com_ui_regenerate: 'Повторная генерация', - com_ui_stop: 'Остановить генерацию', - com_ui_continue: 'Продолжить', - com_ui_prompt_templates: 'Шаблоны подсказок', - com_ui_hide_prompt_templates: 'Скрыть шаблоны подсказок', - com_ui_showing: 'Показано', - com_ui_of: 'из', - com_ui_all: 'все', - com_ui_entries: 'записей', - com_ui_pay_per_call: 'Все AI-разговоры в одном месте. Оплачивайте за звонки, а не за месяц', com_auth_error_login: 'Не удалось войти с предоставленной информацией. Пожалуйста, проверьте ваши учетные данные и попробуйте снова.', - com_auth_no_account: 'Еще нет аккаунта?', + com_auth_error_login_rl: + 'Слишком много попыток входа в систему за короткий промежуток времени. Пожалуйста, повторите попытку позже.', + com_auth_error_login_ban: + 'Ваша учетная запись была временно заблокирована в связи с нарушениями нашего сервиса.', + com_auth_error_login_server: + 'Произошла внутренняя ошибка сервера. Пожалуйста, подождите несколько минут и повторите попытку.', + com_auth_no_account: 'Еще не зарегистрированы?', com_auth_sign_up: 'Зарегистрироваться', com_auth_sign_in: 'Войти', com_auth_google_login: 'Войти с помощью Google', + com_auth_facebook_login: 'Войти с помощью Facebook', com_auth_github_login: 'Войти с помощью Github', com_auth_discord_login: 'Войти с помощью Discord', com_auth_email: 'Email', @@ -74,14 +93,14 @@ export default { com_auth_name_max_length: 'Имя должно быть короче 80 символов', com_auth_username: 'Имя пользователя (необязательно)', com_auth_username_required: 'Имя пользователя обязательно', - com_auth_username_min_length: 'Имя пользователя должно содержать не менее 3 символов', + com_auth_username_min_length: 'Имя пользователя должно содержать не менее 2 символов', com_auth_username_max_length: 'Имя пользователя должно быть не более 20 символов', - com_auth_already_have_account: 'Уже есть аккаунт?', + com_auth_already_have_account: 'Уже зарегистрированы?', com_auth_login: 'Войти', com_auth_reset_password: 'Сбросить пароль', com_auth_click: 'Нажмите', com_auth_here: 'ЗДЕСЬ', - com_auth_to_reset: 'ваш пароль.', + com_auth_to_reset_your_password: 'чтобы сбросить ваш пароль.', com_auth_reset_password_link_sent: 'Письмо отправлено', com_auth_reset_password_email_sent: 'На вашу почту было отправлено письмо с дальнейшими инструкциями по сбросу пароля.', @@ -95,14 +114,16 @@ export default { com_auth_submit_registration: 'Отправить регистрацию', com_auth_welcome_back: 'Добро пожаловать', com_endpoint_open_menu: 'Открыть меню', - com_endpoint_bing_enable_sydney: 'Включить Сидней', - com_endpoint_bing_to_enable_sydney: 'Чтобы включить Сидней', + com_endpoint_bing_enable_sydney: 'Включить Sydney', + com_endpoint_bing_to_enable_sydney: 'Чтобы включить Sydney', com_endpoint_bing_jailbreak: 'Jailbreak', com_endpoint_bing_context_placeholder: 'Bing может использовать до 7 тысяч токенов для "контекста", на который он может ссылаться в разговоре. Точный предел неизвестен, но превышение 7 тысяч токенов может вызвать ошибки.', com_endpoint_bing_system_message_placeholder: - 'ПРЕДУПРЕЖДЕНИЕ: Неправильное использование этой функции может привести к БАНу на использование Bing! Нажмите на "Системное сообщение" для получения полных инструкций и значения по умолчанию, которое является предустановкой "Сидней", считающейся безопасной.', + 'ПРЕДУПРЕЖДЕНИЕ: Неправильное использование этой функции может привести к БАНУ на использование Bing! Нажмите на "Системное сообщение" для получения полных инструкций и значения по умолчанию, которое является предустановкой "Sydney", считающейся безопасной.', com_endpoint_system_message: 'Системное сообщение', + com_endpoint_message: 'Сообщение', + com_endpoint_message_not_appendable: 'Отредактируйте свое сообщение или перегенерируйте.', com_endpoint_default_blank: 'по умолчанию: пусто', com_endpoint_default_false: 'по умолчанию: false', com_endpoint_default_creative: 'по умолчанию: креативный', @@ -115,21 +136,21 @@ export default { com_endpoint_google_temp: 'Более высокие значения = более случайные результаты, более низкие значения = более фокусированные и детерминированные результаты. Мы рекомендуем изменять это или Top P, но не оба значения одновременно.', com_endpoint_google_topp: - 'Top P изменяет то, как модель выбирает токены для вывода. Токены выбираются из наиболее вероятных (см. параметр topK) до наименее вероятных, пока сумма их вероятностей не достигнет значения top-p.', + 'Top-p изменяет то, как модель выбирает токены для вывода. Токены выбираются из наиболее вероятных K (см. параметр topK) до наименее вероятных, пока сумма их вероятностей не достигнет значения top-p.', com_endpoint_google_topk: - 'Top K изменяет то, как модель выбирает токены для вывода. Top K равное 1 означает, что выбирается наиболее вероятный токен из всего словаря модели (так называемое жадное декодирование), а Top K равное 3 означает, что следующий токен выбирается из трех наиболее вероятных токенов (с использованием температуры).', + 'Top-k изменяет то, как модель выбирает токены для вывода. Top-k равное 1 означает, что выбирается наиболее вероятный токен из всего словаря модели (так называемое жадное декодирование), а Top-k равное 3 означает, что следующий токен выбирается из трех наиболее вероятных токенов (с использованием температуры).', com_endpoint_google_maxoutputtokens: - 'Максимальное количество токенов, которые могут быть сгенерированы в ответе. Укажите меньшее значение для более коротких ответов и большее значение для более длинных ответов.', - com_endpoint_google_custom_name_placeholder: 'Установите пользовательское имя для Google', - com_endpoint_google_prompt_prefix_placeholder: - 'Установите пользовательские инструкции или контекст. Игнорируется, если пусто.', - com_endpoint_custom_name: 'Пользовательское имя', - com_endpoint_prompt_prefix: 'Префикс подсказки', + ' Максимальное количество токенов, которые могут быть сгенерированы в ответе. Укажите меньшее значение для более коротких ответов и большее значение для более длинных ответов.', + com_endpoint_google_custom_name_placeholder: 'Задайте кастомное имя для Google', + com_endpoint_prompt_prefix_placeholder: + 'Задайте пользовательские инструкции или контекст. Игнорируется, если пусто.', + com_endpoint_custom_name: 'Кастомное имя', + com_endpoint_prompt_prefix: 'Префикс промта', com_endpoint_temperature: 'Температура', com_endpoint_default: 'по умолчанию', com_endpoint_top_p: 'Top P', com_endpoint_top_k: 'Top K', - com_endpoint_max_output_tokens: 'Максимальное количество токенов в выводе', + com_endpoint_max_output_tokens: 'Максимальное количество выводимых токенов', com_endpoint_openai_temp: 'Более высокие значения = более случайные результаты, более низкие значения = более фокусированные и детерминированные результаты. Мы рекомендуем изменять это или Top P, но не оба значения одновременно.', com_endpoint_openai_max: @@ -140,9 +161,9 @@ export default { 'Число от -2.0 до 2.0. Положительные значения штрафуют новые токены на основе их частоты в тексте до сих пор, уменьшая вероятность модели повторить ту же строку дословно.', com_endpoint_openai_pres: 'Число от -2.0 до 2.0. Положительные значения штрафуют новые токены на основе того, появляются ли они в тексте до сих пор, увеличивая вероятность модели говорить о новых темах.', - com_endpoint_openai_custom_name_placeholder: 'Установите пользовательское имя для ChatGPT', + com_endpoint_openai_custom_name_placeholder: 'Задайте кастомное имя для ChatGPT', com_endpoint_openai_prompt_prefix_placeholder: - 'Установите пользовательские инструкции для включения в системное сообщение. По умолчанию: нет', + 'Задайте кастомные промты для включения в системное сообщение. По умолчанию: нет', com_endpoint_anthropic_temp: 'Диапазон значений от 0 до 1. Используйте значение temp ближе к 0 для аналитических / множественного выбора и ближе к 1 для креативных и генеративных задач. Мы рекомендуем изменять это или Top P, но не оба значения одновременно.', com_endpoint_anthropic_topp: @@ -151,18 +172,40 @@ export default { 'Top K изменяет то, как модель выбирает токены для вывода. Top K равное 1 означает, что выбирается наиболее вероятный токен из всего словаря модели (так называемое жадное декодирование), а Top K равное 3 означает, что следующий токен выбирается из трех наиболее вероятных токенов (с использованием температуры).', com_endpoint_anthropic_maxoutputtokens: 'Максимальное количество токенов, которые могут быть сгенерированы в ответе. Укажите меньшее значение для более коротких ответов и большее значение для более длинных ответов.', + com_endpoint_anthropic_custom_name_placeholder: 'Задайте кастомное имя для Anthropic', com_endpoint_frequency_penalty: 'Штраф за частоту', com_endpoint_presence_penalty: 'Штраф за присутствие', com_endpoint_plug_use_functions: 'Использовать функции', com_endpoint_plug_skip_completion: 'Пропустить завершение', - com_endpoint_disabled_with_tools: 'отключено с инструментами', - com_endpoint_disabled_with_tools_placeholder: 'Отключено с выбранными инструментами', + com_endpoint_disabled_with_tools: 'отключено с плагинами', + com_endpoint_disabled_with_tools_placeholder: 'Отключено при включённых плагинах', com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: - 'Установите пользовательские инструкции для включения в системное сообщение. По умолчанию: нет', - com_endpoint_set_custom_name: 'Установите пользовательское имя, чтобы найти эту предустановку', - com_endpoint_preset_name: 'Имя предустановки', + 'Задайте кастомные инструкции для включения в системное сообщение. По умолчанию: нет', + com_endpoint_import: 'Импортировать', + com_endpoint_set_custom_name: + 'Задайте кастомное имя на случай, если вы сможете найти эту предустановку :)', + com_endpoint_preset_delete_confirm: 'Вы уверены, что хотите удалить этот пресет?', + com_endpoint_preset_clear_all_confirm: 'Вы уверены, что хотите удалить все ваши пресеты?', + com_endpoint_preset_import: 'Пресет Импортирован!', + com_endpoint_preset_import_error: + 'Произошла ошибка при импорте вашего пресета. Пожалуйста, попробуйте еще раз.', + com_endpoint_preset_save_error: + 'Произошла ошибка при сохранении вашего пресета. Пожалуйста, попробуйте еще раз.', + com_endpoint_preset_delete_error: + 'Произошла ошибка при удалении вашего пресета. Пожалуйста, попробуйте еще раз.', + com_endpoint_preset_default_removed: 'больше не пресет по умолчанию.', + com_endpoint_preset_default_item: 'По умолчанию:', + com_endpoint_preset_default_none: 'Нет активных пресетов По умолчанию.', + com_endpoint_preset_title: 'Пресет', + com_endpoint_preset_saved: 'Сохранено!', + com_endpoint_preset_default: 'теперь пресет По умолчаанию.', + com_endpoint_preset: 'пресет', + com_endpoint_presets: 'пресеты', + com_endpoint_preset_selected: 'Пресет Активирован!', + com_endpoint_preset_selected_title: 'Активирован!', + com_endpoint_preset_name: 'Имя пресета', com_endpoint_new_topic: 'Новая тема', - com_endpoint: 'Конечная точка', + com_endpoint: 'Эндпоинт', com_endpoint_hide: 'Скрыть', com_endpoint_show: 'Показать', com_endpoint_examples: 'Примеры', @@ -171,41 +214,68 @@ export default { com_endpoint_show_what_settings: 'Показать настройки {0}', com_endpoint_save: 'Сохранить', com_endpoint_export: 'Экспортировать', - com_endpoint_import: 'Импортировать', - com_endpoint_save_as_preset: 'Сохранить как предустановку', - com_endpoint_not_implemented: 'Не реализовано', - com_endpoint_no_presets: 'Пока нет предустановок', - com_endpoint_not_available: 'Нет доступных конечных точек', - com_endpoint_clear_all: 'Очистить все', - com_endpoint_view_options: 'Просмотреть параметры', - com_endpoint_save_convo_as_preset: 'Сохранить разговор как предустановку', + com_endpoint_save_as_preset: 'Сохранить как Пресет', com_endpoint_presets_clear_warning: - 'Вы уверены, что хотите очистить все предустановки? Эти действия необратимы, и восстановление невозможно.', - com_endpoint_presets: 'предустановки', - com_endpoint_my_preset: 'Моя предустановка', - com_endpoint_config_key: 'Установить ключ API', - com_endpoint_config_key_for: 'Установить ключ API для', - com_endpoint_config_key_name: 'Ключ', - com_endpoint_config_value: 'Введите значение для', - com_endpoint_config_key_name_placeholder: 'Установите сначала ключ API', - com_endpoint_config_key_import_json_key: 'Импортировать JSON-ключ учетной записи.', + 'Вы уверены, что хотите удалить все пресеты? Это действие необратимо и восстановление невозможно.', + com_endpoint_not_implemented: 'Не реализовано', + com_endpoint_no_presets: 'Пока нет пресетов, используйте кнопку настроек чтобы создать его', + com_endpoint_not_available: 'Нет доступных эндпоинтов', + com_endpoint_view_options: 'Просмотреть Настройки', + com_endpoint_save_convo_as_preset: 'Сохранить текущий разговор как Пресет', + com_endpoint_my_preset: 'Мой Пресет', com_endpoint_agent_model: 'Модель агента (Рекомендуется: GPT-3.5)', com_endpoint_completion_model: 'Модель завершения (Рекомендуется: GPT-4)', - com_endpoint_func_hover: 'Включить использование плагинов в качестве функций OpenAI', + com_endpoint_func_hover: 'Включить использование плагинов как функции OpenAI', com_endpoint_skip_hover: 'Пропустить этап завершения, который проверяет окончательный ответ и сгенерированные шаги', com_endpoint_config_token: 'Токен конфигурации', + com_endpoint_config_key: 'Указать ключ к API', + com_endpoint_config_placeholder: 'Укажите ваш ключ к API в меню сверху для начала разговора.', + com_endpoint_config_key_for: 'Установить ключ к API для', + com_endpoint_config_key_name: 'Ключ', + com_endpoint_config_value: 'Введите значение для', + com_endpoint_config_key_name_placeholder: 'Сначала укажите ключ к API', + com_endpoint_config_key_encryption: 'Ваш ключ зашифрован и будет удалён', + com_endpoint_config_key_expiry: 'срок действия', + com_endpoint_config_click_here: 'Нажми Здесь', + com_endpoint_config_google_service_key: 'Google Service Account Key', + com_endpoint_config_google_cloud_platform: '(из Google Cloud Platform)', + com_endpoint_config_google_api_key: 'Google API Key', + com_endpoint_config_google_gemini_api: '(Gemini API)', + com_endpoint_config_google_api_info: + 'Чтобы получить ключ к API Generative Language (для Gemini),', + com_endpoint_config_key_import_json_key: 'Импортировать Service Account JSON Key.', + com_endpoint_config_key_import_json_key_success: 'Успешно Импортирован Service Account JSON Key', + com_endpoint_config_key_import_json_key_invalid: + 'Некорректный Service Account JSON Key, Вы импортировали верный файл?', + com_endpoint_config_key_get_edge_key: 'Чтобы получить ваш токен доступа к Bing, войдите в', + com_endpoint_config_key_get_edge_key_dev_tool: + 'Пока вы на сайте, используйте dev tools или расширение чтобы скопировать содержимое куки _U. Если не получается, следуйте этим', + com_endpoint_config_key_edge_instructions: 'инструкциям', + com_endpoint_config_key_edge_full_key_string: 'чтобы получить все строки cookie.', + com_endpoint_config_key_chatgpt: + 'Чтобы получить токен доступа к "Бесплатной Версии" ChatGPT, войдите в', + com_endpoint_config_key_chatgpt_then_visit: 'затем посетите', + com_endpoint_config_key_chatgpt_copy_token: 'Скопируйте токен доступа.', + com_endpoint_config_key_google_need_to: 'Вам нужно', + com_endpoint_config_key_google_vertex_ai: 'Активировать Vertex AI', + com_endpoint_config_key_google_vertex_api: 'API в Google Cloud, после', + com_endpoint_config_key_google_service_account: 'Создать Service Account', + com_endpoint_config_key_google_vertex_api_role: + 'Убедитесь что нажали на \'Create and Continue\' чтобы получить как минимум \'Vertex AI User\'. Наконец, создайте JSON-ключ чтобы импортировать его сюда.', + com_nav_welcome_message: 'Чем я могу помочь вам сегодня?', + com_nav_auto_scroll: 'Автоматически проматывать к самым новым сообщениям при открытии', com_nav_plugin_store: 'Магазин плагинов', com_nav_plugin_search: 'Поиск плагинов', com_nav_plugin_auth_error: 'При попытке аутентификации этого плагина произошла ошибка. Пожалуйста, попробуйте еще раз.', com_nav_export_filename: 'Имя файла', - com_nav_export_filename_placeholder: 'Установите имя файла', + com_nav_export_filename_placeholder: 'Задайте имя файла', com_nav_export_type: 'Тип', - com_nav_export_include_endpoint_options: 'Включить параметры конечной точки', + com_nav_export_include_endpoint_options: 'Включить параметры эндпоинта', com_nav_enabled: 'Включено', com_nav_not_supported: 'Не поддерживается', - com_nav_export_all_message_branches: 'Экспортировать все ветви сообщений', + com_nav_export_all_message_branches: 'Экспортировать все ветки сообщений', com_nav_export_recursive_or_sequential: 'Рекурсивно или последовательно?', com_nav_export_recursive: 'Рекурсивно', com_nav_export_conversation: 'Экспортировать разговор', @@ -213,22 +283,25 @@ export default { com_nav_theme_system: 'Системная', com_nav_theme_dark: 'Темная', com_nav_theme_light: 'Светлая', - com_nav_language: 'Локализация (Альфа)', + com_nav_language: 'Локализация', + com_nav_setting_account: 'Аккаунт', + com_nav_profile_picture: 'Изображение профиля', + com_nav_change_picture: 'Изменить изображение', + com_nav_lang_auto: 'Автоопределение', com_nav_clear: 'Очистить', - com_nav_clear_all_chats: 'Очистить все чаты', - com_nav_confirm_clear: 'Подтвердить очистку', - com_nav_auto_scroll: 'Автоматическая прокрутка к новым сообщениям в режиме открытия', + com_nav_clear_all_chats: 'Удалить все чаты', + com_nav_confirm_clear: 'Подтвердить удаление', com_nav_close_sidebar: 'Закрыть боковую панель', com_nav_open_sidebar: 'Открыть боковую панель', com_nav_send_message: 'Отправить сообщение', com_nav_log_out: 'Выйти', com_nav_user: 'ПОЛЬЗОВАТЕЛЬ', - com_nav_clear_conversation: 'Очистить разговоры', + com_nav_clear_conversation: 'Удалить разговоры', com_nav_clear_conversation_confirm_message: - 'Вы уверены, что хотите очистить все разговоры? Это действие нельзя отменить.', - com_nav_help_faq: 'Помощь и часто задаваемые вопросы', - com_nav_setting_data: 'Управление данными', + 'Вы уверены, что хотите удалить все разговоры? Это действие нельзя отменить.', + com_nav_help_faq: 'Помощь и ЧаВо', com_nav_settings: 'Настройки', com_nav_search_placeholder: 'Поиск сообщений', com_nav_setting_general: 'Общие', + com_nav_setting_data: 'Управление данными', }; diff --git a/client/src/localization/languages/Zh.tsx b/client/src/localization/languages/Zh.tsx index 01ef650af9b..ffe8552a842 100644 --- a/client/src/localization/languages/Zh.tsx +++ b/client/src/localization/languages/Zh.tsx @@ -246,5 +246,4 @@ export default { com_nav_search_placeholder: '搜索对话及对话内容', com_nav_setting_general: '通用', com_nav_setting_data: '数据管理', - com_nav_language: '语言', }; diff --git a/client/src/mobile.css b/client/src/mobile.css index 17cc9ef5d9f..1643d305f25 100644 --- a/client/src/mobile.css +++ b/client/src/mobile.css @@ -121,3 +121,12 @@ .hide-scrollbar::-webkit-scrollbar { display: none; /* For WebKit browsers */ } + +.gemini-gradient { + /* Adjust the colors and positioning as necessary to match the image */ + background-image: radial-gradient(circle at center, #0000ff, #87cefa, #ffffff); + /* More styling for demonstration purposes */ + border-radius: 50%; + height: 100px; + width: 100px; +} \ No newline at end of file diff --git a/client/src/routes/ChatRoute.tsx b/client/src/routes/ChatRoute.tsx index 95be81d7959..8bbb371e43e 100644 --- a/client/src/routes/ChatRoute.tsx +++ b/client/src/routes/ChatRoute.tsx @@ -1,7 +1,11 @@ import { useRecoilValue } from 'recoil'; import { useEffect, useRef } from 'react'; import { useParams } from 'react-router-dom'; -import { useGetConvoIdQuery, useGetModelsQuery } from 'librechat-data-provider/react-query'; +import { + useGetConvoIdQuery, + useGetModelsQuery, + useGetEndpointsQuery, +} from 'librechat-data-provider/react-query'; import { useNewConvo, useConfigOverride } from '~/hooks'; import ChatView from '~/components/Chat/ChatView'; import useAuthRedirect from './useAuthRedirect'; @@ -21,12 +25,23 @@ export default function ChatRoute() { const initialConvoQuery = useGetConvoIdQuery(conversationId ?? '', { enabled: isAuthenticated && conversationId !== 'new', }); + const endpointsQuery = useGetEndpointsQuery({ enabled: isAuthenticated && modelsQueryEnabled }); useEffect(() => { - if (conversationId === 'new' && modelsQuery.data && !hasSetConversation.current) { + if ( + conversationId === 'new' && + endpointsQuery.data && + modelsQuery.data && + !hasSetConversation.current + ) { newConversation({ modelsData: modelsQuery.data }); hasSetConversation.current = true; - } else if (initialConvoQuery.data && modelsQuery.data && !hasSetConversation.current) { + } else if ( + initialConvoQuery.data && + endpointsQuery.data && + modelsQuery.data && + !hasSetConversation.current + ) { newConversation({ template: initialConvoQuery.data, modelsData: modelsQuery.data, @@ -34,7 +49,7 @@ export default function ChatRoute() { hasSetConversation.current = true; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [initialConvoQuery.data, modelsQuery.data]); + }, [initialConvoQuery.data, modelsQuery.data, endpointsQuery.data]); if (!isAuthenticated) { return null; diff --git a/client/src/store/models.ts b/client/src/store/models.ts index c05dd0c55b5..53c1b5e1b25 100644 --- a/client/src/store/models.ts +++ b/client/src/store/models.ts @@ -1,10 +1,13 @@ import { atom } from 'recoil'; -import { TModelsConfig, EModelEndpoint, openAIModels } from 'librechat-data-provider'; +import { EModelEndpoint, defaultModels } from 'librechat-data-provider'; +import type { TModelsConfig } from 'librechat-data-provider'; const fitlerAssistantModels = (str: string) => { return /gpt-4|gpt-3\\.5/i.test(str) && !/vision|instruct/i.test(str); }; +const openAIModels = defaultModels[EModelEndpoint.openAI]; + const modelsConfig = atom({ key: 'models', default: { @@ -14,14 +17,8 @@ const modelsConfig = atom({ [EModelEndpoint.azureOpenAI]: openAIModels, [EModelEndpoint.bingAI]: ['BingAI', 'Sydney'], [EModelEndpoint.chatGPTBrowser]: ['text-davinci-002-render-sha'], - [EModelEndpoint.google]: ['chat-bison', 'text-bison', 'codechat-bison'], - [EModelEndpoint.anthropic]: [ - 'claude-1', - 'claude-1-100k', - 'claude-instant-1', - 'claude-instant-1-100k', - 'claude-2', - ], + [EModelEndpoint.google]: defaultModels[EModelEndpoint.google], + [EModelEndpoint.anthropic]: defaultModels[EModelEndpoint.anthropic], }, }); diff --git a/client/src/store/user.ts b/client/src/store/user.ts index 04864d34263..d86bdc23730 100644 --- a/client/src/store/user.ts +++ b/client/src/store/user.ts @@ -1,9 +1,9 @@ import { atom } from 'recoil'; -import { TPlugin } from 'librechat-data-provider'; +import type { TUser, TPlugin } from 'librechat-data-provider'; -const user = atom({ +const user = atom({ key: 'user', - default: null, + default: undefined, }); const availableTools = atom({ diff --git a/client/src/utils/json.ts b/client/src/utils/json.ts index f601b0df950..fb89377ed67 100644 --- a/client/src/utils/json.ts +++ b/client/src/utils/json.ts @@ -1,3 +1,12 @@ +export function isJson(str: string) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; +} + export function formatJSON(json: string) { try { return JSON.stringify(JSON.parse(json), null, 2); diff --git a/client/vite.config.ts b/client/vite.config.ts index 90d55faec17..b5030bd55d4 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -56,7 +56,7 @@ export default defineConfig({ resolve: { alias: { '~': path.join(__dirname, 'src/'), - $fonts: resolve('/fonts'), + $fonts: resolve('public/fonts'), }, }, }); diff --git a/config/add-balance.js b/config/add-balance.js index ed2e6128983..75b9b4cda9a 100644 --- a/config/add-balance.js +++ b/config/add-balance.js @@ -1,34 +1,11 @@ -const connectDb = require('../api/lib/db/connectDb'); -const { askQuestion, silentExit } = require('./helpers'); -const User = require('../api/models/User'); -const Transaction = require('../api/models/Transaction'); +const path = require('path'); +require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const { askQuestion, silentExit, connectWithTimeout } = require('./helpers'); +const Transaction = require('~/models/Transaction'); +const User = require('~/models/User'); (async () => { - /** - * Connect to the database - * - If it takes a while, we'll warn the user - */ - // Warn the user if this is taking a while - let timeout = setTimeout(() => { - console.orange( - 'This is taking a while... You may need to check your connection if this fails.', - ); - timeout = setTimeout(() => { - console.orange('Still going... Might as well assume the connection failed...'); - timeout = setTimeout(() => { - console.orange('Error incoming in 3... 2... 1...'); - }, 13000); - }, 10000); - }, 5000); - // Attempt to connect to the database - try { - console.orange('Warming up the engines...'); - await connectDb(); - clearTimeout(timeout); - } catch (e) { - console.error(e); - silentExit(1); - } + await connectWithTimeout(); /** * Show the welcome / help menu diff --git a/config/ban-user.js b/config/ban-user.js index 45e628aeca4..a2c01a1cefa 100644 --- a/config/ban-user.js +++ b/config/ban-user.js @@ -1,34 +1,11 @@ -const connectDb = require('../api/lib/db/connectDb'); -const { askQuestion, silentExit } = require('./helpers'); -const banViolation = require('../api/cache/banViolation'); -const User = require('../api/models/User'); +const path = require('path'); +require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const { askQuestion, silentExit, connectWithTimeout } = require('./helpers'); +const banViolation = require('~/cache/banViolation'); +const User = require('~/models/User'); (async () => { - /** - * Connect to the database - * - If it takes a while, we'll warn the user - */ - // Warn the user if this is taking a while - let timeout = setTimeout(() => { - console.orange( - 'This is taking a while... You may need to check your connection if this fails.', - ); - timeout = setTimeout(() => { - console.orange('Still going... Might as well assume the connection failed...'); - timeout = setTimeout(() => { - console.orange('Error incoming in 3... 2... 1...'); - }, 13000); - }, 10000); - }, 5000); - // Attempt to connect to the database - try { - console.orange('Warming up the engines...'); - await connectDb(); - clearTimeout(timeout); - } catch (e) { - console.error(e); - silentExit(1); - } + await connectWithTimeout(); console.purple('---------------------'); console.purple('Ban a user account!'); diff --git a/config/create-user.js b/config/create-user.js index c854b137a96..cd0c7132559 100644 --- a/config/create-user.js +++ b/config/create-user.js @@ -1,34 +1,11 @@ -const connectDb = require('../api/lib/db/connectDb'); -const { registerUser } = require('../api/server/services/AuthService'); -const { askQuestion, silentExit } = require('./helpers'); -const User = require('../api/models/User'); +const path = require('path'); +require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const { registerUser } = require('~/server/services/AuthService'); +const { askQuestion, silentExit, connectWithTimeout } = require('./helpers'); +const User = require('~/models/User'); (async () => { - /** - * Connect to the database - * - If it takes a while, we'll warn the user - */ - // Warn the user if this is taking a while - let timeout = setTimeout(() => { - console.orange( - 'This is taking a while... You may need to check your connection if this fails.', - ); - timeout = setTimeout(() => { - console.orange('Still going... Might as well assume the connection failed...'); - timeout = setTimeout(() => { - console.orange('Error incoming in 3... 2... 1...'); - }, 13000); - }, 10000); - }, 5000); - // Attempt to connect to the database - try { - console.orange('Warming up the engines...'); - await connectDb(); - clearTimeout(timeout); - } catch (e) { - console.error(e); - silentExit(1); - } + await connectWithTimeout(); /** * Show the welcome / help menu diff --git a/config/delete-user.js b/config/delete-user.js new file mode 100644 index 00000000000..fe7efe057d3 --- /dev/null +++ b/config/delete-user.js @@ -0,0 +1,48 @@ +const path = require('path'); +require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const { connectWithTimeout, askQuestion, silentExit } = require('./helpers'); +const User = require('~/models/User'); + +(async () => { + await connectWithTimeout(); + + /** + * Show the welcome / help menu + */ + console.purple('---------------'); + console.purple('Deleting a user'); + console.purple('---------------'); + + let email = ''; + if (process.argv.length >= 3) { + email = process.argv[2]; + } else { + email = await askQuestion('Email:'); + } + let user = await User.findOne({ email: email }); + if (user !== null) { + if ((await askQuestion(`Delete user ${user}?`)) === 'y') { + user = await User.findOneAndDelete({ _id: user._id }); + if (user !== null) { + console.yellow(`Deleted user ${user}`); + } else { + console.yellow(`Couldn't delete user with email ${email}`); + } + } + } else { + console.yellow(`Didn't find user with email ${email}`); + } + + silentExit(0); +})(); + +process.on('uncaughtException', (err) => { + if (!err.message.includes('fetch failed')) { + console.error('There was an uncaught error:'); + console.error(err); + } + + if (!err.message.includes('fetch failed')) { + process.exit(1); + } +}); diff --git a/config/helpers.js b/config/helpers.js index 6bc6ed0ef2a..2b634612d4a 100644 --- a/config/helpers.js +++ b/config/helpers.js @@ -6,6 +6,8 @@ const fs = require('fs'); const path = require('path'); const readline = require('readline'); const { execSync } = require('child_process'); +require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const connectDb = require('~/lib/db/connectDb'); const askQuestion = (query) => { const rl = readline.createInterface({ @@ -43,6 +45,33 @@ const silentExit = (code = 0) => { process.exit(code); }; +async function connectWithTimeout() { + /** + * Connect to the database + * - If it takes a while, we'll warn the user + */ + let timeout = setTimeout(() => { + console.orange( + 'This is taking a while... You may need to check your connection if this fails.', + ); + timeout = setTimeout(() => { + console.orange('Still going... Might as well assume the connection failed...'); + timeout = setTimeout(() => { + console.orange('Error incoming in 3... 2... 1...'); + }, 13000); + }, 10000); + }, 5000); + // Attempt to connect to the database + try { + console.orange('Warming up the engines...'); + await connectDb(); + clearTimeout(timeout); + } catch (e) { + console.error(e); + silentExit(1); + } +} + // Set the console colours console.orange = (msg) => console.log('\x1b[33m%s\x1b[0m', msg); console.green = (msg) => console.log('\x1b[32m%s\x1b[0m', msg); @@ -58,5 +87,6 @@ module.exports = { askQuestion, silentExit, isDockerRunning, + connectWithTimeout, deleteNodeModules, }; diff --git a/config/list-balances.js b/config/list-balances.js new file mode 100644 index 00000000000..670aba6e5d9 --- /dev/null +++ b/config/list-balances.js @@ -0,0 +1,39 @@ +const path = require('path'); +require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const { connectWithTimeout, silentExit } = require('./helpers'); +const Balance = require('~/models/Balance'); +const User = require('~/models/User'); + +(async () => { + await connectWithTimeout(); + + /** + * Show the welcome / help menu + */ + console.purple('-----------------------------'); + console.purple('Show the balance of all users'); + console.purple('-----------------------------'); + + let users = await User.find({}); + for (const user of users) { + let balance = await Balance.findOne({ user: user._id }); + if (balance !== null) { + console.green(`User ${user.name} has a balance of ${balance.tokenCredits}`); + } else { + console.yellow(`User ${user.name} has no balance`); + } + } + + silentExit(0); +})(); + +process.on('uncaughtException', (err) => { + if (!err.message.includes('fetch failed')) { + console.error('There was an uncaught error:'); + console.error(err); + } + + if (!err.message.includes('fetch failed')) { + process.exit(1); + } +}); diff --git a/config/loader.js b/config/loader.js index c0e3413528b..a29ca19a0a4 100644 --- a/config/loader.js +++ b/config/loader.js @@ -1,7 +1,7 @@ -const dotenv = require('dotenv'); -const path = require('path'); const fs = require('fs'); +const path = require('path'); const crypto = require('crypto'); +const dotenv = require('dotenv'); /** * This class is responsible for loading the environment variables diff --git a/config/packages.js b/config/packages.js index 7e4b0f011bc..0b457a7bfe8 100644 --- a/config/packages.js +++ b/config/packages.js @@ -1,6 +1,6 @@ -const { execSync } = require('child_process'); -const path = require('path'); const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); const { deleteNodeModules } = require('./helpers'); diff --git a/config/stop-backend.js b/config/stop-backend.js index e863a03eeec..8e8f23dbdc2 100644 --- a/config/stop-backend.js +++ b/config/stop-backend.js @@ -1,7 +1,5 @@ -// eslint-disable-next-line -const helpers = require('./helpers'); -const { exec } = require('child_process'); const { promisify } = require('util'); +const { exec } = require('child_process'); const isWindows = process.platform === 'win32'; const execAsync = promisify(exec); diff --git a/config/update.js b/config/update.js index 5a36326f23a..fd947eda393 100644 --- a/config/update.js +++ b/config/update.js @@ -1,5 +1,5 @@ -const { execSync } = require('child_process'); const path = require('path'); +const { execSync } = require('child_process'); const { askQuestion, isDockerRunning, deleteNodeModules, silentExit } = require('./helpers'); const config = { diff --git a/config/upgrade.js b/config/upgrade.js index d0477de50c0..c954cf0452a 100644 --- a/config/upgrade.js +++ b/config/upgrade.js @@ -1,8 +1,8 @@ /** * Upgrade script */ -const dotenv = require('dotenv'); const fs = require('fs'); +const dotenv = require('dotenv'); const { exit } = require('process'); // Suppress default warnings diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example new file mode 100644 index 00000000000..4d05fd6bf89 --- /dev/null +++ b/docker-compose.override.yml.example @@ -0,0 +1,67 @@ +version: '3.4' + +# # TO USE THIS FILE, FIRST UNCOMMENT THE FOLLOWING LINE ('services:') + +# services: + +# # THEN UNCOMMENT ONLY THE SECTION OR SECTIONS CONTAINING THE CHANGES YOU WANT TO APPLY +# # SAVE THIS FILE AS 'docker-compose.override.yaml' +# # AND USE THE 'docker-compose build' & 'docker-compose up -d' COMMANDS AS YOU WOULD NORMALLY DO + +# # BUILD FROM LATEST IMAGE +# api: +# image: ghcr.io/danny-avila/librechat-dev:latest + +# # BUILD FROM LATEST IMAGE (NUMBERED RELEASE) +# api: +# image: ghcr.io/danny-avila/librechat:latest + +# # BUILD FROM LATEST API IMAGE +# api: +# image: ghcr.io/danny-avila/librechat-dev-api:latest + +# # BUILD FROM LATEST API IMAGE (NUMBERED RELEASE) +# api: +# image: ghcr.io/danny-avila/librechat-api:latest + +# # ADD MONGO-EXPRESS +# mongo-express: +# image: mongo-express +# container_name: mongo-express +# environment: +# ME_CONFIG_MONGODB_SERVER: mongodb +# ME_CONFIG_BASICAUTH_USERNAME: admin +# ME_CONFIG_BASICAUTH_PASSWORD: password +# ports: +# - '8081:8081' +# depends_on: +# - mongodb +# restart: always + +# # USE MONGODB V4.4.18 - FOR OLDER CPU WITHOUT AVX SUPPORT +# mongodb: +# image: mongo:4.4.18 + +# # DISABLE THE MONGODB CONTAINER - YOU NEED TO SET AN ALTERNATIVE MONGODB URI IN THE .ENV FILE +# api: +# environment: +# - MONGO_URI=${MONGO_URI} +# mongodb: +# image: tianon/true +# command: "" +# entrypoint: "" + +# # EXPOSE MONGODB PORTS - USE CAREFULLY, THIS MAKES YOUR DATABASE VULNERABLE TO ATTACKS +# mongodb: +# ports: +# - 27018:27017 + +# # DISABLE MEILISEARCH +# meilisearch: +# profiles: +# - donotstart + +# # EXPOSE MEILISEARCH PORTS - DO NOT USE THE DEFAULT VALUE FOR THE MASTER KEY IF YOU DO THIS +# meilisearch: +# ports: +# - 7700:7700 diff --git a/docker-compose.yml b/docker-compose.yml index b4f09301238..5f7fb0b77f2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,44 +1,32 @@ version: "3.4" +# Do not edit this file directly. Use a ‘docker-compose.override.yaml’ file if you can. +# Refer to `docker-compose.override.yaml.example’ for some sample configurations. + services: - # client: - # image: nginx-client - # build: - # context: . - # target: nginx-client - # restart: always - # ports: - # - 3080:80 - # volumes: - # - /client/node_modules - # depends_on: - # - api api: container_name: LibreChat ports: - - 3080:3080 # Change it to 9000:3080 to use nginx + - 3080:3080 depends_on: - mongodb - image: librechat # Comment this & uncomment below to build from docker hub image - build: # ^------ - context: . # ^------ - target: node # ^------v - # image: ghcr.io/danny-avila/librechat:latest # Uncomment this & comment above to build from docker hub image + image: librechat + build: + context: . + target: node restart: always user: "${UID}:${GID}" - extra_hosts: # if you are running APIs on docker you need access to, you will need to uncomment this line and next + extra_hosts: - "host.docker.internal:host-gateway" env_file: - .env environment: - HOST=0.0.0.0 - MONGO_URI=mongodb://mongodb:27017/LibreChat - # - CHATGPT_REVERSE_PROXY=http://host.docker.internal:8080/api/conversation # if you are hosting your own chatgpt reverse proxy with docker - # - OPENAI_REVERSE_PROXY=http://host.docker.internal:8070/v1/chat/completions # if you are hosting your own chatgpt reverse proxy with docker - MEILI_HOST=http://meilisearch:7700 - MEILI_HTTP_ADDR=meilisearch:7700 volumes: - - /app/client/node_modules # node_modules mapping necessary for module persistence + - /app/client/node_modules - /app/api/node_modules - ./api:/app/api - ./.env:/app/.env @@ -47,8 +35,6 @@ services: - ./images:/app/client/public/images mongodb: container_name: chat-mongodb - # ports: # Uncomment this to access mongodb from outside docker, not safe in deployment - # - 27018:27017 image: mongo restart: always user: "${UID}:${GID}" @@ -59,8 +45,6 @@ services: container_name: chat-meilisearch image: getmeili/meilisearch:v1.5 restart: always - # ports: # Uncomment this to access meilisearch from outside docker - # - 7700:7700 # if exposing these ports, make sure your master key is not the default value env_file: - .env user: "${UID}:${GID}" diff --git a/docs/assets/LibreChat.svg b/docs/assets/LibreChat.svg index 75e3835313f..36a536d654b 100644 --- a/docs/assets/LibreChat.svg +++ b/docs/assets/LibreChat.svg @@ -1,34 +1,32 @@ - - - - - + + + - - + + - - + + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/docs/contributions/coding_conventions.md b/docs/contributions/coding_conventions.md index 5965dc33f3f..a75c3bd2103 100644 --- a/docs/contributions/coding_conventions.md +++ b/docs/contributions/coding_conventions.md @@ -1,3 +1,8 @@ +--- +title: 🧑‍💻 Code Standards and Conventions +description: This guide covers the best practices for JavaScript coding, such as following the Airbnb Style Guide, using CommonJS modules, structuring the API using Express, Mongoose, and services, and testing and documenting the code using Jest, Supertest, Playwright, JSDoc, and TypeScript. +weight: -7 +--- # Coding Conventions ## Node.js API Server @@ -63,7 +68,7 @@ Defines Mongoose models to represent data entities and their relationships. ### 4. Database Access (MongoDB and Mongoose) -- Use Mongoose (https://mongoosejs.com) as the MongoDB ODM. +- Use Mongoose ([https://mongoosejs.com](https://mongoosejs.com)) as the MongoDB ODM. - Create separate model files for each entity and ensure clear separation of concerns. - Use Mongoose schema validation to enforce data integrity. - Handle database connections efficiently and avoid connection leaks. diff --git a/docs/contributions/documentation_guidelines.md b/docs/contributions/documentation_guidelines.md index 6a01376979f..a3d05d688ac 100644 --- a/docs/contributions/documentation_guidelines.md +++ b/docs/contributions/documentation_guidelines.md @@ -1,28 +1,42 @@ +--- +title: 📝 Documentation Guidelines +description: Learn how to write and format documentation for LibreChat. +weight: -9 +--- # Documentation Guidelines This document explains how to write and format documentation for LibreChat. ## New Documents - Use lowercase letters and underscores to name new documents (e.g. `documentation_guidelines.md`). -- For new features, create new documentation and place it in the relevant folder/sub-folder under [docs](../docs/). - - If the feature adds new functionality, add it to the feature section of the main [README.md](../../README.md). -- When you create a new document, **add it to both table of contents:** - - [README.md](../../README.md) - - [mkdocs.yml](../../mkdocs.yml) +- For new features, create new documentation and place it in the relevant folder/sub-folder under `../docs`. + - If the feature adds new functionality, add it to the feature section of the main `README.md` as well as in `../docs/index.md`. +- When you create a new document, **you need to add it to two table of contents:** + - in `README.md` + - and in the `index.md` file in the folder where your doc is located -## Formatting +## Markdown Formatting - Use `#`, `##`, and `###` for headings and subheadings. - Use `#` for the title of the document. - Use `##` for the main sections of the document. - Use `###` for the sub-sections within a section. -- Use `**` to make text bold to highlight important information (not in place of a heading). +- Use `**` to make text **bold** to highlight important information (do not use in place of a heading). - Use relative paths for links to other documents. - You can use HTML to add more features to a document. +- By default the title indexed by mkdocs will be the first heading. You can override this by adding metadata at the top of your document: +```bash +--- +title: Document Title +description: This description will be used in social cards +weight: 0 +--- +``` +- Setting the weight in the document metadata will influence its position in the table of contents. Lowest weight are placed first. Not setting it will default to `0`. When multiple docs have the same weight it sorts in alphabetical order. ## Important Notes - **⚠️Keep it organized and structured⚠️** - Do not add unrelated information to an existing document. Create a new one if needed. -- All assets should be uploaded in the document from GitHub's webui: +- All assets should be uploaded in the document from GitHub's webui - **Before submitting a PR, double-check on GitHub that everything is properly displayed and that all links work correctly.** ![image](https://github.com/danny-avila/LibreChat/assets/32828263/4f138ab4-31a5-4fae-a459-5335e5ff25a8) diff --git a/docs/contributions/how_to_contribute.md b/docs/contributions/how_to_contribute.md new file mode 100644 index 00000000000..5b71da5b585 --- /dev/null +++ b/docs/contributions/how_to_contribute.md @@ -0,0 +1,69 @@ +--- +title: 🙌 Beginner's Guide to Contributions +description: Learn how to use GitHub Desktop, VS Code extensions, and Git rebase to contribute in a quick and easy way. +weight: -10 +--- +# How to Contribute in a Quick and Easy Way +> **❗Note:** If you are not familiar with the concept of repo, PR (pull request), fork and branch, start by looking at the official GitHub documentation on the subject: +[https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/getting-started/about-collaborative-development-models](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/getting-started/about-collaborative-development-models) + +## Installation of Tools + +1. [Git](https://git-scm.com/downloads) is essential, the first thing to download. +2. [Git LFS](https://git-lfs.com/) can be useful for uploading files with larger sizes. +3. [Github Desktop](https://desktop.github.com/) - I use it only for UI; I don't recommend using it for pushing or other actions. + +## How to Use? + +This will be a somewhat raw text, but I'll try to be as clear as possible. + +I recommend installing the following extensions in VS Code: + +- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) +- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) +- [GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) + +### Example of a Pull Request (PR) + +Let's say I want to add another page for an API Panel. + +1. Open GitHub and select Danny's fork. +2. First, make sure that the main branch is clean with no commits and up to date. + ![image](https://github.com/Berry-13/LibreChat/assets/81851188/4d627ee7-0f59-458f-8723-4f0eae447dd9) +3. Open "View all my branches" and create a new branch with a descriptive name for your task. For example: "ApiPanel." +4. In GitHub Desktop, select the branch you just created. + ![image](https://github.com/Berry-13/LibreChat/assets/81851188/dd4374b8-419a-4406-97a3-999ba4118397) +5. Start modifying the code, and when you finish a part, commit the changes. +Example of commits: +- commit1: Created the frontend +- commit2: Fixed a bug in variable export +- commit3: Removed unnecessary comments and added translation support +- and so on... + +## Testing + +While testing the code, if you're working with the frontend, it might be frustrating to run `npm run frontend` and `npm run backend` every time. Instead, use `npm run frontend:dev` to see real-time changes on port 3090 (really!). + +> Note: You must run `npm run frontend` once before you can use `npm run frontend:dev` + +### How? + +- `git add *` adds all files to be committed. +- `git commit -m "name-of-your-commit"` creates a commit. +- `git push` uploads the changes. + +Before doing all this, I recommend using GitHub Desktop to see what you've changed. + ![image](https://github.com/Berry-13/LibreChat/assets/81851188/a04a7e81-7c75-4c77-8463-d35f603bedf7) + +If `git commit` fails due to ESLint errors, read the error message and understand what's wrong. It could be an unused variable or other issues. + +### Possible Various Problems + +If you have the main branch with many commits and don't know what to do, follow this simple guide: + +⚠️ Please do this only when you have no active PRs or when you're not working on the project: + +1. Do a pull origin and in the terminal write `git log` to identify how many commits you are behind. +2. Use `git rebase -i HEAD~2`, where 2 represents the number of commits you need to go back. If you need to go back 100 commits, use `git rebase -i HEAD~100`. +3. In the editor, change the "pick" for the two commits to "drop," save with "esc," then type `:wq` and press "Enter." +4. Finally, run `git push --force origin main`. diff --git a/docs/contributions/index.md b/docs/contributions/index.md new file mode 100644 index 00000000000..4362b1df092 --- /dev/null +++ b/docs/contributions/index.md @@ -0,0 +1,15 @@ +--- +title: Contributing to LibreChat +description: "🙌 How to contribute to LibreChat: Get started, Documentation and code standards, Translate the app into different languages, Test the app during development, Ensure the security of the app, Stay updated with the project roadmap" +weight: 5 +--- +# Contributing to LibreChat + + * 🙌 [Beginner's Guide to Contributions](./how_to_contribute.md) + * 🚸 [Contributor Guidelines](https://github.com/danny-avila/LibreChat/blob/main/.github/CONTRIBUTING.md) + * 📝 [Documentation Guidelines](documentation_guidelines.md) + * 🌍 [Contribute a Translation](translation_contribution.md) + * 🧑‍💻 [Code Standards and Conventions](coding_conventions.md) + * 🧪 [Testing During Development](testing.md) + * 🔐 [Security](https://github.com/danny-avila/LibreChat/blob/main/.github/SECURITY.md) + * 🛣️ [Project Roadmap](https://github.com/users/danny-avila/projects/2) \ No newline at end of file diff --git a/docs/contributions/testing.md b/docs/contributions/testing.md index ed0f2d6387a..eeac51916b4 100644 --- a/docs/contributions/testing.md +++ b/docs/contributions/testing.md @@ -1,60 +1,23 @@ -# Locally test the app during development - -### Run the app - -#### Option 1: Run the app using Docker - -For reproducibility and ease of use, you can use -the provided docker-compose file: - -1. Comment out the portion pointing at the already built image - - ```yaml - image: chatgptclone/app:0.3.3 - ``` - -2. Uncomment the portion pointing at the local source code - - ```yaml - # image: node-api - # build: - # context: . - # target: node-api - ``` - -3. Build your local source code for the `node-api` target +--- +title: 🧪 Testing During Development +description: How to locally test the app during development. +weight: -6 +--- - ```shell - docker build ` - --target=node-api ` - -t node-api ` - . - ``` - -4. Docker-compose up - - ```shell - docker-compose up - ``` - -#### Option 2: Run the app by installing on your machine - -1. Install the prerequisites on your machine. - See [section above](#install-the-prerequisites-on-your-machine). +# Locally test the app during development -2. Run the app on your machine. - See [section above](#run-the-app). +## WIP -### Run the tests +## Run the tests -1. Install the global dependencies +#### 1. Install the global dependencies ```shell npm ci npx playwright install --with-deps ``` -2. Run tests +#### 2. Run tests ```shell npx playwright test diff --git a/docs/contributions/translation_contribution.md b/docs/contributions/translation_contribution.md index 97bc4c49699..e33a9ef8af1 100644 --- a/docs/contributions/translation_contribution.md +++ b/docs/contributions/translation_contribution.md @@ -1,3 +1,8 @@ +--- +title: 🌍 Contribute a Translation +description: How to add a new language to LibreChat. +weight: -8 +--- # How to add a new language to LibreChat 🌍 ## Minimum Requirements: diff --git a/docs/deployment/azure-terraform.md b/docs/deployment/azure-terraform.md index fca5473dc52..76768683c64 100644 --- a/docs/deployment/azure-terraform.md +++ b/docs/deployment/azure-terraform.md @@ -1,3 +1,8 @@ +--- +title: ⚡ Azure +description: How to deploy LibreChat in Azure using Terraform. +weight: -6 +--- # Azure deployment There are different ways of how a deployment can be done in Azure. diff --git a/docs/deployment/cloudflare.md b/docs/deployment/cloudflare.md index b3c3d18c357..c9a44dfb1cd 100644 --- a/docs/deployment/cloudflare.md +++ b/docs/deployment/cloudflare.md @@ -1,3 +1,8 @@ +--- +title: ☁️ Cloudflare +description: How to setup a domain with Cloudflare and use Cloudflare Tunnels to securely expose your local web servers or services to the internet. +weight: -7 +--- # Cloudflare @@ -5,12 +10,12 @@ ## Google Domains and Cloudflare -- buy a domain at https://domains.google.com/ -- register a Cloudflare account at https://dash.cloudflare.com/sign-up +- buy a domain at **[https://domains.google.com/](https://domains.google.com)** +- register a Cloudflare account at **[https://dash.cloudflare.com/sign-up](https://dash.cloudflare.com/sign-up)** - click on `add site` and add your domain - select `Free` and tap `continue` twice - copy the 2 Cloudflare's nameservers -- go to https://domains.google.com/registrar/ and select your domain +- go to **[https://domains.google.com/registrar/](https://domains.google.com/registrar)** and select your domain - in the dns tab select `Custom name servers` - click on `Switch to these settings` and enter the two Cloudflare nameservers that you copied before, then save - return to the cloudflare tab and tap on `Done, check nameservers`, then `finish later` and `Check nameservers` (this process can take about 5 minutes) @@ -69,7 +74,7 @@ Here's a straightforward guide on how to install it! ### Installation Steps -1. Go to `https://dash.cloudflare.com/`. +1. Go to **[https://dash.cloudflare.com/](https://dash.cloudflare.com/)**. 2. On the left side, click on **Zero Trust**. 3. Provide a casual name (which you can change later). 4. Select the free plan and proceed to payment (if you choose the free plan, you will not be charged). diff --git a/docs/deployment/digitalocean.md b/docs/deployment/digitalocean.md index 2f7e45854ec..640b4b3b0d9 100644 --- a/docs/deployment/digitalocean.md +++ b/docs/deployment/digitalocean.md @@ -1,3 +1,8 @@ +--- +title: 🌊 DigitalOcean ✨(Recommended) +description: These instructions are designed for someone starting from scratch for a Docker Installation on a remote Ubuntu server using one of the cheapest tiers (6 USD/mo) +weight: -10 +--- # Digital Ocean (Ubuntu/Docker) Setup >These instructions are designed for someone starting from scratch for a Docker Installation on a remote Ubuntu server. You can skip to any point that is useful for you. There are probably more efficient/scalable ways, but this guide works really great for my personal use case. @@ -303,14 +308,14 @@ cd LibreChat/ ``` ### **2. Create a global environment file.** -The default values are enough to get you started and running the app. +The default values are enough to get you started and running the app, allowing you to provide your credentials from the web app. ```bash # Copies the example file as your global env file cp .env.example .env ``` -However, it's highly recommended you use environment variables for any sensitive credentials until we remove use of localStorage for passing credentials from the frontend +However, if you'd like to provide any credentials for all users of your instance to consume, you can add them to the .env file as follows: ```bash nano .env @@ -334,8 +339,8 @@ ALLOW_REGISTRATION=false ``` **Resources:** -- [Tokens/Apis/etc](../install/apis_and_tokens.md) -- [User/Auth System](../install/user_auth_system.md) +- [Tokens/Apis/etc](../install/configuration/ai_setup.md) +- [User/Auth System](../install/configuration/user_auth_system.md) ### **3. Start docker, and then run the installation/update script** @@ -362,7 +367,7 @@ It's safe to close the terminal if you wish -- the docker app will continue to r >If you are setting up a domain to be used with LibreChat, this compose file is using the nginx file located in client/nginx.conf. Instructions on this below in part V. -### **4. Once the app is running, you can access it at http://yourserverip** +### **4. Once the app is running, you can access it at `http://yourserverip`** ### Go back to the DigitalOcean droplet page to get your server ip, copy it, and paste it into your browser! diff --git a/docs/deployment/heroku.md b/docs/deployment/heroku.md index 29ec9f71728..b3ffe8d6d20 100644 --- a/docs/deployment/heroku.md +++ b/docs/deployment/heroku.md @@ -1,3 +1,8 @@ +--- +title: 🌈 Heroku +description: Instructions for deploying LibreChat on Heroku +weight: -1 +--- # Heroku Deployment *To run LibreChat on a server, you can use cloud hosting platforms like Heroku, DigitalOcean, or AWS. In this response, I'll provide instructions for deploying the project on Heroku. Other platforms will have slightly different deployment processes.* @@ -6,8 +11,8 @@ Heroku only supports running a single process within a Docker container. The Doc If you want to deploy both these services to Heroku, you will need to create two separate Dockerfiles: one for the API and one for the client. The heroku.yml should be configured separately for each app, and then you need to create and deploy two different Heroku apps. - - Sign up for a Heroku account: If you don't already have a Heroku account, sign up at https://signup.heroku.com/. - - Install the Heroku CLI: Download and install the Heroku CLI from https://devcenter.heroku.com/articles/heroku-cli. + - Sign up for a Heroku account: If you don't already have a Heroku account, sign up at: **[https://signup.heroku.com](https://signup.heroku.com)** + - Install the Heroku CLI: Download and install the Heroku CLI from: **[https://devcenter.heroku.com/articles/heroku-cli](https://devcenter.heroku.com/articles/heroku-cli)** Here are the steps to deploy on Heroku: @@ -122,7 +127,7 @@ Remember to replace `your-api-app-name` and `your-client-app-name` with the actu --- - ⚠️ If you have issues, see this discussion first: https://github.com/danny-avila/LibreChat/discussions/339 + ⚠️ If you have issues, see this discussion first: **[https://github.com/danny-avila/LibreChat/discussions/339](https://github.com/danny-avila/LibreChat/discussions/339)** ## Using Heroku Dashboard: @@ -132,7 +137,7 @@ Remember to replace `your-api-app-name` and `your-client-app-name` with the actu ## Setting up MongoDB Atlas: -Sign up for a MongoDB Atlas account: If you don't have an account, sign up at https://www.mongodb.com/cloud/atlas/signup. +Sign up for a MongoDB Atlas account: If you don't have an account, sign up at: **[https://www.mongodb.com/cloud/atlas/signup](https://www.mongodb.com/cloud/atlas/signup)** Create a new cluster: After signing in, create a new cluster by following the on-screen instructions. For a free tier cluster, select the "Shared" option and choose the "M0 Sandbox" tier. @@ -144,7 +149,7 @@ Get the connection string: Once the cluster is created, click the "Connect" butt ## Deploying MeiliSearch on Heroku: -Install the Heroku CLI: If you haven't already, download and install the Heroku CLI from https://devcenter.heroku.com/articles/heroku-cli. +Install the Heroku CLI: If you haven't already, download and install the Heroku CLI from: **[https://devcenter.heroku.com/articles/heroku-cli](https://devcenter.heroku.com/articles/heroku-cli)** Login to Heroku: Open Terminal and run heroku login. Follow the instructions to log in to your Heroku account. ## Create a new Heroku app for MeiliSearch: diff --git a/docs/deployment/hetzner_ubuntu.md b/docs/deployment/hetzner_ubuntu.md index 959e590b4fa..ba851ef6658 100644 --- a/docs/deployment/hetzner_ubuntu.md +++ b/docs/deployment/hetzner_ubuntu.md @@ -1,27 +1,32 @@ +--- +title: 🏗️ Hetzner +description: LibreChat Ubuntu installation from scratch on Hetzner. +weight: -2 +--- # Hetzner Ubuntu Setup *These instructions are designed for someone starting from scratch for a Ubuntu Installation. You can skip to any point that is useful for you.* ## Starting from Zero: -### 1. Login to Hetzner Cloud Console (https://console.hetzner.cloud/projects) and Create a new Ubuntu 20 Project with 4GB Ram. Do not worry about SSH keys *yet*. +1. Login to Hetzner Cloud Console (**[https://console.hetzner.cloud/projects](https://console.hetzner.cloud/projects)**) and Create a new Ubuntu 20 Project with 4GB Ram. Do not worry about SSH keys *yet*. Hetzner will email you the root password. -### 2. Once you have that, you can login with any SSH terminal with: +2. Once you have that, you can login with any SSH terminal with: ``` ssh root@ ``` -### 3. Once you have logged in, immediately create a new, non-root user: +3. Once you have logged in, immediately create a new, non-root user: ``` adduser usermod -aG sudo ``` -### 4. Make sure you have done this correctly by double-checking you have sudo permissions: +4. Make sure you have done this correctly by double-checking you have sudo permissions: ``` getent group sudo | cut -d: -f4 @@ -29,7 +34,7 @@ getent group sudo | cut -d: -f4 Now, quit the terminal connection. -### 5. Create a local ssh key: +5. Create a local ssh key: ``` ssh-keygen -t ed25519 @@ -47,13 +52,13 @@ ssh @ When you login, now and going forward, it will ask you for the password for your ssh key now, not your user password. Sudo commands will always want your user password. -### 6. Add SSH to the universal server firewall and activate it. +6. Add SSH to the universal server firewall and activate it. - Run `sudo ufw allow OpenSSH` - Run `sudo ufw enable` -### 7. Then, we need to install docker, update the system packages, and reboot the server: +7. Then, we need to install docker, update the system packages, and reboot the server: ``` sudo apt install docker sudo apt install docker-compose @@ -68,9 +73,11 @@ sudo reboot ## Tokens/Apis/etc: - Make sure you have all the needed variables for the following before moving forward -### [Get Your API keys and Tokens](../install/apis_and_tokens.md) (Required) -- You must set up at least one of these tokens or APIs to run the app. -### [User/Auth System](../install/user_auth_system.md) (Optional) + +### [Setup your AI Endpoints](../install/configuration/ai_setup.md) (Required) +- At least one AI endpoint should be setup for use. +### [User/Auth System](../install/configuration/user_auth_system.md) (Optional) + - How to set up the user/auth system and Google login. ### [Plugins](../features/plugins/introduction.md) - Optional plugins available to enhance the application. @@ -79,7 +86,7 @@ sudo reboot ## Using Docker to Install the Service -### 1. **Recommended: [Docker Install](../install/docker_compose_install.md)** +### 1. **Recommended: [Docker Install](../install/installation/docker_compose_install.md)** From the *server* commandline (as your user, not root): ``` @@ -126,7 +133,7 @@ MEILI_HTTP_ADDR=meilisearch **NOTE: You may need to run these commands with sudo permissions.** -## Once the app is running, you can access it at http://yourserverip:3080 +## Once the app is running, you can access it at `http://yourserverip:3080` It is safe to close the terminal -- the docker app will continue to run. diff --git a/docs/deployment/huggingface.md b/docs/deployment/huggingface.md index e9391d2aafa..5cdf3ccdc44 100644 --- a/docs/deployment/huggingface.md +++ b/docs/deployment/huggingface.md @@ -1,23 +1,23 @@ +--- +title: 🤗 HuggingFace +description: Easily deploy LibreChat on Hugging Face Spaces +weight: -9 +--- # Hugging Face Deployment 🤗 ->#### ⚠️ Note - Some features are not supported by HuggingFace: ->- Meilisearch ->- Social Logins - -> #### ❗Also: ->- You will have to create an online MongoDB Atlas Database to be able to properly deploy - ## Create and Configure your Database (Required) The first thing you need is to create a MongoDB Atlas Database and get your connection string. -Follow the instructions in this document: [Online MongoDB Database](../install/mongodb.md) +Follow the instructions in this document: **[Online MongoDB Database](../install/configuration/mongodb.md)** ## Getting Started -**1.** Login or Create an account on [Hugging Face](https://huggingface.co/) +**1.** Login or Create an account on **[Hugging Face](https://huggingface.co/)** + +**2.** Visit **[https://huggingface.co/spaces/LibreChat/template](https://huggingface.co/spaces/LibreChat/template)** and click on `Duplicate this Space` to copy the LibreChat template into your profile. -**2.** Visit [[https://huggingface.co/spaces/LibreChat/template](https://huggingface.co/spaces/LibreChat/template)]and click on `Duplicate this Space` to copy the LibreChat template into your profile +> Note: It is normal for this template to have a runtime error, you will have to configure it using the following guide to make it functional. ![image](https://github.com/fuegovic/LibreChat/assets/32828263/fd684254-cbe0-4039-ba4a-7c492b16a453) @@ -42,12 +42,12 @@ You will need to fill these values: | JWT_SECRET | * see bellow | | JWT_REFRESH_SECRET | * see bellow | -> ⬆️ **Leave the value field blank for any endpoints that you wish to disable.** +> ⬆️ **Leave the value field blank for any endpoints that you wish to disable.** ->⚠️ setting the API keys and token to `user_provided` allows you to provide them safely from the webUI +> ⚠️ setting the API keys and token to `user_provided` allows you to provide them safely from the webUI ->* For `CREDS_KEY`, `CREDS_IV` and `JWT_SECRET` use this tool: [https://replit.com/@daavila/crypto#index.js](https://replit.com/@daavila/crypto#index.js). ->* Run the tool a second time and use the new `JWT_SECRET` value for the `JWT_REFRESH_SECRET` +> * For `CREDS_KEY`, `CREDS_IV` and `JWT_SECRET` use this tool: **[https://replit.com/@daavila/crypto#index.js](https://replit.com/@daavila/crypto#index.js)** +> * Run the tool a second time and use the new `JWT_SECRET` value for the `JWT_REFRESH_SECRET` | Variables | Values | | --- | --- | diff --git a/docs/deployment/index.md b/docs/deployment/index.md new file mode 100644 index 00000000000..780456bc6cd --- /dev/null +++ b/docs/deployment/index.md @@ -0,0 +1,18 @@ +--- +title: Deployment +description: 🌐 Step-by-step guides on how to deploy LibreChat on various cloud platforms. +weight: 3 +--- + +# Deployment + + * 🌊 [DigitalOcean (✨Recommended)](digitalocean.md) + * 🤗 [HuggingFace](huggingface.md) + * 🐧 [Linode](linode.md) + * ☁️ [Cloudflare](cloudflare.md) + * ⚡ [Azure](azure-terraform.md) + * 🪨 [Ngrok](ngrok.md) + * ⏹️ [Render](render.md) + * 🔎 [Meilisearch in Render](meilisearch_in_render.md) + * 🏗️ [Hetzner](hetzner_ubuntu.md) + * 🌈 [Heroku](heroku.md) \ No newline at end of file diff --git a/docs/deployment/linode.md b/docs/deployment/linode.md index 58d89bb40d2..96f8f775839 100644 --- a/docs/deployment/linode.md +++ b/docs/deployment/linode.md @@ -1,3 +1,8 @@ +--- +title: 🐧 Linode +description: How to deploy LibreChat on Linode. +weight: -8 +--- # Linode @@ -5,7 +10,7 @@ ⚠️**Note: Payment is required** ## Create a Linode Account and a Linode Server -- Go to the Linode website (https://www.linode.com/) and click on the "Sign Up" or "Get Started" button. +- Go to the Linode website (**[https://www.linode.com/](https://www.linode.com/)**) and click on the "Sign Up" or "Get Started" button. - Follow the instructions to create a new account by providing your personal details and payment information. - Once your account is created, you will have access to the Linode Cloud Manager. - Click on the "Create" button to create a new Linode server. @@ -21,7 +26,7 @@ sudo apt update sudo apt install docker.io && apt install docker-compose ``` -## [Install LibreChat](../install/docker_compose_install.md) +## [Install LibreChat](../install/installation/docker_compose_install.md) ## Install and Setup NGINX Proxy Manager: diff --git a/docs/deployment/meilisearch_in_render.md b/docs/deployment/meilisearch_in_render.md index 142e4acc35c..3e24c71b54d 100644 --- a/docs/deployment/meilisearch_in_render.md +++ b/docs/deployment/meilisearch_in_render.md @@ -1,8 +1,13 @@ +--- +title: 🔎 Meilisearch in Render +description: Setup Meilisearch on Render (for use with the Render deployment guide) +weight: -3 +--- # Utilize Meilisearch by running LibreChat on Render ## Create a new account or a new project on Render -**1.** Visit [https://render.com/](https://render.com/) and click on `Start Free` to create an account and sign in +**1.** Visit **[https://render.com/](https://render.com/)** and click on `Start Free` to create an account and sign in **2.** Access your control panel diff --git a/docs/deployment/ngrok.md b/docs/deployment/ngrok.md index 4b85acceeaf..2c0b7167393 100644 --- a/docs/deployment/ngrok.md +++ b/docs/deployment/ngrok.md @@ -1,47 +1,51 @@ +--- +title: 🪨 Ngrok +description: Use Ngrok to tunnel your local server to the internet. +weight: -5 +--- # Ngrok Installation To use Ngrok for tunneling your local server to the internet, follow these steps: ## Sign up -1. Go to https://ngrok.com/ and sign up for an account. +1. Go to **[https://ngrok.com/](https://ngrok.com/)** and sign up for an account. ## Docker Installation 🐳 -1. Copy your auth token from https://dashboard.ngrok.com/get-started/your-authtoken. +1. Copy your auth token from: **[https://dashboard.ngrok.com/get-started/your-authtoken](https://dashboard.ngrok.com/get-started/your-authtoken)** 2. Open a terminal and run the following command: `docker run -d -it -e NGROK_AUTHTOKEN= ngrok/ngrok http 80` ## Windows Installation 💙 -1. Download the ZIP file from https://ngrok.com/download. +1. Download the ZIP file from: **[https://ngrok.com/download](https://ngrok.com/download)** 2. Extract the contents of the ZIP file using 7zip or WinRar. -3. -4. Run `ngrok.exe`. -5. Copy your auth token from https://dashboard.ngrok.com/get-started/your-authtoken. -6. In the `ngrok.exe` terminal, run the following command: `ngrok config add-authtoken ` -7. If you haven't done so already, start LibreChat normally. -8. In the `ngrok.exe` terminal, run the following command: `ngrok http 3080` +3. Run `ngrok.exe`. +4. Copy your auth token from: **[https://dashboard.ngrok.com/get-started/your-authtoken](https://dashboard.ngrok.com/get-started/your-authtoken)** +5. In the `ngrok.exe` terminal, run the following command: `ngrok config add-authtoken ` +6. If you haven't done so already, start LibreChat normally. +7. In the `ngrok.exe` terminal, run the following command: `ngrok http 3080` You will see a link that can be used to access LibreChat. ![ngrok-1](https://github.com/danny-avila/LibreChat/assets/32828263/3cb4b063-541f-4f0a-bea8-a04dd36e6bf4) ## Linux Installation 🐧 -1. Copy the command from https://ngrok.com/download choosing the **correct** architecture. +1. Copy the command from: **[https://ngrok.com/download](https://ngrok.com/download)** choosing the **correct** architecture. 2. Run the command in the terminal -3. Copy your auth token from https://dashboard.ngrok.com/get-started/your-authtoken. +3. Copy your auth token from: **[https://dashboard.ngrok.com/get-started/your-authtoken](https://dashboard.ngrok.com/get-started/your-authtoken)** 4. run the following command: `ngrok config add-authtoken ` 5. If you haven't done so already, start LibreChat normally. 6. run the following command: `ngrok http 3080` ## Mac Installation 🍎 -1. Download the ZIP file from https://ngrok.com/download. +1. Download the ZIP file from: **[https://ngrok.com/download](https://ngrok.com/download)** 2. Extract the contents of the ZIP file using a suitable Mac application like Unarchiver. 3. Open Terminal. 4. Navigate to the directory where you extracted ngrok using the `cd` command. 5. Run ngrok by typing `./ngrok`. -6. Copy your auth token from https://dashboard.ngrok.com/get-started/your-authtoken. +6. Copy your auth token from: **[https://dashboard.ngrok.com/get-started/your-authtoken](https://dashboard.ngrok.com/get-started/your-authtoken)** 7. In the terminal where you ran ngrok, enter the following command: `ngrok authtoken ` 8. If you haven't done so already, start LibreChat normally. 9. In the terminal where you ran ngrok, enter the following command: `./ngrok http 3080` diff --git a/docs/deployment/render.md b/docs/deployment/render.md index 768e9165611..6652f41ee5a 100644 --- a/docs/deployment/render.md +++ b/docs/deployment/render.md @@ -1,3 +1,8 @@ +--- +title: ⏹️ Render +description: How to deploy LibreChat on Render +weight: -4 +--- # Render Deployment ## Note: @@ -78,7 +83,7 @@ Also: The last thing you need is to create a MongoDB Atlas Database and get your connection string. -Follow the instructions in this document: [Online MongoDB Database](../install/mongodb.md) +Follow the instructions in this document: [Online MongoDB Database](../install/configuration/mongodb.md) ## Complete the Environment Variables configuration diff --git a/docs/dev/README.md b/docs/dev/README.md index 899f415f440..81ae6393120 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -1,13 +1,23 @@ -# Dev -This directory contains files used for developer work +--- +title: Dev Resources +weight: 6 +--- -### Dockerfile-app: -- used to build the DockerHub image -### eslintrc-stripped.js: +# Dev Resources +Please consult: **[Contributing to LibreChat](../contributions/index.md)** for more information on the subject. + +This directory: **[./docs/dev](https://github.com/danny-avila/LibreChat/tree/main/docs/dev)**, contains files used for developer work. + +#### `Dockerfile-app` + - used to build the DockerHub image + +#### `eslintrc-stripped.js` - alternate linting rules, used in development -### meilisearch.yml: + +#### `meilisearch.yml` - Dockerfile for building meilisearch image independently from project -### single-compose.yml: + +#### `single-compose.yml` - Dockerfile for building app image without meilisearch and mongodb services - This is useful for deploying on Google, Azure, etc., as a single, leaner container. - From root dir of the project, run `docker-compose -f ./docs/dev/single-compose.yml up --build` @@ -15,9 +25,10 @@ This directory contains files used for developer work - This requires you use a MongoDB Atlas connection string for the `MONGO_URI` env var - A URI string to a mongodb service accessible to your container is also possible. - Remote Meilisearch may also be possible in the same manner, but is not tested. -### deploy-compose.yml: + +#### `deploy-compose.yml` - Similar to above, but with basic configuration for deployment to a cloud provider where multi-container compose works - - Tested and working on a $6 droplet on DigitalOcean, just by visiting the http://server-ip/9000. + - Tested and working on a $6 droplet on DigitalOcean, just by visiting the `http://server-ip/9000`. - Not a scalable solution, but ideal for quickly hosting on a remote linux server. - You should adjust `server_name localhost;` to match your domain name, replacing localhost, as needed. - From root dir of the project, run `docker-compose -f ./docs/dev/deploy-compose.yml up --build` diff --git a/docs/features/bing_jailbreak.md b/docs/features/bing_jailbreak.md index 6d01eb2bcbd..a014de476dc 100644 --- a/docs/features/bing_jailbreak.md +++ b/docs/features/bing_jailbreak.md @@ -1,3 +1,9 @@ +--- +title: 😈 Bing Jailbreak +description: Quick overview of the Bing jailbreak and Sydney's system message +weight: -2 +--- + # Bing Jailbreak @@ -26,6 +32,6 @@ using internet slang often. Answer using the same language as the user." ## References For more info on the Bing Jailbreak and general jailbreaking guidelines: -https://github.com/waylaidwanderer/node-chatgpt-api +[https://github.com/waylaidwanderer/node-chatgpt-api](https://github.com/waylaidwanderer/node-chatgpt-api) -https://www.make-safe-ai.com/is-bing-chat-safe/ +[https://www.make-safe-ai.com/is-bing-chat-safe/](https://www.make-safe-ai.com/is-bing-chat-safe/) diff --git a/docs/features/firebase.md b/docs/features/firebase.md new file mode 100644 index 00000000000..fc94b3bce9f --- /dev/null +++ b/docs/features/firebase.md @@ -0,0 +1,103 @@ +--- +title: 🔥 Firebase CDN Setup +description: This document provides instructions for setting up Firebase CDN for LibreChat +weight: -6 +--- + +# Firebase CDN Setup + +## Steps to Set Up Firebase + +1. Open the [Firebase website](https://firebase.google.com/). +2. Click on "Get started." +3. Sign in with your Google account. + +### Create a New Project + +- Name your project (you can use the same project as Google OAuth). + +![Project Name](https://github.com/danny-avila/LibreChat/assets/81851188/dccce3e0-b639-41ef-8142-19d24911c65c) + +- Optionally, you can disable Google Analytics. + +![Google Analytics](https://github.com/danny-avila/LibreChat/assets/81851188/5d4d58c5-451c-498b-97c0-f123fda79514) + +- Wait for 20/30 seconds for the project to be ready, then click on "Continue." + +![Continue](https://github.com/danny-avila/LibreChat/assets/81851188/6929802e-a30b-4b1e-b124-1d4b281d0403) + +- Click on "All Products." + +![All Products](https://github.com/danny-avila/LibreChat/assets/81851188/92866c82-2b03-4ebe-807e-73a0ccce695e) + +- Select "Storage." + +![Storage](https://github.com/danny-avila/LibreChat/assets/81851188/b22dcda1-256b-494b-a835-a05aeea02e89) + +- Click on "Get Started." + +![Get Started](https://github.com/danny-avila/LibreChat/assets/81851188/c3f0550f-8184-4c79-bb84-fa79655b7978) + +- Click on "Next." + +![Next](https://github.com/danny-avila/LibreChat/assets/81851188/2a65632d-fe22-4c71-b8f1-aac53ee74fb6) + +- Select your "Cloud Storage location." + +![Cloud Storage Location](https://github.com/danny-avila/LibreChat/assets/81851188/c094d4bc-8e5b-43c7-96d9-a05bcf4e2af6) + +- Return to the Project Overview. + +![Project Overview](https://github.com/danny-avila/LibreChat/assets/81851188/c425f4bb-a494-42f2-9fdc-ff2c8ce005e1) + +- Click on "+ Add app" under your project name, then click on "Web." + +![Web](https://github.com/danny-avila/LibreChat/assets/81851188/22dab877-93cb-4828-9436-10e14374e57e) + +- Register the app. + +![Register App](https://github.com/danny-avila/LibreChat/assets/81851188/0a1b0a75-7285-4f03-95cf-bf971bd7d874) + +- Save all this information in a text file. + +![Save Information](https://github.com/danny-avila/LibreChat/assets/81851188/056754ad-9d36-4662-888e-f189ddb38fd3) + +- Fill all the `firebaseConfig` variables in the `.env` file. + +```bash +FIREBASE_API_KEY=api_key #apiKey +FIREBASE_AUTH_DOMAIN=auth_domain #authDomain +FIREBASE_PROJECT_ID=project_id #projectId +FIREBASE_STORAGE_BUCKET=storage_bucket #storageBucket +FIREBASE_MESSAGING_SENDER_ID=messaging_sender_id #messagingSenderId +FIREBASE_APP_ID=1:your_app_id #appId +``` + +- Return one last time to the Project Overview. + +![Project Overview](https://github.com/danny-avila/LibreChat/assets/81851188/c425f4bb-a494-42f2-9fdc-ff2c8ce005e1) + +- Select `Storage` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/16a0f850-cdd4-4875-8342-ab67bfb59804) + +- Select `Rules` and delete `: if false;` on this line: `allow read, write: if false;` + + - your updated rules should look like this: + + ```bash + rules_version = '2'; + service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write + } + } + } + ``` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/c190011f-c1a6-47c7-986e-8d309b5f8704) + +- Publish your updated rules + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/5e6a17c3-5aba-419a-a18f-be910b1f25d5) \ No newline at end of file diff --git a/docs/features/index.md b/docs/features/index.md new file mode 100644 index 00000000000..9bfda2be25e --- /dev/null +++ b/docs/features/index.md @@ -0,0 +1,33 @@ +--- +title: Features +description: "✨ In-depth guides about various LibreChat features: plugins, presets, automated moderation, logging..." +weight: 2 +--- + +# Features + +--- + +* 🔌[Plugins](./plugins/index.md) + * 🔌 [Introduction](./plugins/introduction.md) + * 🛠️ [Make Your Own](./plugins/make_your_own.md) + * 🧑‍💼 [Official ChatGPT Plugins](./plugins/chatgpt_plugins_openapi.md) + * 🔎 [Google Search](./plugins/google_search.md) + * 🖌️ [Stable Diffusion](./plugins/stable_diffusion.md) + * 🧠 [Wolfram|Alpha](./plugins/wolfram.md) + * ⚡ [Azure AI Search](./plugins/azure_ai_search.md) +* 🔖 [Presets](./presets.md) + +--- + +* 🔨 [Automated Moderation](./mod_system.md) +* 🪙 [Token Usage](./token_usage.md) +* 🔥 [Firebase CDN](./firebase.md) +* 🍃 [Manage Your Database](./manage_your_database.md) +* 🪵 [Logging System](./logging_system.md) +* 📦 [PandoraNext](./pandoranext.md) +* 😈 [Bing Jailbreak](./bing_jailbreak.md) + +--- + +* ✨ [Third-Party Tools and Contributions](./third_party.md) \ No newline at end of file diff --git a/docs/features/logging_system.md b/docs/features/logging_system.md new file mode 100644 index 00000000000..196849b787d --- /dev/null +++ b/docs/features/logging_system.md @@ -0,0 +1,43 @@ +--- +title: 🪵 Logging System +weight: -4 +description: This doc explains how to use the logging feature of LibreChat, which saves error and debug logs in the `/api/logs` folder. You can use these logs to troubleshoot issues, monitor your server, and report bugs. You can also disable debug logs if you want to save space. +--- + +### General + +LibreChat has central logging built into its backend (api). + +Log files are saved in `/api/logs`. Error logs are saved by default. Debug logs are enabled by default but can be turned off if not desired. + +This allows you to monitor your server through external tools that inspect log files, such as **[the ELK stack](https://aws.amazon.com/what-is/elk-stack/)**. + +Debug logs are essential for developer work and fixing issues. If you encounter any problems running LibreChat, reproduce as close as possible, and **[report the issue](https://github.com/danny-avila/LibreChat/issues)** with your logs found in `./api/logs/debug-%DATE%.log`. + +Errors logs are also saved in the same location: `./api/logs/error-%DATE%.log`. If you have meilisearch configured, there is a separate log file for this as well. + +> Note: Logs are rotated on a 14-day basis, so you will generate 1 error log file, 1 debug log file, and 1 meiliSync log file per 14 days. +> Errors will also be present in debug log files as well, but provide stack traces and more detail in the error log files. + +### Setup + +Toggle debug logs with the following environment variable. By default, even if you never set this variable, debug logs will be generated, but you have the option to disable them by setting it to `FALSE`. + +Note: it's recommended to disable debug logs in a production environment. + +```bash +DEBUG_LOGGING=TRUE +``` + +```bash +# in a production environment +DEBUG_LOGGING=FALSE +``` + +For verbose server output in the console/terminal, you can also set the following: + +```bash +DEBUG_CONSOLE=TRUE +``` + +This is not recommend, however, as the outputs can be quite verbose. It's disabled by default and should be enabled sparingly. \ No newline at end of file diff --git a/docs/features/manage_your_database.md b/docs/features/manage_your_database.md index dc3cc71128f..3de01e21cbc 100644 --- a/docs/features/manage_your_database.md +++ b/docs/features/manage_your_database.md @@ -1,3 +1,9 @@ +--- +title: 🍃 Manage Your Database +description: How to install and configure Mongo Express to securely access and manage your MongoDB database in Docker. +weight: -5 +--- + diff --git a/docs/features/mod_system.md b/docs/features/mod_system.md index 107c61cd355..099c5cb3a45 100644 --- a/docs/features/mod_system.md +++ b/docs/features/mod_system.md @@ -1,3 +1,8 @@ +--- +title: 🔨 Automated Moderation +description: The Automated Moderation System uses a scoring mechanism to track user violations. As users commit actions like excessive logins, registrations, or messaging, they accumulate violation scores. Upon reaching a set threshold, the user and their IP are temporarily banned. This system ensures platform security by monitoring and penalizing rapid or suspicious activities. +weight: -8 +--- ## Automated Moderation System (optional) The Automated Moderation System uses a scoring mechanism to track user violations. As users commit actions like excessive logins, registrations, or messaging, they accumulate violation scores. Upon reaching a set threshold, the user and their IP are temporarily banned. This system ensures platform security by monitoring and penalizing rapid or suspicious activities. @@ -29,7 +34,7 @@ The project's current rate limiters are as follows (see below under setup for de ### Setup -The following are all of the related env variables to make use of and configure the mod system. Note this is also found in the [/.env.example](/.env.example) file, to be set in your own `.env` file. +The following are all of the related env variables to make use of and configure the mod system. Note this is also found in the [/.env.example](https://github.com/danny-avila/LibreChat/blob/main/.env.example) file, to be set in your own `.env` file. ```bash BAN_VIOLATIONS=true # Whether or not to enable banning users for violations (they will still be logged) @@ -64,4 +69,29 @@ MESSAGE_IP_WINDOW=1 # in minutes, determines the window of time for MESSAGE_IP_M LIMIT_MESSAGE_USER=false # Whether to limit the amount of messages an IP can send per MESSAGE_USER_WINDOW MESSAGE_USER_MAX=40 # The max amount of messages an IP can send per MESSAGE_USER_WINDOW MESSAGE_USER_WINDOW=1 # in minutes, determines the window of time for MESSAGE_USER_MAX messages -``` \ No newline at end of file +``` + +## OpenAI moderation text + +### OPENAI_MODERATION +enable or disable OpenAI moderation + +Values: +`true`: OpenAI moderation is enabled +`false`: OpenAI moderation is disabled + +### OPENAI_MODERATION_API_KEY +Specify your OpenAI moderation API key here + +### OPENAI_MODERATION_REVERSE_PROXY +enable or disable reverse proxy compatibility for OpenAI moderation. Note that it may not work with some reverse proxies + +Values: +`true`: Enable reverse proxy compatibility +`false`: Disable reverse proxy compatibility + +```bash +OPENAI_MODERATION=true +OPENAI_MODERATION_API_KEY=sk-1234 +# OPENAI_MODERATION_REVERSE_PROXY=false +``` diff --git a/docs/features/pandoranext.md b/docs/features/pandoranext.md index 710da355a57..e639796e4d2 100644 --- a/docs/features/pandoranext.md +++ b/docs/features/pandoranext.md @@ -1,3 +1,9 @@ +--- +title: 📦 PandoraNext +description: How to deploy PandoraNext to enable the `CHATGPT_REVERSE_PROXY` for use with LibreChat. +weight: -3 +--- + # PandoraNext Deployment Guide If you're looking to use the `ChatGPT` Endpoint in LibreChat, setting up a reverse proxy is a essential. PandoraNext offers a robust solution for this purpose. This guide will walk you through deploying PandoraNext to enable the `CHATGPT_REVERSE_PROXY` for use with LibreChat. @@ -6,151 +12,158 @@ If you're looking to use the `ChatGPT` Endpoint in LibreChat, setting up a rever You can use it locally in docker or deploy it onthe web for remote access. -## Deploy Online by Duplicating Hugging Face Space - -To deploy PandoraNext online by duplicating the Hugging Face Space, follow these steps: - -1. Get your PandoraNext license id here: [PandoraNext Dashboard](https://dash.pandoranext.com/) - -2. **Configure `config.json`:** - Edit the following `config.json`. Specify your `license_id` and `proxy_api_prefix`. For the `proxy_api_prefix`, use at least 8 characters, avoid characters that can't be used in a URL and make sure it's unique. - - Here's the `config.json` for your reference: - - ```json - { - "bind": "0.0.0.0:8181", - "tls": { - "enabled": false, - "cert_file": "", - "key_file": "" - }, - "timeout": 600, - "proxy_url": "", - "license_id": "", - "public_share": false, - "site_password": "", - "setup_password": "", - "server_tokens": true, - "proxy_api_prefix": "", - "isolated_conv_title": "*", - "captcha": { - "provider": "", - "site_key": "", - "site_secret": "", - "site_login": false, - "setup_login": false, - "oai_username": false, - "oai_password": false - }, - "whitelist": null - } - ``` - -3. **Hugging Face Space:** - Visit the [PandoraNext LibreChat Space](https://huggingface.co/spaces/LibreChat/PandoraNext) on Hugging Face. - -4. **Duplicate the Space:** - Utilize the available options to duplicate or fork the space into your own Hugging Face account. - -5. **Fill the required secrets** - When asked for the `SECRETS`, - - for `CONFIG_JSON` use the whole content of the `config.json` you just modified, - - for `TOKENS_JSON` use the following default `token.json`: - ```json - { - "test-1": { - "token": "access token / session token / refresh token", - "shared": true, - "show_user_info": false - }, - "test-2": { - "token": "access token / session token / refresh token", - "shared": true, - "show_user_info": true, - "plus": true - }, - "test2": { - "token": "access token / session token / refresh token / share token", - "password": "12345" - } - } - ``` -6. **Configure LibreChat:** - In the .env file (or secrets settings if you host LibreChat on Hugging Face), set the `CHATGPT_REVERSE_PROXY` variable using the following format: - - ```bash - CHATGPT_REVERSE_PROXY=http://your_server_domain.com/your_proxy_api_prefix_here/backend-api/conversation - ``` - - - Replace `your_server_domain.com` with the domain of your deployed space. - - you can use this format: `https://username-pandoranext.hf.space` (replace `username` with your Huggingface username) - - Replace `your_proxy_api_prefix_here` with the `proxy_api_prefix` you have set in your `config.json`. - - The resulting URL should look similar to: - `https://username-pandoranext.hf.space/your_proxy_api_prefix_here/backend-api/conversation` +--- ## Deploy Locally Using Docker For local deployment using Docker, the steps are as follows: -1. **Clone or Download the Repository:** - Get the latest release from the [PandoraNext GitHub repository](https://github.com/pandora-next/deploy). - - ```bash - git clone https://github.com/pandora-next/deploy.git - ``` +### 1. **Clone or Download the Repository:** +Get the latest release from the [PandoraNext GitHub repository](https://github.com/pandora-next/deploy). + +```bash +git clone https://github.com/pandora-next/deploy.git +``` + +### 2. Get your PandoraNext `License ID` +Visit the **[PandoraNext Dashboard](https://dash.pandoranext.com/)** to get your `license ID` + +### 3. **Configure `config.json`:** +Within the cloned repository, in the `data` folder, edit `config.json`. Specify your `license_id` and `proxy_api_prefix`. For the `proxy_api_prefix`, use at least 8 characters, avoid characters that can't be used in a URL and make sure it's unique. + +Here's the `config.json` for your reference: + +```json +{ +"bind": "0.0.0.0:8181", +"tls": { + "enabled": false, + "cert_file": "", + "key_file": "" +}, +"timeout": 600, +"proxy_url": "", +"license_id": "", +"public_share": false, +"site_password": "", +"setup_password": "", +"server_tokens": true, +"proxy_api_prefix": "", +"isolated_conv_title": "*", +"captcha": { + "provider": "", + "site_key": "", + "site_secret": "", + "site_login": false, + "setup_login": false, + "oai_username": false, + "oai_password": false +}, +"whitelist": null +} +``` + +### 4. **Set Up the LibreChat `.env` Filer:** +In the `.env` file within your LibreChat directory, you'll need to set the `CHATGPT_REVERSE_PROXY` variable: + +```bash +CHATGPT_REVERSE_PROXY=http://host.docker.internal:8181/your_proxy_api_prefix_here/backend-api/conversation +``` +- Replace `your_proxy_api_prefix_here` with the actual proxy API prefix. + +### 5. **Start Docker Containers:** +From the PandoraNext directory, run the following command to launch the Docker containers: + +```bash +docker-compose up -d +``` + +--- + +## Deploy Online on Hugging Face -2. Get your PandoraNext license id here: [PandoraNext Dashboard](https://dash.pandoranext.com/) - -3. **Configure `config.json`:** - Within the cloned repository, in the `data` folder, edit `config.json`. Specify your `license_id` and `proxy_api_prefix`. For the `proxy_api_prefix`, use at least 8 characters, avoid characters that can't be used in a URL and make sure it's unique. - - Here's the `config.json` for your reference: +To deploy PandoraNext online by duplicating the Hugging Face Space, follow these steps: - ```json - { - "bind": "0.0.0.0:8181", - "tls": { - "enabled": false, - "cert_file": "", - "key_file": "" +### 1. Get your PandoraNext `License ID` +Visit the **[PandoraNext Dashboard](https://dash.pandoranext.com/)** to get your `license ID` + +### 2. **Configure `config.json`:** +Edit the following `config.json`. Specify your `license_id` and `proxy_api_prefix`. For the `proxy_api_prefix`, use at least 8 characters, avoid characters that can't be used in a URL and make sure it's unique. + +Here's the `config.json` for your reference: + +```json +{ +"bind": "0.0.0.0:8181", +"tls": { + "enabled": false, + "cert_file": "", + "key_file": "" +}, +"timeout": 600, +"proxy_url": "", +"license_id": "", +"public_share": false, +"site_password": "", +"setup_password": "", +"server_tokens": true, +"proxy_api_prefix": "", +"isolated_conv_title": "*", +"captcha": { + "provider": "", + "site_key": "", + "site_secret": "", + "site_login": false, + "setup_login": false, + "oai_username": false, + "oai_password": false +}, +"whitelist": null +} +``` + +### 3. **Hugging Face Space:** +Visit the [PandoraNext LibreChat Space](https://huggingface.co/spaces/LibreChat/PandoraNext) on Hugging Face. + +### 4. **Duplicate the Space:** +Utilize the available options to duplicate or fork the space into your own Hugging Face account. + +### 5. **Fill the required secrets** +When asked for the `SECRETS`, +- for `CONFIG_JSON` use the whole content of the `config.json` you just modified, +- for `TOKENS_JSON` use the following default `token.json`: +```json +{ + "test-1": { + "token": "access token / session token / refresh token", + "shared": true, + "show_user_info": false }, - "timeout": 600, - "proxy_url": "", - "license_id": "", - "public_share": false, - "site_password": "", - "setup_password": "", - "server_tokens": true, - "proxy_api_prefix": "", - "isolated_conv_title": "*", - "captcha": { - "provider": "", - "site_key": "", - "site_secret": "", - "site_login": false, - "setup_login": false, - "oai_username": false, - "oai_password": false + "test-2": { + "token": "access token / session token / refresh token", + "shared": true, + "show_user_info": true, + "plus": true }, - "whitelist": null + "test2": { + "token": "access token / session token / refresh token / share token", + "password": "12345" } - ``` - -4. **Set Up the LibreChat `.env` Filer:** - In the `.env` file within your LibreChat directory, you'll need to set the `CHATGPT_REVERSE_PROXY` variable: +} +``` - ```bash - CHATGPT_REVERSE_PROXY=http://host.docker.internal:8181/your_proxy_api_prefix_here/backend-api/conversation - ``` - - Replace `your_proxy_api_prefix_here` with the actual proxy API prefix. +### 6. **Configure LibreChat:** +In the .env file (or secrets settings if you host LibreChat on Hugging Face), set the `CHATGPT_REVERSE_PROXY` variable using the following format: -4. **Start Docker Containers:** - From the PandoraNext directory, run the following command to launch the Docker containers: +```bash +CHATGPT_REVERSE_PROXY=http://your_server_domain.com/your_proxy_api_prefix_here/backend-api/conversation +``` - ```bash - docker-compose up -d - ``` +- Replace `your_server_domain.com` with the domain of your deployed space. + - you can use this format: `https://username-pandoranext.hf.space` (replace `username` with your Huggingface username) +- Replace `your_proxy_api_prefix_here` with the `proxy_api_prefix` you have set in your `config.json`. +- The resulting URL should look similar to: +`https://username-pandoranext.hf.space/your_proxy_api_prefix_here/backend-api/conversation` ## Final Notes diff --git a/docs/features/plugins/azure_ai_search.md b/docs/features/plugins/azure_ai_search.md index 865180c632d..0e874204532 100644 --- a/docs/features/plugins/azure_ai_search.md +++ b/docs/features/plugins/azure_ai_search.md @@ -1,3 +1,8 @@ +--- +title: ⚡ Azure AI Search +description: How to configure Azure AI Search for answers to your questions with assistance from GPT. +weight: -4 +--- # Azure AI Search Plugin Through the plugins endpoint, you can use Azure AI Search for answers to your questions with assistance from GPT. @@ -30,7 +35,7 @@ This is the authentication key to use when utilizing the search endpoint. Please ## Create or log in to your account on Azure Portal -**1.** Visit [https://azure.microsoft.com/en-us/](https://azure.microsoft.com/en-us/) and click on `Get started` or `Try Azure for Free` to create an account and sign in. +**1.** Visit **[https://azure.microsoft.com/en-us/](https://azure.microsoft.com/en-us/)** and click on `Get started` or `Try Azure for Free` to create an account and sign in. **2.** Choose pay per use or Azure Free with $200. @@ -72,7 +77,7 @@ Now select the free option or select your preferred option (may incur charges). ![image](https://github.com/itzraiss/images/blob/main/Captura%20de%20tela%202023-11-26%20152107.png) -**2.** Follow the Microsoft tutorial.[https://learn.microsoft.com/en-us/azure/search/search-get-started-portal](https://learn.microsoft.com/en-us/azure/search/search-get-started-portal), after finishing, save the name given to the index somewhere. +**2.** Follow the Microsoft tutorial: **[https://learn.microsoft.com/en-us/azure/search/search-get-started-portal](https://learn.microsoft.com/en-us/azure/search/search-get-started-portal)**, after finishing, save the name given to the index somewhere. **3.** Now you have your `AZURE_AI_SEARCH_INDEX_NAME`, copy and save it in a local safe place. @@ -113,7 +118,7 @@ The following are configuration values that are not required but can be specifie If there are concerns that the search result data may be too large and exceed the prompt size, consider reducing the size of the search result data by using AZURE_AI_SEARCH_SEARCH_OPTION_TOP and AZURE_AI_SEARCH_SEARCH_OPTION_SELECT. For details on each parameter, please refer to the following document: -https://learn.microsoft.com/en-us/rest/api/searchservice/search-documents +**[https://learn.microsoft.com/en-us/rest/api/searchservice/search-documents](https://learn.microsoft.com/en-us/rest/api/searchservice/search-documents)** ```env AZURE_AI_SEARCH_API_VERSION=2023-10-01-Preview diff --git a/docs/features/plugins/chatgpt_plugins_openapi.md b/docs/features/plugins/chatgpt_plugins_openapi.md index 21d4800922b..7bf8a04a0e4 100644 --- a/docs/features/plugins/chatgpt_plugins_openapi.md +++ b/docs/features/plugins/chatgpt_plugins_openapi.md @@ -1,21 +1,30 @@ +--- +title: 🧑‍💼 Official ChatGPT Plugins +description: How to add official OpenAI Plugins to LibreChat +weight: -8 +--- # Using official ChatGPT Plugins / OpenAPI specs ChatGPT plugins are API integrations for OpenAI models that extend their capabilities. They are structured around three key components: an API, an **OpenAPI specification** (spec for short), and a JSON **Plugin Manifest** file. -To learn more about them, or how to make your own, read here: [ChatGPT Plugins: Getting Started](https://platform.openai.com/docs/plugins/getting-started). - -Thanks to the introduction of [OpenAI Functions](https://openai.com/blog/function-calling-and-other-api-updates) and their utilization in [Langchain](https://js.langchain.com/docs/modules/chains/openai_functions/openapi), it's now possible to directly use OpenAI Plugins through LibreChat, without building any custom langchain tools. The main use case we gain from integrating them to LibreChat is to allow use of plugins with gpt-3.5 models, and without ChatGPT Plus. They also find a great use case when you want to limit your own private API's interactions with chat.openai.com and their servers in favor of a self-hosted LibreChat instance. - -### Table of Contents -- [Intro](#intro) -- [Adding a Plugin](#adding-a-plugin) -- [Editing Manifest Files](#editing-manifest-files) - - [Override Parameter Values](#override-parameter-values) - - [Add Header Fields](#add-header-fields) - - [Custom OpenAPI Spec files](#custom-openapi-spec-files) - - [Plugins with Authentication](#plugins-with-authentication) -- [Showcase](#showcase) -- [Disclaimers](#disclaimers) +To learn more about them, or how to make your own, read here: **[ChatGPT Plugins: Getting Started](https://platform.openai.com/docs/plugins/getting-started)** + +Thanks to the introduction of **[OpenAI Functions](https://openai.com/blog/function-calling-and-other-api-updates)** and their utilization in **[Langchain](https://js.langchain.com/docs/modules/chains/openai_functions/openapi)**, it's now possible to directly use OpenAI Plugins through LibreChat, without building any custom langchain tools. The main use case we gain from integrating them to LibreChat is to allow use of plugins with gpt-3.5 models, and without ChatGPT Plus. They also find a great use case when you want to limit your own private API's interactions with chat.openai.com and their servers in favor of a self-hosted LibreChat instance. + + ## Intro @@ -24,11 +33,11 @@ Before continuing, it's important to fully distinguish what a Manifest file is v ### **[Plugin Manifest File:](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest)** - Usually hosted on the API’s domain as `https://example.com/.well-known/ai-plugin.json` - The manifest file is required for LLMs to connect with your plugin. If there is no file found, the plugin cannot be installed. -- Has required properties, and will error if they are missing. Check what they are in the [OpenAI Docs](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest) +- Has required properties, and will error if they are missing. Check what they are in the **[OpenAI Docs](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest)** - Has optional properties, specific to LibreChat, that will enable them to work consistently, or for customizing headers/params made by every API call (see below) ### **[OpenAPI Spec](https://platform.openai.com/docs/plugins/getting-started/openapi-definition)** -- The OpenAPI specification is used to document the API that the plugin will interact with. It is a [universal format](https://www.openapis.org/) meant to standardize API definitions. +- The OpenAPI specification is used to document the API that the plugin will interact with. It is a **[universal format](https://www.openapis.org/)** meant to standardize API definitions. - Referenced by the Manifest file in its `api.url` property - Usually as `https://example.com/openapi.yaml` or `.../swagger.yaml` - Can be a .yaml or .json file @@ -45,7 +54,7 @@ Download the Plugin manifest file, or copy the raw JSON data into a new file, an `api\app\clients\tools\.well-known` -You should see multiple manifest files that have been tested, or edited, to work with LibreChat. ~~I've renamed them by their `name_for_model` property and it's recommended, but not required, that you do the same.~~ As of v0.5.8, It's **required** to name the manifest JSON file after its `name_for_model` property should you add one yourself. +You should see multiple manifest files that have been tested, or edited, to work with LibreChat. As of v0.5.8, It's **required** to name the manifest JSON file after its `name_for_model` property should you add one yourself. After doing so, start/re-start the project server and they should now load in the Plugin store. @@ -144,7 +153,7 @@ curl -H "Authorization: Bearer ffc5226d1af346c08a98dee7deec9f76" https://example As of now, LibreChat only supports plugins using Bearer Authentication, like in the example above. -If your plugin requires authentication, it's necessary to have these fields filled in your manifest file according to [OpenAI definitions](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest), which for Bearer Authentication must follow the schema above. +If your plugin requires authentication, it's necessary to have these fields filled in your manifest file according to **[OpenAI definitions](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest)**, which for Bearer Authentication must follow the schema above. Important: Some ChatGPT plugins may use Bearer Auth., but have either stale verification tokens in their manifest, or only support calls from OpenAI servers. Web Pilot is one with the latter case, and thankfully it has a required header field for allowing non-OpenAI origination. See above for editing headers. @@ -160,16 +169,16 @@ Important: Some ChatGPT plugins may use Bearer Auth., but have either stale veri ## Disclaimers -Use of ChatGPT Plugins is only possible with official OpenAI models and their use of [Functions](https://platform.openai.com/docs/api-reference/chat/create#chat/create-functions). If you are accessing OpenAI models via reverse proxy through some 3rd party service, function calling may not be supported. +Use of ChatGPT Plugins is only possible with official OpenAI models and their use of **[Functions](https://platform.openai.com/docs/api-reference/chat/create#chat/create-functions)**. If you are accessing OpenAI models via reverse proxy through some 3rd party service, function calling may not be supported. -This implementation depends on the [LangChain OpenAPI Chain](https://js.langchain.com/docs/modules/chains/openai_functions/openapi) and general improvements to its use here will have to be made to the LangChainJS library. +This implementation depends on the **[LangChain OpenAPI Chain](https://js.langchain.com/docs/modules/chains/openai_functions/openapi)** and general improvements to its use here will have to be made to the LangChainJS library. Custom Langchain Tools are preferred over ChatGPT Plugins/OpenAPI specs as this can be more token-efficient, especially with OpenAI Functions. A better alternative may be to make a Langchain tool modelled after an OpenAPI spec, for which I'll make a guide soon. LibreChat's implementation is not 1:1 with ChatGPT's, as OpenAI has a robust, exclusive, and restricted authentication pipeline with its models & specific plugins, which are not as limited by context windows and token usage. Furthermore, some of their hosted plugins requiring authentication will not work, especially those with OAuth or stale verification tokens, and some may not be handled by the LLM in the same manner, especially those requiring multi-step API calls. -Some plugins may detect that the API call does not originate from OpenAI's servers, will either be defunct outside of chat.openai.com or need special handling, and/or editing of their manifest/spec files. This is not to say plugin use will not improve and more closely mirror how ChatGPT handles plugins, but there is still work to this end. In short, some will work perfectly while others may not work at all. +Some plugins may detect that the API call does not originate from OpenAI's servers, will either be defunct outside of **[chat.openai.com](https://chat.openai.com/)** or need special handling, and/or editing of their manifest/spec files. This is not to say plugin use will not improve and more closely mirror how ChatGPT handles plugins, but there is still work to this end. In short, some will work perfectly while others may not work at all. -The use of ChatGPT Plugins with LibreChat does not violate OpenAI's [Terms of Service](https://openai.com/policies/terms-of-use). According to their [Service Terms](https://openai.com/policies/service-terms) and [Usage Policies](https://openai.com/policies/usage-policies), the host, in this case OpenAI, is not responsible for the plugins hosted on their site and their usage outside of their platform, chat.openai.com. Furthermore, there is no explicit mention of restrictions on accessing data that is not directly displayed to the user. Therefore, accessing the payload of their plugins for display purposes is not in violation of their Terms of Service. +The use of ChatGPT Plugins with LibreChat does not violate OpenAI's **[Terms of Service](https://openai.com/policies/terms-of-use)**. According to their **[Service Terms](https://openai.com/policies/service-terms)** and **[Usage Policies](https://openai.com/policies/usage-policies)**, the host, in this case OpenAI, is not responsible for the plugins hosted on their site and their usage outside of their platform, **[chat.openai.com](https://chat.openai.com/)**. Furthermore, there is no explicit mention of restrictions on accessing data that is not directly displayed to the user. Therefore, accessing the payload of their plugins for display purposes is not in violation of their Terms of Service. -Please note that the ChatGPT Plugins integration is currently in an alpha state, and you may encounter errors. Although preliminary testing has been conducted, not all plugins have been thoroughly tested, and you may find that some I haven't added will not work for any one of the reasons I've mentioned above. Some of the errors may be caused by the plugin itself, and will also not work on https://chat.openai.com/. If you encounter any errors, double checking if they work on the official site is advisable before reporting them as a GitHub issue. I can only speak for the ones I tested and included, and the date of inclusion. +Please note that the ChatGPT Plugins integration is currently in an alpha state, and you may encounter errors. Although preliminary testing has been conducted, not all plugins have been thoroughly tested, and you may find that some I haven't added will not work for any one of the reasons I've mentioned above. Some of the errors may be caused by the plugin itself, and will also not work on **[chat.openai.com](https://chat.openai.com/)**. If you encounter any errors, double checking if they work on the official site is advisable before reporting them as a GitHub issue. I can only speak for the ones I tested and included, and the date of inclusion. diff --git a/docs/features/plugins/google_search.md b/docs/features/plugins/google_search.md index a412bc628f5..0b575414f2c 100644 --- a/docs/features/plugins/google_search.md +++ b/docs/features/plugins/google_search.md @@ -1,3 +1,9 @@ +--- +title: 🔎 Google Search +description: How to set up and use the Google Search Plugin, which allows you to query Google with GPT's help. +weight: -7 +--- + # Google Search Plugin Through the plugins endpoint, you can use google search for answers to your questions with assistance from GPT! To get started, you need to get a Google Custom Search API key, and a Google Custom Search Engine ID. You can then define these as follows in your `.env` file: ```env @@ -5,9 +11,9 @@ GOOGLE_API_KEY="...." GOOGLE_CSE_ID="...." ``` -You first need to create a programmable search engine and get the search engine ID: https://developers.google.com/custom-search/docs/tutorial/creatingcse +You first need to create a programmable search engine and get the search engine ID: **[https://developers.google.com/custom-search/docs/tutorial/creatingcse](https://developers.google.com/custom-search/docs/tutorial/creatingcse)** -Then you can get the API key, click the "Get a key" button on this page: https://developers.google.com/custom-search/v1/introduction +Then you can get the API key, click the "Get a key" button on this page: **[https://developers.google.com/custom-search/v1/introduction](https://developers.google.com/custom-search/v1/introduction)** diff --git a/docs/features/plugins/index.md b/docs/features/plugins/index.md new file mode 100644 index 00000000000..7eb2d3f1539 --- /dev/null +++ b/docs/features/plugins/index.md @@ -0,0 +1,14 @@ +--- +title: Plugins +description: 🔌 All about plugins, how to make them, how use the official ChatGPT plugins, and how to configure custom plugins. +weight: -10 +--- + +# Plugins +* 🔌 [Introduction](./introduction.md) +* 🛠️ [Make Your Own](./make_your_own.md) +* 🧑‍💼 [Official ChatGPT Plugins](./chatgpt_plugins_openapi.md) +* 🔎 [Google Search](./google_search.md) +* 🖌️ [Stable Diffusion](./stable_diffusion.md) +* 🧠 [Wolfram|Alpha](./wolfram.md) +* ⚡ [Azure AI Search](./azure_ai_search.md) \ No newline at end of file diff --git a/docs/features/plugins/introduction.md b/docs/features/plugins/introduction.md index 3f1bd376c02..f5533c82bf1 100644 --- a/docs/features/plugins/introduction.md +++ b/docs/features/plugins/introduction.md @@ -1,34 +1,34 @@ +--- +title: 🔌 Introduction +description: This doc introduces the plugins endpoint, which enables you to use different LLMs and tools with more flexibility and control. You can change your settings and plugins on the fly, and use plugins to access various sources of information and assistance. +weight: -10 +--- # Plugins Endpoint -![introduction-1](https://github.com/danny-avila/LibreChat/assets/32828263/7e426681-2bef-4dfc-9b4e-0cf2c09cb1d5) +![intro-1](https://github.com/danny-avila/LibreChat/assets/32828263/7db788a5-2173-4115-b34b-43ea132dae69) + The plugins endpoint opens the door to prompting LLMs in new ways other than traditional input/output prompting. -The first step is using chain-of-thought prompting & ["agency"](https://zapier.com/blog/ai-agent/) for using plugins/tools in a fashion mimicing the official ChatGPT Plugins feature. +The first step is using chain-of-thought prompting & **["agency"](https://zapier.com/blog/ai-agent/)** for using plugins/tools in a fashion mimicing the official ChatGPT Plugins feature. More than this, you can use this endpoint for changing your conversation settings mid-conversation. Unlike the official ChatGPT site and all other endpoints, you can switch models, presets, and settings mid-convo, even when you have no plugins selected. This is useful if you first want a creative response from GPT-4, and then a deterministic, lower cost response from GPT-3. Soon, you will be able to use Google, HuggingFace, local models, all in this or a similar endpoint in the same modular manner. -### Roadmap: -- More plugins and advanced plugin usage (ongoing) -- **[ChatGPT Plugins/OpenAPI Specs (complete)](./chatgpt_plugins_openapi.md)** -- More LLMs to choose from for both Thinking and Completion Phases -- Alternative prompting methods such as Tree-of-Thought - ## Using Plugins The LLM process when using Plugins is illustrated below. -![introduction-2](https://github.com/danny-avila/LibreChat/assets/32828263/789406e1-7345-43d2-823b-8aed0588bb78) +![intro-2](https://github.com/danny-avila/LibreChat/assets/32828263/789406e1-7345-43d2-823b-8aed0588bb78) **When you open the settings with the Plugins endpoint selected, you will view the default settings for the Completion Phase.** Clicking on **"Show Agent Settings"** will allow you to modify parameters for the thinking phase -![introduction-3](https://github.com/danny-avila/LibreChat/assets/32828263/f3cf33d0-701f-409d-8ef6-f336993df55d) +![intro-3](https://github.com/danny-avila/LibreChat/assets/32828263/d9a43517-5b35-4786-a126-0adf62b5b087) --- -![introduction-4](https://github.com/danny-avila/LibreChat/assets/32828263/c6a33fb2-aa14-4a88-9467-9f2c429e6338) +![intro-4](https://github.com/danny-avila/LibreChat/assets/32828263/12a51feb-c030-4cf0-8429-16360270988d) - You can specify which plugins you would like to select from by installing/uninstalling them in the Plugin store - See this guide on how to create your own plugins (WIP) @@ -53,8 +53,8 @@ Clicking on **"Show Agent Settings"** will allow you to modify parameters for th - **[Stable Diffusion](./stable_diffusion.md)** - **[Wolfram](./wolfram.md)** - **DALL-E** - same setup as above, you just need an OpenAI key, and it's made distinct from your main API key to make Chats but it can be the same one -- **Zapier** - You need a Zapier account. Get your [API key from here](https://nla.zapier.com/credentials/) after you've made an account - - Create allowed actions - Follow step 3 in this [getting start guide](https://nla.zapier.com/start/) from Zapier +- **Zapier** - You need a Zapier account. Get your **[API key from here](https://nla.zapier.com/credentials/)** after you've made an account + - Create allowed actions - Follow step 3 in this **[Start Here guide](https://nla.zapier.com/start/)** from Zapier - ⚠️ NOTE: zapier is known to be finicky with certain actions. I found that writing email drafts is probably the best use of it - there are improvements that can be made to override the official NLA integration and that is TBD - **Browser/Scraper** - This is not to be confused with 'browsing' on chat.openai.com (which is technically a plugin suite or multiple plugins) @@ -63,7 +63,7 @@ Clicking on **"Show Agent Settings"** will allow you to modify parameters for th - A better solution for 'browsing' is planned but can't guarantuee when - This plugin is best used in combination with google so it doesn't hallucinate webpages to visit - **Serpapi** - an alternative to Google search but not as performant in my opinion - - You can get an API key here: https://serpapi.com/dashboard + - You can get an API key here: **[https://serpapi.com/dashboard](https://serpapi.com/dashboard)** - For free tier, you are limited to 100 queries/month - With google, you are limited to 100/day for free, which is a better deal, and any after may cost you a few pennies diff --git a/docs/features/plugins/make_your_own.md b/docs/features/plugins/make_your_own.md index 565a7cf10eb..e6534e47aff 100644 --- a/docs/features/plugins/make_your_own.md +++ b/docs/features/plugins/make_your_own.md @@ -1,14 +1,19 @@ +--- +title: 🛠️ Make Your Own +description: This doc shows you how to create custom plugins for LibreChat by extending the LangChain `Tool` class. You will learn how to use different APIs and functions with your plugins, and how to integrate them with the LangChain framework. +weight: -9 +--- # Making your own Plugin Creating custom plugins for this project involves extending the `Tool` class from the `langchain/tools` module. **Note:** I will use the word plugin interchangeably with tool, as the latter is specific to LangChain, and we are mainly conforming to the library. -You are essentially creating DynamicTools in LangChain speak. See the [LangChainJS docs](https://js.langchain.com/docs/modules/agents/tools/dynamic) for more info. +You are essentially creating DynamicTools in LangChain speak. See the **[LangChainJS docs](https://js.langchain.com/docs/modules/agents/tools/dynamic)** for more info. This guide will walk you through the process of creating your own custom plugins, using the `StableDiffusionAPI` and `WolframAlphaAPI` tools as examples. -When using the Functions Agent (the default mode for plugins), tools are converted to [OpenAI functions](https://openai.com/blog/function-calling-and-other-api-updates); in any case, plugins/tools are invoked conditionally based on the LLM generating a specific format that we parse. +When using the Functions Agent (the default mode for plugins), tools are converted to **[OpenAI functions](https://openai.com/blog/function-calling-and-other-api-updates)**; in any case, plugins/tools are invoked conditionally based on the LLM generating a specific format that we parse. The most common implementation of a plugin is to make an API call based on the natural language input from the AI, but there is virtually no limit in programmatic use case. @@ -43,7 +48,7 @@ Remember, the key to creating a custom plugin is to extend the `Tool` class and **Multi-Input Plugins** -If you would like to make a plugin that would benefit from multiple inputs from the LLM, instead of a singular input string as we will review, you need to make a LangChain [StructuredTool](https://blog.langchain.dev/structured-tools/) instead. A detailed guide for this is in progress, but for now, you can look at how I've made StructuredTools in this directory: `api\app\clients\tools\structured\`. This guide is foundational to understanding StructuredTools, and it's recommended you continue reading to better understand LangChain tools first. The blog linked above is also helpful once you've read through this guide. +If you would like to make a plugin that would benefit from multiple inputs from the LLM, instead of a singular input string as we will review, you need to make a LangChain **[StructuredTool](https://blog.langchain.dev/structured-tools/)** instead. A detailed guide for this is in progress, but for now, you can look at how I've made StructuredTools in this directory: `api\app\clients\tools\structured\`. This guide is foundational to understanding StructuredTools, and it's recommended you continue reading to better understand LangChain tools first. The blog linked above is also helpful once you've read through this guide. --- @@ -127,7 +132,7 @@ class StableDiffusionAPI extends Tool { The `_call` method is where the main functionality of your plugin is implemented. This method is called when the language model decides to use your plugin. It should take an `input` parameter and return a result. -> In a basic Tool, the LLM will generate one string value as an input. If your plugin requires multiple inputs from the LLM, read the [StructuredTools](#StructuredTools) section. +> In a basic Tool, the LLM will generate one string value as an input. If your plugin requires multiple inputs from the LLM, read the **[StructuredTools](#StructuredTools)** section. ```javascript class StableDiffusionAPI extends Tool { diff --git a/docs/features/plugins/stable_diffusion.md b/docs/features/plugins/stable_diffusion.md index 8ae3bd78f17..e9734cc511f 100644 --- a/docs/features/plugins/stable_diffusion.md +++ b/docs/features/plugins/stable_diffusion.md @@ -1,12 +1,18 @@ +--- +title: 🖌️ Stable Diffusion +description: How to set up and configure the Stable Diffusion plugin +weight: -6 +--- + # Stable Diffusion Plugin -To use Stable Diffusion with this project, you will either need to download and install [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) or, for a dockerized deployment, you can also use [stable-diffusion-webui-docker](https://github.com/AbdBarho/stable-diffusion-webui-docker) +To use Stable Diffusion with this project, you will either need to download and install **[AUTOMATIC1111 - Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)** or, for a dockerized deployment, you can also use **[stable-diffusion-webui-docker](https://github.com/AbdBarho/stable-diffusion-webui-docker)** With the docker deployment you can skip step 2 and step 3, use the setup instructions from their repository instead. - Note: you need a compatible GPU ("CPU-only" is possible but very slow). Nvidia is recommended, but there is no clear resource on incompatible GPUs. Any decent GPU should work. -## 1. Follow download and installation instructions from [stable-diffusion-webui readme](https://github.com/AUTOMATIC1111/stable-diffusion-webui) +## 1. Follow download and installation instructions from **[stable-diffusion-webui readme](https://github.com/AUTOMATIC1111/stable-diffusion-webui)** ## 2. Edit your run script settings @@ -58,10 +64,10 @@ With the docker deployment you can skip step 2 and step 3, use the setup instruc ### Select the plugins endpoint -![plugins_endpoint](https://github.com/danny-avila/LibreChat/assets/32828263/9717e111-2e9e-43da-bd08-c5a0bbbc4d6d) +![plugins-endpoint](https://github.com/danny-avila/LibreChat/assets/32828263/7db788a5-2173-4115-b34b-43ea132dae69) ### Open the Plugin store and Install Stable Diffusion -![plugin_store](https://github.com/danny-avila/LibreChat/assets/32828263/add4d6a8-e5d6-4129-a334-4ff9290c0e2d) +![plugin_store](https://github.com/danny-avila/LibreChat/assets/32828263/12a51feb-c030-4cf0-8429-16360270988d) ![stable_diffusion-1](https://github.com/danny-avila/LibreChat/assets/32828263/b4364f41-0f7e-4197-af86-7d6061797366) diff --git a/docs/features/plugins/wolfram.md b/docs/features/plugins/wolfram.md index b00e8ff22ad..0237e2d515f 100644 --- a/docs/features/plugins/wolfram.md +++ b/docs/features/plugins/wolfram.md @@ -1,21 +1,32 @@ +--- +title: 🧠 Wolfram|Alpha +description: How to set up and configure the Wolfram Alpha plugin +weight: -5 +--- + # Wolfram Alpha Plugin An AppID must be supplied in all calls to the Wolfram|Alpha API. - Note: Wolfram API calls are limited to 100 calls/day and 2000/month for regular users. -## 1. Make an account at Wolfram|Alpha -## 2. Go to the Developer Portal click on "Get an AppID". -## 3. Configure it in LibreChat -### Select the plugins endpoint -![plugins_endpoint](https://github.com/danny-avila/LibreChat/assets/32828263/9717e111-2e9e-43da-bd08-c5a0bbbc4d6d) -### Open the Plugin store -![plugin_store](https://github.com/danny-avila/LibreChat/assets/32828263/add4d6a8-e5d6-4129-a334-4ff9290c0e2d) -### Install Wolfram and Provide your AppID +### Make an account +- Visit: **[products.wolframalpha.com/api/](https://products.wolframalpha.com/api/)** to create your account + +### Get your AppID +- Go to the **[Developer Portal](https://developer.wolframalpha.com/portal/myapps/)** click on `Get an AppID`. + +### Configure it in LibreChat +- Select the plugins endpoint +![plugins_endpoint](https://github.com/danny-avila/LibreChat/assets/32828263/7db788a5-2173-4115-b34b-43ea132dae69) +- Open the Plugin store +![plugin_store](https://github.com/danny-avila/LibreChat/assets/32828263/12a51feb-c030-4cf0-8429-16360270988d) +- Install Wolfram and Provide your AppID ![wolfram-1](https://github.com/danny-avila/LibreChat/assets/32828263/bd165497-d529-441d-8372-a68db19adc3f) -- Alternatively: you (the admin) can set the value in `\.env` to bypass the prompt: `WOLFRAM_APP_ID=your_app_id` +> Alternatively: you (the admin) can set the value in `\.env` to bypass the prompt: `WOLFRAM_APP_ID=your_app_id` + -## 5. Select the plugin and enjoy! +### Select the plugin and enjoy! ![wolfram-2](https://github.com/danny-avila/LibreChat/assets/32828263/2825e961-6c46-4728-96cd-1012a0862943) diff --git a/docs/features/presets.md b/docs/features/presets.md new file mode 100644 index 00000000000..3f26cd8411f --- /dev/null +++ b/docs/features/presets.md @@ -0,0 +1,92 @@ +--- +title: 🔖 Presets +description: The "presets" feature in our app is a powerful tool that allows users to save and load predefined settings for their conversations. Users can import and export these presets as JSON files, set a default preset, and share them with others on Discord. +weight: -9 +--- +# Guide to Using the "Presets" Feature + +The "presets" feature in our app is a powerful tool that allows users to save and load predefined settings for their conversations. Users can import and export these presets as JSON files, set a default preset, and share them with others on Discord. + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/8c39ad89-71ae-42c6-a792-3db52d539fcd) + +## Create a Preset: + +- Go in the model settings + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/2fc883e9-f4a3-47ac-b375-502e82234194) + +- Choose the model, give it a name, some custom instructions, and adjust the parameters if needed + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/090dc065-f9ea-4a43-9380-e6d504e64992) + +- Test it + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/8a383495-0d5e-4ab7-93a7-eca5388c3f6f) + +- Go back in the model advanced settings, and tweak it if needed. When you're happy with the result, click on `Save As Preset` (from the model advanced settings) + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/96fd88ec-b4b6-4de0-a7d7-f156fdace354) + +- Give it a proper name, and click save + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/76ad8db4-a949-4633-8a5f-f9e8358d57f3) + +- Now you can select it from the preset menu! + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/81271990-2739-4f5c-b1a5-7d7deeaa385c) + +## Parameters Explained: + +- **Preset Name:** + - This is where you name your preset for easy identification. + +- **Endpoint:** + - Choose the endpoint, such as openAI, that you want to use for processing the conversation. + +- **Model:** + - Select the model like `gpt-3.5-turbo` that will be used for generating responses. + +- **Custom Name:** + - Optionally provide a custom name for your preset. This is the name that will be shown in the UI when using it. + +- **Prompt Prefix:** + - Define instructions or guidelines that will be displayed before each prompt to guide the user in providing input. + +- **Temperature:** + - Adjust this parameter to control the randomness of the model's output. A higher value makes the output more random, while a lower value makes it more focused and deterministic. + +- **Top P:** + - Control the nucleus sampling parameter to influence the diversity of generated text. Lower values make text more focused while higher values increase diversity. + +- **Frequency Penalty:** + - Use this setting to penalize frequently occurring tokens and promote diversity in responses. + +- **Presence Penalty:** + - Adjust this parameter to penalize new tokens that are introduced into responses, controlling repetition and promoting consistency. + +## Importing/Exporting Presets + +You can easily import or export presets as JSON files by clicking on either 'Import' or 'Export' buttons respectively. This allows you to share your customized settings with others or switch between different configurations quickly. + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/b9ef56e2-393e-45eb-b72b-8d568a13a015) + +To export a preset, first go in the preset menu, then click on the button to edit the selected preset + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/3fb065e6-977b-49b4-9fc6-de55b9839031) + +Then in the bottom of the preset settings you'll have the option to export it. + +

+ +

+ +## Setting Default Preset + +Choose a preset as default so it loads automatically whenever you start a new conversation. This saves time if you often use specific settings. + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/5912650d-49b6-40d3-b9ad-ff2ff7fbe3e7) +![image](https://github.com/danny-avila/LibreChat/assets/32828263/dcfb5e27-f60b-419e-b387-25db85fa6a63) + +## Sharing on Discord + +Join us on [discord](https://discord.librechat.ai) and see our **[#presets ](https://discord.com/channels/1086345563026489514/1093249324797935746)** channel where thousands of presets are shared by users worldwide. Check out pinned posts for popular presets! \ No newline at end of file diff --git a/docs/features/proxy.md b/docs/features/proxy.md deleted file mode 100644 index 586fbe58db9..00000000000 --- a/docs/features/proxy.md +++ /dev/null @@ -1,32 +0,0 @@ -# Proxy - -If your server cannot connect to the chatGPT API server by some reason, (eg in China). You can set a environment variable `PROXY`. This will be transmitted to `node-chatgpt-api` interface. - -**Warning:** `PROXY` is not `reverseProxyUrl` in `node-chatgpt-api` - -## Set up proxy in local environment - -- **Option 1:** system level environment -`export PROXY="http://127.0.0.1:7890"` - -- **Option 2:** set in .env file -`PROXY="http://127.0.0.1:7890"` - -**Change `http://127.0.0.1:7890` to your proxy server** - - -## Set up proxy in docker environment - -set in docker-compose.yml file, under services - api - environment - -``` - api: - ... - environment: - ... - - "PROXY=http://127.0.0.1:7890" - # add this line ↑ -``` - -**Change `http://127.0.0.1:7890` to your proxy server** - diff --git a/docs/features/third_party.md b/docs/features/third_party.md index 888ac190ec6..3161e440402 100644 --- a/docs/features/third_party.md +++ b/docs/features/third_party.md @@ -1,3 +1,9 @@ +--- +title: ✨ Third-Party Tools and Contributions +description: Collection of third-party tools provided by the community +weight: -2 +--- + # Third-Party Tools > ⚠️ Warning: The tools featured here are not officially maintained or supported by the LibreChat team diff --git a/docs/features/token_usage.md b/docs/features/token_usage.md index e04ed3ca6be..0dfc835309c 100644 --- a/docs/features/token_usage.md +++ b/docs/features/token_usage.md @@ -1,3 +1,8 @@ +--- +title: 🪙 Token Usage +description: This doc covers how to track and control your token usage for the OpenAI/Plugins endpoints in LibreChat. You will learn how to view your transactions, enable user balances, and add credits to your account. +weight: -7 +--- # Token Usage As of v6.0.0, LibreChat accurately tracks token usage for the OpenAI/Plugins endpoints. @@ -25,6 +30,14 @@ npm run add-balance danny@librechat.ai 1000 This works well to track your own usage for personal use; 1000 credits = $0.001 (1 mill USD) +## Listing of balances + +To see the balances of your users, you can run: + +```bash +npm run list-balances +``` + ## Notes - With summarization enabled, you will be blocked from making an API request if the cost of the content that you need to summarize + your messages payload exceeds the current balance diff --git a/docs/general_info/breaking_changes.md b/docs/general_info/breaking_changes.md index b46bc2dfaa7..9991d42f6ff 100644 --- a/docs/general_info/breaking_changes.md +++ b/docs/general_info/breaking_changes.md @@ -1,4 +1,9 @@ -# ⚠️ **Breaking Changes** ⚠️ +--- +title: ⚠️ Breaking Changes +description: This doc lists the breaking changes that affect the functionality and compatibility of LibreChat. You should read this doc before updating to a new version of LibreChat, and follow the instructions to resolve any issues. +weight: -10 +--- +# ⚠️ Breaking Changes > **Note:** **If you experience any issues after updating, we recommend clearing your browser cache and cookies.** @@ -20,7 +25,7 @@ Certain changes in the updates may impact cookies, leading to unexpected behavio ## v0.5.8 -- It's now required to name manifest JSON files (for [ChatGPT Plugins](..\features\plugins\chatgpt_plugins_openapi.md)) in the `api\app\clients\tools\.well-known` directory after their `name_for_model` property should you add one yourself. +- It's now required to name manifest JSON files (for [ChatGPT Plugins](../features/plugins/chatgpt_plugins_openapi.md)) in the `api\app\clients\tools\.well-known` directory after their `name_for_model` property should you add one yourself. - This was a recommended convention before, but is now required. ## v0.5.7 @@ -120,7 +125,7 @@ I had to change the environment variable from AZURE_OPENAI_API_KEY to AZURE_API_ --- ### Docker -- The docker-compose file had some change. Review the [new docker instructions](../install/docker_compose_install.md) to make sure you are setup properly. This is still the simplest and most effective method. +- The docker-compose file had some change. Review the [new docker instructions](../install/installation/docker_compose_install.md) to make sure you are setup properly. This is still the simplest and most effective method. --- diff --git a/docs/general_info/index.md b/docs/general_info/index.md new file mode 100644 index 00000000000..1addb356e70 --- /dev/null +++ b/docs/general_info/index.md @@ -0,0 +1,13 @@ +--- +title: General Information +description: 📜 This section contains information about LibreChat, such as its history, purpose, and values. You will also find the details of the tech stack, the code of conduct, and the breaking changes that affect the project. +weight: 4 +--- + +# General Information + + * ⚠️ [Breaking Changes](./breaking_changes.md) + * 👮 [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/.github/CODE_OF_CONDUCT.md) + * 🌍 [Multilingual Information](multilingual_information.md) + * 🧭 [Origin](project_origin.md) + * 🧑‍💻 [Tech Stack](tech_stack.md) \ No newline at end of file diff --git a/docs/general_info/multilingual_information.md b/docs/general_info/multilingual_information.md index 13ae755365f..5b51c4eea57 100644 --- a/docs/general_info/multilingual_information.md +++ b/docs/general_info/multilingual_information.md @@ -1,3 +1,8 @@ +--- +title: 🌍 Multilingual Information +description: To set up the project, please follow the instructions in the documentation. The documentation is in English only, so you may need to use a translation tool or an AI assistant (e.g. ChatGPT) if you have difficulty understanding it. +weight: -9 +--- # Multilingual Information To set up the project, please follow the instructions in the documentation. The documentation is in English only, so you may need to use a translation tool or an AI assistant (e.g. ChatGPT) if you have difficulty understanding it. diff --git a/docs/general_info/project_origin.md b/docs/general_info/project_origin.md index d15244cda72..d1dc673238f 100644 --- a/docs/general_info/project_origin.md +++ b/docs/general_info/project_origin.md @@ -1,3 +1,8 @@ +--- +title: 🧭 Origin +description: How it all started... +weight: -8 +--- # Origin This project was started early in Feb '23, anticipating the release of the official ChatGPT API from OpenAI, which is now used. It was originally created as a Minimum Viable Product (or MVP) for the [@HackReactor](https://github.com/hackreactor/) Bootcamp. It was built with OpenAI response streaming and most of the UI completed in under 20 hours. During the end of that time, I had most of the UI and basic functionality done. This was created without using any boilerplates or templates, including create-react-app and other toolchains. I didn't follow any 'un-official chatgpt' video tutorials, and simply referenced the official site for the UI. The purpose of the exercise was to learn setting up a full stack project from scratch. diff --git a/docs/general_info/tech_stack.md b/docs/general_info/tech_stack.md index 2a4f38d96a4..acb0100b5bf 100644 --- a/docs/general_info/tech_stack.md +++ b/docs/general_info/tech_stack.md @@ -1,9 +1,37 @@ +--- +title: 🧑‍💻 Tech Stack +description: This doc describes the technologies and frameworks that LibreChat uses. +weight: -8 +--- # Tech Stack ## This project uses: -- [node-chatgpt-api](https://github.com/waylaidwanderer/node-chatgpt-api) -- No React boilerplate/toolchain/clone tutorials, created from scratch with react@latest -- Use of Tailwind CSS and [shadcn/ui](https://github.com/shadcn/ui) components -- Docker, useSWR, Redux, Express, MongoDB, [Keyv](https://www.npmjs.com/package/keyv) +- JavaScript/TypeScript: The project was initially developed entirely in JavaScript (JS). The frontend is in the process of transitioning from JS to TypeScript (TS). The backend is currently in JS, and there are considerations for transitioning it to TS in the future. + +- React: The frontend UI is built using React. + +- Express.js: The backend server is built using Express.js. + +- OpenAI API: The project uses the official ChatGPT API from OpenAI. + +- Docker: Docker is used for containerization of the application. + +- MongoDB: MongoDB is used as the database for the application. + +- npm: npm is used as the package manager. + +- Git: Git is used for version control, following a GitFlow workflow. + +- ESLint: ESLint is used for linting the codebase. + +- Husky: Husky is used for pre-commit checks. + +- Playwright: Playwright is used for running integration tests. + +- GitHub: GitHub is used for hosting the codebase and managing contributions. + +- Discord: Discord is used for community engagement and discussions. + +- Various Cloud Deployment Options: The project supports deployment on multiple cloud platforms including DigitalOcean, Azure, Linode, Cloudflare, Ngrok, HuggingFace, and Render. diff --git a/docs/index.md b/docs/index.md index 7dbbd55c806..3abf481c36d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,9 @@ +--- +title: Home +description: 🪶 Introducing LibreChat +weight: -10 +--- +

Star History Chart @@ -78,7 +79,7 @@ With LibreChat, you no longer need to opt for ChatGPT Plus and can instead use f --- -## Contributors +## ✨ Contributors Contributions and suggestions bug reports and fixes are welcome! Please read the documentation before you do! @@ -86,7 +87,7 @@ For new features, components, or extensions, please open an issue and discuss be - Join the [Discord community](https://discord.gg/uDyZ5Tzhct) -This project exists in its current state thanks to all the people who contribute +## 💖 This project exists in its current state thanks to all the people who contribute --- diff --git a/docs/install/apis_and_tokens.md b/docs/install/apis_and_tokens.md deleted file mode 100644 index d03b377a7e0..00000000000 --- a/docs/install/apis_and_tokens.md +++ /dev/null @@ -1,162 +0,0 @@ -# How to setup various tokens and APIs for the project - -This doc explains how to setup various tokens and APIs for the project. You will need some of these tokens and APIs to run the app and use its features. You must set up at least one of these tokens or APIs to run the app. - -## OpenAI API key - -To get your OpenAI API key, you need to: - -- Go to [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys) -- Create an account or log in with your existing one -- Add a payment method to your account (this is not free, sorry 😬) -- Copy your secret key (sk-...) and save it in ./.env as OPENAI_API_KEY - -## ChatGPT Free Access token - -> Note that this is disabled by default and requires additional configuration to work. -> See: [ChatGPT Reverse Proxy](../features/pandoranext.md) - -To get your Access token for ChatGPT 'Web Version', you need to: - -- Go to [https://chat.openai.com](https://chat.openai.com) -- Create an account or log in with your existing one -- Visit [https://chat.openai.com/api/auth/session](https://chat.openai.com/api/auth/session) -- Copy the value of the "accessToken" field and save it in ./.env as CHATGPT_ACCESS_TOKEN - -Warning: There may be a chance of your account being banned if you deploy the app to multiple users with this method. Use at your own risk. 😱 - -## Bing Access Token - -To get your Bing Access Token, you have a few options: - -- You can try leaving it blank and see if it works (fingers crossed 🤞) - -- You can follow these [new instructions](https://github.com/danny-avila/LibreChat/issues/370#issuecomment-1560382302) (thanks @danny-avila for sharing 🙌) - -- You can use MS Edge, navigate to bing.com, and do the following: - - Make sure you are logged in - - Open the DevTools by pressing F12 on your keyboard - - Click on the tab "Application" (On the left of the DevTools) - - Expand the "Cookies" (Under "Storage") - - Copy the value of the "\_U" cookie and save it in ./.env as BING_ACCESS_TOKEN - -## Anthropic Endpoint (Claude) - -- Create an account at [https://console.anthropic.com/](https://console.anthropic.com/) -- Go to [https://console.anthropic.com/account/keys](https://console.anthropic.com/account/keys) and get your api key -- add it to `ANTHROPIC_API_KEY=` in the `.env` file - -## Google LLMs - -To setup Google LLMs (via Google Cloud Vertex AI), first, signup for Google Cloud: https://cloud.google.com/ - -You can usually get $300 starting credit, which makes this option free for 90 days. - -### Once signed up, Enable the Vertex AI API on Google Cloud: - - Go to [Vertex AI page on Google Cloud console](https://console.cloud.google.com/vertex-ai) - - Click on "Enable API" if prompted -### Create a Service Account with Vertex AI role: - - **[Click here to create a Service Account](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1)** - - **Select or create a project** - - ### Enter a service account ID (required), name and description are optional - - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/0c5cd177-029b-44fa-a398-a794aeb09de6) - - ### Click on "Create and Continue" to give at least the "Vertex AI User" role - - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/22d3a080-e71e-446e-8485-bcc5bf558dbb) - - **Click on "Continue/Done"** -### Create a JSON key to Save in Project Directory: - - **Go back to [the Service Accounts page](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts)** - - **Select your service account** - - ### Click on "Keys" - - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/735a7bbe-25a6-4b4c-9bb5-e0d8aa91be3d) - - ### Click on "Add Key" and then "Create new key" - - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/cfbb20d3-94a8-4cd1-ac39-f9cd8c2fceaa) - - **Choose JSON as the key type and click on "Create"** - - **Download the key file and rename it as 'auth.json'** - - **Save it within the project directory, in `/api/data/`** - - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/f5b8bcb5-1b20-4751-81a1-d3757a4b3f2f) - - -## Azure OpenAI - -In order to use Azure OpenAI with this project, specific environment variables must be set in your `.env` file. These variables will be used for constructing the API URLs. - -The variables needed are outlined below: - -### Required Variables - -* `AZURE_API_KEY`: Your Azure OpenAI API key. -* `AZURE_OPENAI_API_INSTANCE_NAME`: The instance name of your Azure OpenAI API. -* `AZURE_OPENAI_API_DEPLOYMENT_NAME`: The deployment name of your Azure OpenAI API. -* `AZURE_OPENAI_API_VERSION`: The version of your Azure OpenAI API. - -For example, with these variables, the URL for chat completion would look something like: -```plaintext -https://{AZURE_OPENAI_API_INSTANCE_NAME}.openai.azure.com/openai/deployments/{AZURE_OPENAI_API_DEPLOYMENT_NAME}/chat/completions?api-version={AZURE_OPENAI_API_VERSION} -``` -You should also consider changing the `AZURE_OPENAI_MODELS` variable to the models available in your deployment. - -#### Additional Configuration Notes - -- **Endpoint Construction**: The provided variables help customize the construction of the API URL for Azure. - -- **Model Deployment Naming**: As of 2023-11-10, the Azure API allows only one model per deployment. It's advisable to name your deployments after the model name (e.g., "gpt-3.5-turbo") for easy deployment switching. This is facilitated by setting `AZURE_USE_MODEL_AS_DEPLOYMENT_NAME` to `TRUE`. - -Alternatively, use custom deployment names and set `AZURE_OPENAI_DEFAULT_MODEL` for expected functionality. - -- **`AZURE_OPENAI_MODELS`**: List the available models, separated by commas without spaces. The first listed model will be the default. If left blank, internal settings will be used. Note that deployment names can't have periods, which are removed when generating the endpoint. - -Example use: - -```bash -# .env file -AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4,gpt-5 - -``` - -- **`AZURE_USE_MODEL_AS_DEPLOYMENT_NAME`**: Enable using the model name as the deployment name for the API URL. - -Example use: - -```bash -# .env file -AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE - -``` - -Note: Azure API does not use the `model` in the payload and is more of an identifying field for the LibreChat App. If using non-model deployment names, but you're having issues with the model not being recognized, you should set this field. It will also not be used as the deployment name if AZURE_USE_MODEL_AS_DEPLOYMENT_NAME is enabled, which will prioritize what the user selects as the model. - -- **`AZURE_OPENAI_DEFAULT_MODEL`**: Override the model setting for Azure, useful if using custom deployment names. - -Example use: - -```bash -# .env file -AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo # do include periods in the model name here - -``` - -### Optional Variables - -* `AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME`: The deployment name for completion. This is currently not in use but may be used in future. -* `AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME`: The deployment name for embedding. This is currently not in use but may be used in future. - -These two variables are optional but may be used in future updates of this project. - -### Using Plugins with Azure - -Note: To use the Plugins endpoint with Azure OpenAI, you need a deployment supporting [function calling](https://techcommunity.microsoft.com/t5/azure-ai-services-blog/function-calling-is-now-available-in-azure-openai-service/ba-p/3879241). Otherwise, you need to set "Functions" off in the Agent settings. When you are not using "functions" mode, it's recommend to have "skip completion" off as well, which is a review step of what the agent generated. - -To use Azure with the Plugins endpoint, make sure the following environment variables are set: - -* `PLUGINS_USE_AZURE`: If set to "true" or any truthy value, this will enable the program to use Azure with the Plugins endpoint. -* `AZURE_API_KEY`: Your Azure API key must be set with an environment variable. - -## That's it! You're all set. 🎉 - ---- - ## [Free AI APIs](free_ai_apis.md) - ---- - ->⚠️ Note: If you're having trouble, before creating a new issue, please search for similar ones on our [#issues thread on our discord](https://discord.gg/weqZFtD9C4) or our [troubleshooting discussion](https://github.com/danny-avila/LibreChat/discussions/categories/troubleshooting) on our Discussions page. If you don't find a relevant issue, feel free to create a new one and provide as much detail as possible. - diff --git a/docs/install/configuration/ai_setup.md b/docs/install/configuration/ai_setup.md new file mode 100644 index 00000000000..4743b2b73e3 --- /dev/null +++ b/docs/install/configuration/ai_setup.md @@ -0,0 +1,401 @@ +--- +title: 🤖 AI Setup +description: This doc explains how to setup your AI providers, their APIs and credentials. +weight: -8 +--- + + + +--- + +# AI Setup + +This doc explains how to setup your AI providers, their APIs and credentials. + +**"Endpoints"** refer to the AI provider, configuration or API to use, which determines what models and settings are available for the current chat request. + +For example, OpenAI, Google, Plugins, Azure OpenAI, Anthropic, are all different "endpoints". Since OpenAI was the first supported endpoint, it's listed first by default. + +Using the default environment values from [/.env.example](https://github.com/danny-avila/LibreChat/blob/main/.env.example) will enable several endpoints, with credentials to be provided on a per-user basis from the web app. Alternatively, you can provide credentials for all users of your instance. + +This guide will walk you through setting up each Endpoint as needed. + +**Reminder: If you use docker, you should [rebuild the docker image (here's how)](dotenv.md) each time you update your credentials** + +*Note: Configuring pre-made Endpoint/model/conversation settings as singular options for your users is a planned feature. See the related discussion here: [System-wide custom model settings (lightweight GPTs) #1291](https://github.com/danny-avila/LibreChat/discussions/1291)* + +## General + +### [Free AI APIs](free_ai_apis.md) + +### Setting a Default Endpoint + +In the case where you have multiple endpoints setup, but want a specific one to be first in the order, you need to set the following environment variable. + +```bash +# .env file +# No spaces between values +ENDPOINTS=azureOpenAI,openAI,google +``` + +Note that LibreChat will use your last selected endpoint when creating a new conversation. So if Azure OpenAI is first in the order, but you used or view an OpenAI conversation last, when you hit "New Chat," OpenAI will be selected with its default conversation settings. + +To override this behavior, you need a preset and you need to set that specific preset as the default one to use on every new chat. + +### Setting a Default Preset +See the **[Presets Guide](../../features/presets.md)** for more details + +A preset refers to a specific Endpoint/Model/Conversation Settings that you can save. + +The default preset will always be used when creating a new conversation. + +Here's a video to demonstrate: **[Setting a Default Preset](https://github.com/danny-avila/LibreChat/assets/110412045/bbde830f-18d9-4884-88e5-1bd8f7ac585d)** + +--- + +## OpenAI + +To get your OpenAI API key, you need to: + +- Go to **[https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys)** +- Create an account or log in with your existing one +- Add a payment method to your account (this is not free, sorry 😬) +- Copy your secret key (sk-...) and save it in ./.env as OPENAI_API_KEY + +Notes: +- Selecting a vision model for messages with attachments is not necessary as it will be switched behind the scenes for you. If you didn't outright select a vision model, it will only be used for the vision request and you should still see the non-vision model you had selected after the request is successful +- OpenAI Vision models allow for messages without attachments + +--- + +## Anthropic + +- Create an account at **[https://console.anthropic.com/](https://console.anthropic.com/)** +- Go to **[https://console.anthropic.com/account/keys](https://console.anthropic.com/account/keys)** and get your api key +- add it to `ANTHROPIC_API_KEY=` in the `.env` file + +--- + +## Google + +For the Google Endpoint, you can either use the **Generative Language API** (for Gemini models), or the **Vertex AI API** (for PaLM2 & Codey models, Gemini support coming soon). + +The Generative Language API uses an API key, which you can get from **Google AI Studio**. + +For Vertex AI, you need a Service Account JSON key file, with appropriate access configured. + +Instructions for both are given below. + +### Generative Language API (Gemini) + +**60 Gemini requests/minute are currently free until early next year when it enters general availability.** + +⚠️ Google will be using that free input/output to help improve the model, with data de-identified from your Google Account and API key. +⚠️ During this period, your messages “may be accessible to trained reviewers.” + +To use Gemini models, you'll need an API key. If you don't already have one, create a key in Google AI Studio. + +Get an API key here: **[makersuite.google.com](https://makersuite.google.com/app/apikey)** + +Once you have your key, provide the key in your .env file, which allows all users of your instance to use it. + +```bash +GOOGLE_KEY=mY_SeCreT_w9347w8_kEY +``` + +Or, you can make users provide it from the frontend by setting the following: +```bash +GOOGLE_KEY=user_provided +``` + +Notes: +- PaLM2 and Codey models cannot be accessed through the Generative Language API, only through Vertex AI. +- Selecting `gemini-pro-vision` for messages with attachments is not necessary as it will be switched behind the scenes for you +- Since `gemini-pro-vision`does not accept non-attachment messages, messages without attachments are automatically switched to use `gemini-pro` (otherwise, Google responds with an error) + +Setting `GOOGLE_KEY=user_provided` in your .env file will configure both the Vertex AI Service Account JSON key file and the Generative Language API key to be provided from the frontend like so: + +![image](https://github.com/danny-avila/LibreChat/assets/110412045/728cbc04-4180-45a8-848c-ae5de2b02996) + +### Vertex AI (PaLM 2 & Codey) + +To setup Google LLMs (via Google Cloud Vertex AI), first, signup for Google Cloud: **[cloud.google.com](https://cloud.google.com/)** + +You can usually get **$300 starting credit**, which makes this option free for 90 days. + +### 1. Once signed up, Enable the Vertex AI API on Google Cloud: + - Go to **[Vertex AI page on Google Cloud console](https://console.cloud.google.com/vertex-ai)** + - Click on `Enable API` if prompted +### 2. Create a Service Account with Vertex AI role: + - **[Click here to create a Service Account](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1)** + - **Select or create a project** + - ### Enter a service account ID (required), name and description are optional + - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/0c5cd177-029b-44fa-a398-a794aeb09de6) + - ### Click on "Create and Continue" to give at least the "Vertex AI User" role + - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/22d3a080-e71e-446e-8485-bcc5bf558dbb) + - **Click on "Continue/Done"** +### 3. Create a JSON key to Save in your Project Directory: + - **Go back to [the Service Accounts page](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts)** + - **Select your service account** + - ### Click on "Keys" + - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/735a7bbe-25a6-4b4c-9bb5-e0d8aa91be3d) + - ### Click on "Add Key" and then "Create new key" + - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/cfbb20d3-94a8-4cd1-ac39-f9cd8c2fceaa) + - **Choose JSON as the key type and click on "Create"** + - **Download the key file and rename it as 'auth.json'** + - **Save it within the project directory, in `/api/data/`** + - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/f5b8bcb5-1b20-4751-81a1-d3757a4b3f2f) + +**Saving your JSON key file in the project directory which allows all users of your LibreChat instance to use it.** + +Alternatively, you can make users provide it from the frontend by setting the following: + +```bash +# Note: this configures both the Vertex AI Service Account JSON key file +# and the Generative Language API key to be provided from the frontend. +GOOGLE_KEY=user_provided +``` + +Note: Using Gemini models through Vertex AI is possible but not yet supported. + +--- + +## Azure OpenAI + +In order to use Azure OpenAI with this project, specific environment variables must be set in your `.env` file. These variables will be used for constructing the API URLs. + +The variables needed are outlined below: + +### Required Variables + +These variables construct the API URL for Azure OpenAI. + +* `AZURE_API_KEY`: Your Azure OpenAI API key. +* `AZURE_OPENAI_API_INSTANCE_NAME`: The instance name of your Azure OpenAI API. +* `AZURE_OPENAI_API_DEPLOYMENT_NAME`: The deployment name of your Azure OpenAI API. +* `AZURE_OPENAI_API_VERSION`: The version of your Azure OpenAI API. + +For example, with these variables, the URL for chat completion would look something like: +```plaintext +https://{AZURE_OPENAI_API_INSTANCE_NAME}.openai.azure.com/openai/deployments/{AZURE_OPENAI_API_DEPLOYMENT_NAME}/chat/completions?api-version={AZURE_OPENAI_API_VERSION} +``` +You should also consider changing the `AZURE_OPENAI_MODELS` variable to the models available in your deployment. + +```bash +# .env file +AZURE_OPENAI_MODELS=gpt-4-1106-preview,gpt-4,gpt-3.5-turbo,gpt-3.5-turbo-1106,gpt-4-vision-preview +``` + +Overriding the construction of the API URL will be possible but is not yet implemented. Follow progress on this feature here: **[Issue #1266](https://github.com/danny-avila/LibreChat/issues/1266)** + +### Model Deployments + +> Note: a change will be developed to improve current configuration settings, to allow multiple deployments/model configurations setup with ease: **[#1390](https://github.com/danny-avila/LibreChat/issues/1390)** + +As of 2023-12-18, the Azure API allows only one model per deployment. + +**It's highly recommended** to name your deployments *after* the model name (e.g., "gpt-3.5-turbo") for easy deployment switching. + +When you do so, LibreChat will correctly switch the deployment, while associating the correct max context per model, if you have the following environment variable set: + +```bash +AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE +``` + +For example, when you have set `AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE`, the following deployment configuration provides the most seamless, error-free experience for LibreChat, including Vision support and tracking the correct max context tokens: + +![Screenshot 2023-12-18 111742](https://github.com/danny-avila/LibreChat/assets/110412045/4aa8a61c-0317-4681-8262-a6382dcaa7b0) + + +Alternatively, you can use custom deployment names and set `AZURE_OPENAI_DEFAULT_MODEL` for expected functionality. + +- **`AZURE_OPENAI_MODELS`**: List the available models, separated by commas without spaces. The first listed model will be the default. If left blank, internal settings will be used. Note that deployment names can't have periods, which are removed when generating the endpoint. + +Example use: + +```bash +# .env file +AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4,gpt-5 + +``` + +- **`AZURE_USE_MODEL_AS_DEPLOYMENT_NAME`**: Enable using the model name as the deployment name for the API URL. + +Example use: + +```bash +# .env file +AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE + +``` + +### Setting a Default Model for Azure + +This section is relevant when you are **not** naming deployments after model names as shown above. + +**Important:** The Azure OpenAI API does not use the `model` field in the payload but is a necessary identifier for LibreChat. If your deployment names do not correspond to the model names, and you're having issues with the model not being recognized, you should set this field to explicitly tell LibreChat to treat your Azure OpenAI API requests as if the specified model was selected. + +If AZURE_USE_MODEL_AS_DEPLOYMENT_NAME is enabled, the model you set with `AZURE_OPENAI_DEFAULT_MODEL` will **not** be recognized and will **not** be used as the deployment name; instead, it will use the model selected by the user as the "deployment" name. + +- **`AZURE_OPENAI_DEFAULT_MODEL`**: Override the model setting for Azure, useful if using custom deployment names. + +Example use: + +```bash +# .env file +# MUST be a real OpenAI model, named exactly how it is recognized by OpenAI API (not Azure) +AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo # do include periods in the model name here + +``` + +### Enabling Auto-Generated Titles with Azure + +The default titling model is set to `gpt-3.5-turbo`. + +If you're using `AZURE_USE_MODEL_AS_DEPLOYMENT_NAME` and have "gpt-35-turbo" setup as a deployment name, this should work out-of-the-box. + +In any case, you can adjust the title model as such: `OPENAI_TITLE_MODEL=your-title-model` + +### Using GPT-4 Vision with Azure + +Currently, the best way to setup Vision is to use your deployment names as the model names, as [shown here](#model-deployments) + +This will work seamlessly as it does with the [OpenAI endpoint](#openai) (no need to select the vision model, it will be switched behind the scenes) + +Alternatively, you can set the [required variables](#required-variables) to explicitly use your vision deployment, but this may limit you to exclusively using your vision deployment for all Azure chat settings. + +As of December 18th, 2023, Vision models seem to have degraded performance with Azure OpenAI when compared to [OpenAI](#openai) + +![image](https://github.com/danny-avila/LibreChat/assets/110412045/7306185f-c32c-4483-9167-af514cc1c2dd) + + +> Note: a change will be developed to improve current configuration settings, to allow multiple deployments/model configurations setup with ease: **[#1390](https://github.com/danny-avila/LibreChat/issues/1390)** + +### Optional Variables + +*These variables are currently not used by LibreChat* + +* `AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME`: The deployment name for completion. This is currently not in use but may be used in future. +* `AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME`: The deployment name for embedding. This is currently not in use but may be used in future. + +These two variables are optional but may be used in future updates of this project. + +### Using Plugins with Azure + +Note: To use the Plugins endpoint with Azure OpenAI, you need a deployment supporting **[function calling](https://techcommunity.microsoft.com/t5/azure-ai-services-blog/function-calling-is-now-available-in-azure-openai-service/ba-p/3879241)**. Otherwise, you need to set "Functions" off in the Agent settings. When you are not using "functions" mode, it's recommend to have "skip completion" off as well, which is a review step of what the agent generated. + +To use Azure with the Plugins endpoint, make sure the following environment variables are set: + +* `PLUGINS_USE_AZURE`: If set to "true" or any truthy value, this will enable the program to use Azure with the Plugins endpoint. +* `AZURE_API_KEY`: Your Azure API key must be set with an environment variable. + +--- + +## [OpenRouter](https://openrouter.ai/) + +**[OpenRouter](https://openrouter.ai/)** is a legitimate proxy service to a multitude of LLMs, both closed and open source, including: +- OpenAI models (great if you are barred from their API for whatever reason) +- Anthropic Claude models (same as above) +- Meta's Llama models +- pygmalionai/mythalion-13b +- and many more open source models. Newer integrations are usually discounted, too! + +> See their available models and pricing here: **[Supported Models](https://openrouter.ai/docs#models)** + +OpenRouter is so great, I decided to integrate it to the project as a standalone feature. + +**Setup:** +- Signup to **[OpenRouter](https://openrouter.ai/)** and create a key. You should name it and set a limit as well. +- Set the environment variable `OPENROUTER_API_KEY` in your .env file to the key you just created. +- Set something in the `OPENAI_API_KEY`, it can be anyting, but **do not** leave it blank or set to `user_provided` +- Restart your LibreChat server and use the OpenAI or Plugins endpoints. + +**Notes:** +- [TODO] **In the future, you will be able to set up OpenRouter from the frontend as well.** +- This will override the official OpenAI API or your reverse proxy settings for both Plugins and OpenAI. +- On initial setup, you may need to refresh your page twice to see all their supported models populate automatically. +- Plugins: Functions Agent works with OpenRouter when using OpenAI models. +- Plugins: Turn functions off to try plugins with non-OpenAI models (ChatGPT plugins will not work and others may not work as expected). +- Plugins: Make sure `PLUGINS_USE_AZURE` is not set in your .env file when wanting to use OpenRouter and you have Azure configured. + +--- + +## Unofficial APIs + +**Important:** Stability for Unofficial APIs are not guaranteed. Access methods to these APIs are hacky, prone to errors, and patching, and are marked lowest in priority in LibreChat's development. + +### ChatGPTBrowser + +**Backend Access to https://chat.openai.com/api** + +This is not to be confused with [OpenAI's Official API](#openai)! + +> Note that this is disabled by default and requires additional configuration to work. +> Also, using this may have your data exposed to 3rd parties if using a proxy, and OpenAI may flag your account. +> See: [ChatGPT Reverse Proxy](../../features/pandoranext.md) + +To get your Access token for ChatGPT Browser Access, you need to: + +- Go to **[https://chat.openai.com](https://chat.openai.com)** +- Create an account or log in with your existing one +- Visit **[https://chat.openai.com/api/auth/session](https://chat.openai.com/api/auth/session)** +- Copy the value of the "accessToken" field and save it in ./.env as CHATGPT_ACCESS_TOKEN + +Warning: There may be a chance of your account being banned if you deploy the app to multiple users with this method. Use at your own risk. + +--- + +### BingAI +I recommend using Microsoft Edge for this: + +- Navigate to **[Bing Chat](https://www.bing.com/chat)** +- **Login** if you haven't already +- Initiate a conversation with Bing +- Open `Dev Tools`, usually with `F12` or `Ctrl + Shift + C` +- Navigate to the `Network` tab +- Look for `lsp.asx` (if it's not there look into the other entries for one with a **very long** cookie) +- Copy the whole cookie value. (Yes it's very long 😉) +- Use this **"full cookie string"** for your "BingAI Token" + +

+ +

+ +--- + +## Conclusion + +

That's it! You're all set. 🎉

+ +--- + +>⚠️ Note: If you're having trouble, before creating a new issue, please search for similar ones on our [#issues thread on our discord](https://discord.gg/weqZFtD9C4) or our [troubleshooting discussion](https://github.com/danny-avila/LibreChat/discussions/categories/troubleshooting) on our Discussions page. If you don't find a relevant issue, feel free to create a new one and provide as much detail as possible. + diff --git a/docs/install/default_language.md b/docs/install/configuration/default_language.md similarity index 80% rename from docs/install/default_language.md rename to docs/install/configuration/default_language.md index 483821a1cd7..8255489c571 100644 --- a/docs/install/default_language.md +++ b/docs/install/configuration/default_language.md @@ -1,3 +1,9 @@ +--- +title: 🌍 Default Language +description: How to change LibreChat's default language +weight: -3 +--- + # Default Language 🌍 ## How to change the default language @@ -33,4 +39,4 @@ export default { lang }; ``` --- -> **❗If you wish to contribute your own translation to LibreChat, please refer to this document for instructions: [Contribute a Translation](../contributions/translation_contribution.md)** +> **❗If you wish to contribute your own translation to LibreChat, please refer to this document for instructions: [Contribute a Translation](../../contributions/translation_contribution.md)** diff --git a/docs/install/configuration/docker_override.md b/docs/install/configuration/docker_override.md new file mode 100644 index 00000000000..a1a54b4e309 --- /dev/null +++ b/docs/install/configuration/docker_override.md @@ -0,0 +1,77 @@ +--- +title: 🐋 Docker Compose Override +description: "How to Use the Docker Compose Override File: In Docker Compose, an override file is a powerful feature that allows you to modify the default configuration provided by the main `docker-compose.yml` without the need to directly edit or duplicate the whole file." +weight: -9 +--- + +# How to Use the Docker Compose Override File + +In Docker Compose, an override file is a powerful feature that allows you to modify the default configuration provided by the main `docker-compose.yml` without the need to directly edit or duplicate the whole file. The primary use of the override file is for local development customizations, and Docker Compose merges the configurations of the `docker-compose.yml` and the `docker-compose.override.yml` files when you run `docker-compose up`. + +Here's a quick guide on how to use the `docker-compose.override.yml`: + +> Note: Please consult the `docker-compose.override.yml.example` for more examples + +See the the official docker documentation for more info: + +- **[docker docs - understanding-multiple-compose-files](https://docs.docker.com/compose/multiple-compose-files/extends/#understanding-multiple-compose-files)** +- **[docker docs - merge-compose-files](https://docs.docker.com/compose/multiple-compose-files/merge/#merge-compose-files)** +- **[docker docs - specifying-multiple-compose-files](https://docs.docker.com/compose/reference/#specifying-multiple-compose-files)** + +## Step 1: Create a `docker-compose.override.yml` file + +If you don't already have a `docker-compose.override.yml` file, you can create one by copying the example override content: + +```bash +cp docker-compose.override.yml.example docker-compose.override.yml +``` + +This file will be picked up by Docker Compose automatically when you run docker-compose commands. + +## Step 2: Edit the override file + +Open your `docker-compose.override.yml` file with vscode or any text editor. + +Make your desired changes by uncommenting the relevant sections and customizing them as needed. + +For example, if you want to use a prebuilt image for the `api` service and expose MongoDB's port, your `docker-compose.override.yml` might look like this: + +```yaml +version: '3.4' + +services: + api: + image: ghcr.io/danny-avila/librechat:latest + + mongodb: + ports: + - 27018:27017 +``` + +> Note: Be cautious with exposing ports like MongoDB to the public, as it can make your database vulnerable to attacks. + +## Step 3: Apply the changes + +To apply your configuration changes, simply run Docker Compose as usual. Docker Compose automatically takes into account both the `docker-compose.yml` and the `docker-compose.override.yml` files: + +```bash +docker-compose up -d +``` + +If you want to invoke a build with the changes before starting containers: + +```bash +docker-compose build +docker-compose up -d +``` + +## Step 4: Verify the changes + +After starting your services with the modified configuration, you can verify that the changes have been applied using the `docker ps` command to list the running containers and their properties, such as ports. + +## Important Considerations + +- **Order of Precedence**: Values defined in the override file take precedence over those specified in the original `docker-compose.yml` file. +- **Security**: When customizing ports and publicly exposing services, always be conscious of the security implications. Avoid using defaults for production or sensitive environments. + +By following these steps and considerations, you can easily and safely modify your Docker Compose configuration without altering the original `docker-compose.yml` file, making it simpler to manage and maintain different environments or local customizations. \ No newline at end of file diff --git a/docs/install/dotenv.md b/docs/install/configuration/dotenv.md similarity index 69% rename from docs/install/dotenv.md rename to docs/install/configuration/dotenv.md index 4bf62840a84..5eda7ba3f7b 100644 --- a/docs/install/dotenv.md +++ b/docs/install/configuration/dotenv.md @@ -1,8 +1,41 @@ +--- +title: ⚙️ Environment Variables +description: Comprehensive guide for configuring your application's environment with the `.env` file. This document is your one-stop resource for understanding and customizing the environment variables that will shape your application's behavior in different contexts. +weight: -10 +--- + # .env File Configuration Welcome to the comprehensive guide for configuring your application's environment with the `.env` file. This document is your one-stop resource for understanding and customizing the environment variables that will shape your application's behavior in different contexts. While the default settings provide a solid foundation for a standard `docker` installation, delving into this guide will unveil the full potential of LibreChat. This guide empowers you to tailor LibreChat to your precise needs. Discover how to adjust language model availability, integrate social logins, manage the automatic moderation system, and much more. It's all about giving you the control to fine-tune LibreChat for an optimal user experience. +**If you use docker, you should rebuild the docker image each time you update your environment variables** + +Rebuild command: +```bash +npm run update:docker + +# OR, if you don't have npm +docker-compose build --no-cache +``` + +Alternatively, you can create a new file named `docker-compose.override.yml` in the same directory as your main `docker-compose.yml` file for LibreChat, where you can set your .env variables as needed under `environment`, or modify the default configuration provided by the main `docker-compose.yml`, without the need to directly edit or duplicate the whole file. + +For more info see: + +- Our quick guide: + - **[Docker Override](../configuration/docker_override.md)** + +- The official docker documentation: + - **[docker docs - understanding-multiple-compose-files](https://docs.docker.com/compose/multiple-compose-files/extends/#understanding-multiple-compose-files)** + - **[docker docs - merge-compose-files](https://docs.docker.com/compose/multiple-compose-files/merge/#merge-compose-files)** + - **[docker docs - specifying-multiple-compose-files](https://docs.docker.com/compose/reference/#specifying-multiple-compose-files)** + +- You can also view an example of an override file for LibreChat in your LibreChat folder and on GitHub: + - **[docker-compose.override.example](https://github.com/danny-avila/LibreChat/blob/main/docker-compose.override.yaml.example)** + +--- + ## Server Configuration ### Customization @@ -30,11 +63,13 @@ PORT=3080 ### MongoDB Database -- Change this to your MongoDB URI if different. It is recommend to append LibreChat. +- Change this to your MongoDB URI if different. You should also add `LibreChat` or your own `APP_TITLE` as the database name in the URI. For example: + - if you are using docker, the URI format is `mongodb://:/`. Your `MONGO_URI` should look like this: `mongodb://127.0.0.1:27018/LibreChat` + - if you are using an online db, the URI format is `mongodb+srv://:@/?`. Your `MONGO_URI` should look like this: `mongodb+srv://username:password@host.mongodb.net/LibreChat?retryWrites=true` (`retryWrites=true` is the only option you need when using the online db) - Instruction on how to create an online MongoDB database (useful for use without docker): - [Online MongoDB](./mongodb.md) - Securely access your docker MongoDB database: - - [Manage your database](../features/manage_your_database.md) + - [Manage your database](../../features/manage_your_database.md) ```bash MONGO_URI=mongodb://127.0.0.1:27018/LibreChat @@ -50,6 +85,37 @@ DOMAIN_CLIENT=http://localhost:3080 DOMAIN_SERVER=http://localhost:3080 ``` +### Prevent Public Search Engines Indexing +By default, your website will not be indexed by public search engines (e.g. Google, Bing, …). This means that people will not be able to find your website through these search engines. If you want to make your website more visible and searchable, you can change the following setting to `false` + +```bash +NO_INDEX=true +``` + +> ❗**Note:** This method is not guaranteed to work for all search engines, and some search engines may still index your website or web page for other purposes, such as caching or archiving. Therefore, you should not rely solely on this method to protect sensitive or confidential information on your website or web page. + +### Logging + +LibreChat has built-in central logging, see [Logging System](../../features/logging_system.md) for more info. + +- Debug logging is enabled by default and crucial for development. +- To report issues, reproduce the error and submit logs from `./api/logs/debug-%DATE%.log` at: **[LibreChat GitHub Issues](https://github.com/danny-avila/LibreChat/issues)** +- Error logs are stored in the same location. +- Keep debug logs active by default or disable them by setting `DEBUG_LOGGING=false` in the environment variable. +- For more information about this feature, read our docs: **[Logging System](../../features/logging_system.md)** + +```bash +DEBUG_LOGGING=true +``` + +- Enable verbose server output in the console with `DEBUG_CONSOLE=TRUE`, though it's not recommended due to high verbosity. + +```bash +DEBUG_CONSOLE=false +``` + +This is not recommend, however, as the outputs can be quite verbose, and so it's disabled by default. + ### Permission > UID and GID are numbers assigned by Linux to each user and group on the system. If you have permission problems, set here the UID and GID of the user running the docker compose command. The applications in the container will run with these uid/gid. @@ -71,7 +137,7 @@ PROXY= ``` ### Anthropic -see: [Anthropic Endpoint](./apis_and_tokens.md#anthropic-endpoint-claude) +see: [Anthropic Endpoint](./ai_setup.md#anthropic) - You can request an access key from https://console.anthropic.com/ - Leave `ANTHROPIC_API_KEY=` blank to disable this endpoint - Set `ANTHROPIC_API_KEY=` to "user_provided" to allow users to provide their own API key from the WebUI @@ -85,7 +151,7 @@ ANTHROPIC_REVERSE_PROXY= ``` ### Azure -see: [Azure OpenAI](./apis_and_tokens.md#azure-openai) +**Important:** See [the complete Azure OpenAI setup guide](./ai_setup.md#azure-openai) for thorough instructions on enabling Azure OpenAI - To use Azure with this project, set the following variables. These will be used to build the API URL. @@ -130,15 +196,15 @@ PLUGINS_USE_AZURE="true" ``` ### BingAI -Bing, also used for Sydney, jailbreak, and Bing Image Creator, see: [Bing Access token](./apis_and_tokens.md#bing-access-token) and [Bing Jailbreak](../features/bing_jailbreak.md) +Bing, also used for Sydney, jailbreak, and Bing Image Creator, see: [Bing Access token](./ai_setup.md#bingai) and [Bing Jailbreak](../../features/bing_jailbreak.md) -- Follow these instructions to get your bing access token (it's best to use the full cookie string for that purpose): [Bing Access Token](https://github.com/danny-avila/LibreChat/issues/370#issuecomment-1560382302) +- Follow these instructions to get your bing access token (it's best to use the full cookie string for that purpose): **[Bing Access Token](../configuration/ai_setup.md#bingai)** - Leave `BINGAI_TOKEN=` blank to disable this endpoint - Set `BINGAI_TOKEN=` to "user_provided" to allow users to provide their own API key from the WebUI > Note: It is recommended to leave it as "user_provided" and provide the token from the WebUI. -- `BINGAI_HOST` can be necessary for some people in different countries, e.g. China (https://cn.bing.com). Leave it blank or commented out to use default server. +- `BINGAI_HOST` can be necessary for some people in different countries, e.g. China (`https://cn.bing.com`). Leave it blank or commented out to use default server. ```bash BINGAI_TOKEN=user_provided @@ -146,16 +212,17 @@ BINGAI_HOST= ``` ### ChatGPT -see: [ChatGPT Free Access token](./apis_and_tokens.md#chatgpt-free-access-token) +see: [ChatGPT Free Access token](../configuration/ai_setup.md#chatgptbrowser) -> **Warning**: To use this endpoint you'll have to set up your own reverse proxy. Here is the installation guide to deploy your own (based on [PandoraNext](https://github.com/pandora-next/deploy)): **[PandoraNext Deployment Guide](../features/pandoranext.md)** +> **Warning**: To use this endpoint you'll have to set up your own reverse proxy. Here is the installation guide to deploy your own (based on [PandoraNext](https://github.com/pandora-next/deploy)): **[PandoraNext Deployment Guide](../../features/pandoranext.md)** ```bash CHATGPT_REVERSE_PROXY= ``` -> ~~Note: If you're a GPT plus user you can add gpt-4, gpt-4-plugins, gpt-4-code-interpreter, and gpt-4-browsing to the list above and use the models for these features; however, the view/display portion of these features are not supported, but you can use the underlying models, which have higher token context~~ -> **Note:** The current method only works with `text-davinci-002-render-sha` +> **Note:** If you're a GPT plus user you can try adding `gpt-4`, `gpt-4-plugins`, `gpt-4-code-interpreter`, and `gpt-4-browsing` to the list above and use the models for these features; **however, the view/display portion of these features are not supported**, but you can use the underlying models, which have higher token context + +> This method **might only works** with `text-davinci-002-render-sha` and **might stop working** at any moment. - Leave `CHATGPT_TOKEN=` blank to disable this endpoint - Set `CHATGPT_TOKEN=` to "user_provided" to allow users to provide their own API key from the WebUI @@ -167,13 +234,22 @@ CHATGPT_MODELS=text-davinci-002-render-sha ``` ### Google -Follow these instruction to setup: [Google LLMs](./apis_and_tokens.md#google-llms) +Follow these instructions to setup the [Google Endpoint](./ai_setup.md#google) ```bash GOOGLE_KEY=user_provided GOOGLE_REVERSE_PROXY= ``` +- Customize the available models, separated by commas, **without spaces**. + - The first will be default. + - Leave it blank or commented out to use internal settings (default: all listed below). + +```bash +# all available models as of 12/16/23 +GOOGLE_MODELS=gemini-pro,gemini-pro-vision,chat-bison,chat-bison-32k,codechat-bison,codechat-bison-32k,text-bison,text-bison-32k,text-unicorn,code-gecko,code-bison,code-bison-32k +``` + ### OpenAI - To get your OpenAI API key, you need to: @@ -256,9 +332,9 @@ OPENROUTER_API_KEY= ### Plugins Here are some useful documentation about plugins: -- [Introduction](../features/plugins/introduction.md) -- [Make Your Own](../features/plugins/make_your_own.md) -- [Using official ChatGPT Plugins](../features/plugins/chatgpt_plugins_openapi.md) +- [Introduction](../../features/plugins/introduction.md) +- [Make Your Own](../../features/plugins/make_your_own.md) +- [Using official ChatGPT Plugins](../../features/plugins/chatgpt_plugins_openapi.md) #### General Configuration: - Identify the available models, separated by commas **without spaces**. The first model in the list will be set as default. Leave it blank or commented out to use internal settings. @@ -274,7 +350,7 @@ DEBUG_PLUGINS=true ``` - For securely storing credentials, you need a fixed key and IV. You can set them here for prod and dev environments. - - You need a 32-byte key (64 characters in hex) and 16-byte IV (32 characters in hex) You can use this replit to generate some quickly: [Key Generator](https://replit.com/@daavila/crypto#index.js) + - You need a 32-byte key (64 characters in hex) and 16-byte IV (32 characters in hex) You can use this replit to generate some quickly: **[Key Generator](https://replit.com/@daavila/crypto#index.js)** > Warning: If you don't set them, the app will crash on startup. @@ -284,7 +360,7 @@ CREDS_IV=e2341419ec3dd3d19b13a1a87fafcbfb ``` #### Azure AI Search -This plugin supports searching Azure AI Search for answers to your questions. See: [Azure AI Search](../features/plugins/azure_ai_search.md) +This plugin supports searching Azure AI Search for answers to your questions. See: [Azure AI Search](../../features/plugins/azure_ai_search.md) ```bash AZURE_AI_SEARCH_SERVICE_ENDPOINT= @@ -305,7 +381,7 @@ AZURE_AI_SEARCH_SEARCH_OPTION_SELECT= ``` - For customization of the DALL-E-3 System prompt, uncomment the following, and provide your own prompt. **(Advanced)** - - See official prompt for reference: [DALL-E System Prompt](https://github.com/spdustin/ChatGPT-AutoExpert/blob/main/_system-prompts/dall-e.md) + - See official prompt for reference: **[DALL-E System Prompt](https://github.com/spdustin/ChatGPT-AutoExpert/blob/main/_system-prompts/dall-e.md)** ```bash DALLE3_SYSTEM_PROMPT="Your System Prompt here" @@ -329,7 +405,7 @@ DALLE_REVERSE_PROXY= > Note: if you have PROXY set, it will be used for DALL-E calls also, which is universal for the app #### Google Search -See detailed instructions here: [Google Search](../features/plugins/google_search.md) +See detailed instructions here: [Google Search](../../features/plugins/google_search.md) ```bash GOOGLE_API_KEY= @@ -344,23 +420,23 @@ SERPAPI_API_KEY= ``` #### Stable Diffusion (Automatic1111) -See detailed instructions here: [Stable Diffusion](../features/plugins/stable_diffusion.md) +See detailed instructions here: **[Stable Diffusion](../../features/plugins/stable_diffusion.md)** -- Use "http://127.0.0.1:7860" with local install and "http://host.docker.internal:7860" for docker +- Use `http://127.0.0.1:7860` with local install and `http://host.docker.internal:7860` for docker ```bash SD_WEBUI_URL=http://host.docker.internal:7860 ``` #### WolframAlpha -See detailed instructions here: [Wolfram Alpha](../features/plugins/wolfram.md) +See detailed instructions here: **[Wolfram Alpha](../../features/plugins/wolfram.md)** ```bash WOLFRAM_APP_ID= ``` #### Zapier -- You need a Zapier account. Get your API key from here: [Zapier](https://nla.zapier.com/credentials/) +- You need a Zapier account. Get your API key from here: **[Zapier](https://nla.zapier.com/credentials/)** - Create allowed actions - Follow step 3 in this getting start guide from Zapier > Note: zapier is known to be finicky with certain actions. Writing email drafts is probably the best use of it. @@ -414,13 +490,23 @@ This section contains the configuration for: ### Moderation The Automated Moderation System uses a scoring mechanism to track user violations. As users commit actions like excessive logins, registrations, or messaging, they accumulate violation scores. Upon reaching a set threshold, the user and their IP are temporarily banned. This system ensures platform security by monitoring and penalizing rapid or suspicious activities. -see: [Automated Moderation](../features/mod_system.md) +see: **[Automated Moderation](../../features/mod_system.md)** #### Basic Moderation Settings +- `OPENAI_MODERATION`: Set to true or false, Whether or not to enable OpenAI moderation on the **OpenAI** and **Plugins** endpoints +- `OPENAI_MODERATION_API_KEY`: Your OpenAI API key +- `OPENAI_MODERATION_REVERSE_PROXY`: Note: Commented out by default, this is not working with all reverse proxys + +```bash +OPENAI_MODERATION=false +OPENAI_MODERATION_API_KEY= +OPENAI_MODERATION_REVERSE_PROXY= +``` + - `BAN_VIOLATIONS`: Whether or not to enable banning users for violations (they will still be logged) - `BAN_DURATION`: How long the user and associated IP are banned for (in milliseconds) -- `BAN_INTERVAL` The user will be banned everytime their score reaches/crosses over the interval threshold +- `BAN_INTERVAL`: The user will be banned everytime their score reaches/crosses over the interval threshold ```bash BAN_VIOLATIONS=true @@ -491,10 +577,11 @@ MESSAGE_USER_WINDOW=1 ### Balance The following enables user balances for the OpenAI/Plugins endpoints, which you can add manually or you will need to build out a balance accruing system for users. -see: [Token Usage](../features/token_usage.md) +see: **[Token Usage](../../features/token_usage.md)** - To manually add balances, run the following command:`npm run add-balance` - You can also specify the email and token credit amount to add, e.g.:`npm run add-balance example@example.com 1000` + - To list the balance of every user: `npm run list-balances` > **Note:** 1000 credits = $0.001 (1 mill USD) @@ -505,7 +592,7 @@ CHECK_BALANCE=false ``` ### Registration and Login -see: [User/Auth System](../install/user_auth_system.md) +see: **[User/Auth System](../configuration/user_auth_system.md)** ![image](https://github.com/danny-avila/LibreChat/assets/81851188/52a37d1d-7392-4a9a-a79f-90ed2da7f841) @@ -516,15 +603,17 @@ see: [User/Auth System](../install/user_auth_system.md) - `ALLOW_SOCIAL_REGISTRATION`: Enable or disable registration of new user using various social network. Set to `true` or `false` to enable or disable. > **Quick Tip:** Even with registration disabled, add users directly to the database using `npm run create-user`. +> **Quick Tip:** With registration disabled, you can delete a user with `npm run delete-user email@domain.com`. ```bash +ALLOW_EMAIL_LOGIN=true ALLOW_REGISTRATION=true ALLOW_SOCIAL_LOGIN=false ALLOW_SOCIAL_REGISTRATION=false ``` - Default values: session expiry: 15 minutes, refresh token expiry: 7 days - - For more information: [Refresh Token](https://github.com/danny-avila/LibreChat/pull/927) + - For more information: **[Refresh Token](https://github.com/danny-avila/LibreChat/pull/927)** ```bash SESSION_EXPIRY=1000 * 60 * 15 @@ -532,7 +621,7 @@ REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7 ``` - You should use new secure values. The examples given are 32-byte keys (64 characters in hex). - - Use this replit to generate some quickly: [JWT Keys](https://replit.com/@daavila/crypto#index.js) + - Use this replit to generate some quickly: **[JWT Keys](https://replit.com/@daavila/crypto#index.js)** ```bash JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef @@ -541,9 +630,9 @@ JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8 ### Social Logins -#### [Discord](../install/user_auth_system.md#discord-authentication) +#### [Discord Authentication](../configuration/user_auth_system.md#discord) -for more information: [Discord](../install/user_auth_system.md#discord-authentication) +for more information: **[Discord](../configuration/user_auth_system.md#discord)** ```bash # Discord @@ -552,9 +641,9 @@ DISCORD_CLIENT_SECRET=your_client_secret DISCORD_CALLBACK_URL=/oauth/discord/callback ``` -#### [Facebook](../install/user_auth_system.md#facebook-authentication) +#### [Facebook Authentication](../configuration/user_auth_system.md#facebook) -for more information: [Facebook](../install/user_auth_system.md#facebook-authentication) +for more information: **[Facebook Authentication](../configuration/user_auth_system.md#facebook)** ```bash # Facebook @@ -563,9 +652,9 @@ FACEBOOK_CLIENT_SECRET= FACEBOOK_CALLBACK_URL=/oauth/facebook/callback ``` -#### [GitHub](../install/user_auth_system.md#github-authentication) +#### [GitHub Authentication](../configuration/user_auth_system.md#github) -for more information: [GitHub](../install/user_auth_system.md#github-authentication) +for more information: **[GitHub Authentication](../configuration/user_auth_system.md#github)** ```bash # GitHub @@ -574,9 +663,9 @@ GITHUB_CLIENT_SECRET=your_client_secret GITHUB_CALLBACK_URL=/oauth/github/callback ``` -#### [Google](../install/user_auth_system.md#google-authentication) +#### [Google Authentication](../configuration/user_auth_system.md#google) -for more information: [Google](../install/user_auth_system.md#google-authentication) +for more information: **[Google Authentication](../configuration/user_auth_system.md#google)** ```bash # Google @@ -585,9 +674,9 @@ GOOGLE_CLIENT_SECRET= GOOGLE_CALLBACK_URL=/oauth/google/callback ``` -#### [OpenID](../install/user_auth_system.md#openid-authentication-with-azure-ad) +#### [OpenID Authentication](../configuration/user_auth_system.md#openid-with-aws-cognito) -for more information: [Azure OpenID](../install/user_auth_system.md#openid-authentication-with-azure-ad) or [AWS Cognito OpenID](../install/user_auth_system.md#openid-authentication-with-aws-cognito) +for more information: **[Azure OpenID Authentication](../configuration/user_auth_system.md#openid-with-azure-ad)** or **[AWS Cognito OpenID Authentication](../configuration/user_auth_system.md#openid-with-aws-cognito)** ```bash # OpenID @@ -603,7 +692,7 @@ OPENID_IMAGE_URL= ``` ### Email Password Reset -Email is used for password reset. See: [Email Password Reset](../install/user_auth_system.md#email-and-password-reset) +Email is used for password reset. See: **[Email Password Reset](../configuration/user_auth_system.md#email-and-password-reset)** - Note that all either service or host, username and password and the From address must be set for email to work. @@ -613,7 +702,7 @@ Email is used for password reset. See: [Email Password Reset](../install/user_au > > Failing to set valid values here will result in LibreChat using the unsecured password reset! -See: [nodemailer well-known-services](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/) +See: **[nodemailer well-known-services](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/)** ```bash EMAIL_SERVICE= diff --git a/docs/install/free_ai_apis.md b/docs/install/configuration/free_ai_apis.md similarity index 56% rename from docs/install/free_ai_apis.md rename to docs/install/configuration/free_ai_apis.md index 25e26fdf152..671ed9b6206 100644 --- a/docs/install/free_ai_apis.md +++ b/docs/install/configuration/free_ai_apis.md @@ -1,38 +1,20 @@ +--- +title: 💸 Free AI APIs +description: There are APIs offering free/free-trial access to AI APIs via reverse proxy... +weight: -6 +--- + # Free AI APIs There are APIs offering free/free-trial access to AI APIs via reverse proxy. -Here is a well-maintained public list of [Free AI APIs](https://github.com/zukixa/cool-ai-stuff) that may or may not be compatible with LibreChat - -### [OpenRouter](https://openrouter.ai/) ⇆ (preferred) - -While not completely free, you get free trial credits when you [sign up to OpenRouter](https://openrouter.ai/), a legitimate proxy service to a multitude of LLMs, both closed and open source, including: -- OpenAI models (great if you are barred from their API for whatever reason) -- Anthropic Claude models (same as above) -- Meta's Llama models -- pygmalionai/mythalion-13b -- and many more open source models. Newer integrations are usually discounted, too! - -OpenRouter is so great, I decided to integrate it to the project as a standalone feature. - -**Setup:** -- Signup to [OpenRouter](https://openrouter.ai/) and create a key. You should name it and set a limit as well. -- Set the environment variable `OPENROUTER_API_KEY` in your .env file to the key you just created. -- Restart your LibreChat server and use the OpenAI or Plugins endpoints. - -**Notes:** -- [TODO] **In the future, you will be able to set up OpenRouter from the frontend as well.** -- This will override the official OpenAI API or your reverse proxy settings for both Plugins and OpenAI. -- On initial setup, you may need to refresh your page twice to see all their supported models populate automatically. -- Plugins: Functions Agent works with OpenRouter when using OpenAI models. -- Plugins: Turn functions off to try plugins with non-OpenAI models (ChatGPT plugins will not work and others may not work as expected). -- Plugins: Make sure `PLUGINS_USE_AZURE` is not set in your .env file when wanting to use OpenRouter and you have Azure configured. +Here is a well-maintained public list of **[Free AI APIs](https://github.com/zukixa/cool-ai-stuff)** that may or may not be compatible with LibreChat -> ⚠️ OpenRouter is in a category of its own, and is highly recommended over the "free" services below. NagaAI and other 'free' API proxies tend to have intermittent issues, data leaks, and/or problems with the guidelines of the platforms they advertise on. Use the below at your own risk. +> ⚠️ **[OpenRouter](./ai_setup.md#openrouter)** is in a category of its own, and is highly recommended over the "free" services below. NagaAI and other 'free' API proxies tend to have intermittent issues, data leaks, and/or problems with the guidelines of the platforms they advertise on. Use the below at your own risk. ### NagaAI -Since NagaAI works with LibreChat, and offers Llama2 along with OpenAI models, let's start with that one: [NagaAI](https://t.me/chimera_ai) +Since NagaAI works with LibreChat, and offers Llama2 along with OpenAI models, let's start with that one: **[NagaAI](https://t.me/chimera_ai)** > ⚠️ Never trust 3rd parties. Use at your own risk of privacy loss. Your data may be used for AI training at best or for nefarious reasons at worst; this is true in all cases, even with official endpoints: never give an LLM sensitive/identifying information. If something is free, you are the product. If errors arise, they are more likely to be due to the 3rd party, and not this project, as I test the official endpoints first and foremost. diff --git a/docs/install/configuration/index.md b/docs/install/configuration/index.md new file mode 100644 index 00000000000..e577a8b3a56 --- /dev/null +++ b/docs/install/configuration/index.md @@ -0,0 +1,19 @@ +--- +title: Configuration +description: ⚙️ This section provides detailed guides on how to configure LibreChat to suit your needs and preferences. You will learn how to set up various environment variables, customize your Docker settings, choose your AI models and APIs, enable user authentication, connect to online MongoDB, change the default language, and more. +weight: 2 +--- + +# Configuration + + * ⚙️ [Environment Variables](./dotenv.md) + * 🐋 [Docker Compose Override](./docker_override.md) +--- + * 🤖 [AI Setup](./ai_setup.md) + * 🚅 [LiteLLM](./litellm.md) + * 💸 [Free AI APIs](./free_ai_apis.md) +--- + * 🛂 [Authentication System](./user_auth_system.md) + * 🍃 [Online MongoDB](./mongodb.md) + * 🌍 [Default Language](./default_language.md) + * 🌀 [Miscellaneous](./misc.md) \ No newline at end of file diff --git a/docs/install/litellm.md b/docs/install/configuration/litellm.md similarity index 92% rename from docs/install/litellm.md rename to docs/install/configuration/litellm.md index dfae409e00d..c9f86368993 100644 --- a/docs/install/litellm.md +++ b/docs/install/configuration/litellm.md @@ -1,5 +1,11 @@ +--- +title: 🚅 LiteLLM +description: Using LibreChat with LiteLLM Proxy +weight: -7 +--- + # Using LibreChat with LiteLLM Proxy -Use LiteLLM Proxy for: +Use **[LiteLLM Proxy](https://docs.litellm.ai/docs/simple_proxy)** for: * Calling 100+ LLMs Huggingface/Bedrock/TogetherAI/etc. in the OpenAI ChatCompletions & Completions format * Load balancing - between Multiple Models + Deployments of the same model LiteLLM proxy can handle 1k+ requests/second during load tests * Authentication & Spend Tracking Virtual Keys @@ -11,7 +17,7 @@ pip install litellm ``` ### Create a config.yaml for litellm proxy -More information on LiteLLM configurations here: https://docs.litellm.ai/docs/simple_proxy#proxy-configs +More information on LiteLLM configurations here: **[docs.litellm.ai/docs/simple_proxy](https://docs.litellm.ai/docs/simple_proxy)** ```yaml model_list: diff --git a/docs/install/misc.md b/docs/install/configuration/misc.md similarity index 92% rename from docs/install/misc.md rename to docs/install/configuration/misc.md index c3388dc75b0..75892fc7c74 100644 --- a/docs/install/misc.md +++ b/docs/install/configuration/misc.md @@ -1,9 +1,14 @@ +--- +title: 🌀 Miscellaneous +description: As LibreChat has varying use cases and environment possibilities, this page will host niche setup/configurations, as contributed by the community, that are not better delegated to any of the other guides. +weight: -2 +author: danny-avila and jerkstorecaller +--- + As LibreChat has varying use cases and environment possibilities, this page will host niche setup/configurations, as contributed by the community, that are not better delegated to any of the other guides. # Using LibreChat behind a reverse proxy with Basic Authentication -Written by [@danny-avila](https://github.com/danny-avila) and [@jerkstorecaller](https://github.com/jerkstorecaller) - ### Basic Authentication (Basic Auth) Basic Authentication is a simple authentication scheme built into the HTTP protocol. When a client sends a request to a server, the server can respond with a `401 Unauthorized` status code, prompting the client to provide a username and password. This username and password are then sent with subsequent requests in the HTTP header, encoded in Base64 format. diff --git a/docs/install/mongodb.md b/docs/install/configuration/mongodb.md similarity index 75% rename from docs/install/mongodb.md rename to docs/install/configuration/mongodb.md index dfd13ecede6..f280c4d25f8 100644 --- a/docs/install/mongodb.md +++ b/docs/install/configuration/mongodb.md @@ -1,7 +1,13 @@ +--- +title: 🍃 Online MongoDB +description: This guide teaches you how to set up an online MongoDB database for LibreChat using MongoDB Atlas, a cloud-based service. You will learn how to create an account, a project, and a cluster, as well as how to configure your database credentials, network access, and connection string. +weight: -4 +--- + # Set Up an Online MongoDB Database ## Create an account -- Open a new tab and go to [https://account.mongodb.com/account/register](https://account.mongodb.com/account/register) to create an account. +- Open a new tab and go to **[account.mongodb.com/account/register](https://account.mongodb.com/account/register)** to create an account. ## Create a project - Once you have set up your account, create a new project and name it (the name can be anything): @@ -80,10 +86,10 @@ ![image](https://github.com/fuegovic/LibreChat/assets/32828263/ccc52648-39fa-4f45-8e2b-96c93ffede4a) -- Make sure to replace `` with the database password you created in the "[database credentials](#database-credentials)" section above. Do not forget to remove the `<` `>` around the password. Also remove `&w=majority` at the end of the connection string. +- The URI format is `mongodb+srv://:@/?`. Make sure to replace `` with the database password you created in the "[database credentials](#database-credentials)" section above. Do not forget to remove the `<` `>` around the password. Also remove `&w=majority` at the end of the connection string. `retryWrites=true` is the only option you need to keep. You should also add `LibreChat` or your own `APP_TITLE` as the database name in the URI. - example: ``` -mongodb+srv://fuegovic:1Gr8Banana@render-librechat.fgycwpi.mongo.net/?retryWrites=true +mongodb+srv://fuegovic:1Gr8Banana@render-librechat.fgycwpi.mongo.net/LibreChat?retryWrites=true ``` --- diff --git a/docs/install/configuration/user_auth_system.md b/docs/install/configuration/user_auth_system.md new file mode 100644 index 00000000000..17016845dea --- /dev/null +++ b/docs/install/configuration/user_auth_system.md @@ -0,0 +1,578 @@ +--- +title: 🛂 Authentication System +description: This guide explains how to use the user authentication system of LibreChat, which offers secure and easy email and social logins. You will learn how to set up sign up, log in, password reset, and more. +weight: -5 +--- + +# User Authentication System + +LibreChat has a user authentication system that allows users to sign up and log in securely and easily. The system is scalable and can handle a large number of concurrent users without compromising performance or security. + +By default, we have email signup and login enabled, which means users can create an account using their email address and a password. They can also reset their password if they forget it. + +Additionally, our system can integrate social logins from various platforms such as Google, GitHub, Discord, OpenID, and more. This means users can log in using their existing accounts on these platforms, without having to create a new account or remember another password. + +>❗**Important:** When you run the app for the first time, you need to create a new account by clicking on "Sign up" on the login page. The first account you make will be the admin account. The admin account doesn't have any special features right now, but it might be useful if you want to make an admin dashboard to manage other users later. + +>> **Note:** The first account created should ideally be a local account (email and password). + +## Basic Configuration: + +### General + +Here's an overview of the general configuration, located in the `.env` file at the root of the LibreChat folder. + + - `ALLOW_EMAIL_LOGIN`: Email login. Set to `true` or `false` to enable or disable ONLY email login. + - `ALLOW_REGISTRATION`: Email registration of new users. Set to `true` or `false` to enable or disable Email registration. + - `ALLOW_SOCIAL_LOGIN`: Allow users to connect to LibreChat with various social networks, see below. Set to `true` or `false` to enable or disable. + - `ALLOW_SOCIAL_REGISTRATION`: Enable or disable registration of new user using various social network. Set to `true` or `false` to enable or disable. + +> **Note:** OpenID does not support the ability to disable only registration. + +>> **Quick Tip:** Even with registration disabled, add users directly to the database using `npm run create-user`. If you can't get npm to work, try `sudo docker exec -ti LibreChat sh` first to "ssh" into the container. +>> **Quick Tip:** To delete a user, you can run `docker-compose exec api npm run delete-user email@domain.com` + +![image](https://github.com/danny-avila/LibreChat/assets/81851188/52a37d1d-7392-4a9a-a79f-90ed2da7f841) + +```bash +ALLOW_EMAIL_LOGIN=true +ALLOW_REGISTRATION=true +ALLOW_SOCIAL_LOGIN=false +ALLOW_SOCIAL_REGISTRATION=false +``` + +### Session Expiry and Refresh Token + +- Default values: session expiry: 15 minutes, refresh token expiry: 7 days + - For more information: **[GitHub PR #927 - Refresh Token](https://github.com/danny-avila/LibreChat/pull/927)** + +```bash +SESSION_EXPIRY=1000 * 60 * 15 +REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7 +``` + +``` mermaid +sequenceDiagram + Client->>Server: Login request with credentials + Server->>Passport: Use authentication strategy (e.g., 'local', 'google', etc.) + Passport-->>Server: User object or false/error + Note over Server: If valid user... + Server->>Server: Generate access and refresh tokens + Server->>Database: Store hashed refresh token + Server-->>Client: Access token and refresh token + Client->>Client: Store access token in HTTP Header and refresh token in HttpOnly cookie + Client->>Server: Request with access token from HTTP Header + Server-->>Client: Requested data + Note over Client,Server: Access token expires + Client->>Server: Request with expired access token + Server-->>Client: Unauthorized + Client->>Server: Request with refresh token from HttpOnly cookie + Server->>Database: Retrieve hashed refresh token + Server->>Server: Compare hash of provided refresh token with stored hash + Note over Server: If hashes match... + Server-->>Client: New access token and refresh token + Client->>Server: Retry request with new access token + Server-->>Client: Requested data +``` + +### JWT Secret and Refresh Secret + +- You should use new secure values. The examples given are 32-byte keys (64 characters in hex). + - Use this replit to generate some quickly: **[JWT Keys](https://replit.com/@daavila/crypto#index.js)** + +```bash +JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef +JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418 +``` + +--- + +## Automated Moderation System (optional) + +The Automated Moderation System is enabled by default. It uses a scoring mechanism to track user violations. As users commit actions like excessive logins, registrations, or messaging, they accumulate violation scores. Upon reaching a set threshold, the user and their IP are temporarily banned. This system ensures platform security by monitoring and penalizing rapid or suspicious activities. + +To set up the mod system, review [the setup guide](../../features/mod_system.md). + +> *Please Note: If you want this to work in development mode, you will need to create a file called `.env.development` in the root directory and set `DOMAIN_CLIENT` to `http://localhost:3090` or whatever port is provided by vite when runnning `npm run frontend-dev`* + +--- + +## **Email and Password Reset** + +### General setup + +in the .env file modify these variables: + +``` +EMAIL_SERVICE= # eg. gmail - see https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/ +EMAIL_HOST= # eg. example.com - if EMAIL_SERVICE is not set, connect to this server. +EMAIL_PORT=25 # eg. 25 - mail server port to connect to with EMAIL_HOST (usually 25, 465, 587) +EMAIL_ENCRYPTION= # eg. starttls - valid values: starttls (force STARTTLS), tls (obligatory TLS), anything else (use STARTTLS if available) +EMAIL_ENCRYPTION_HOSTNAME= # eg. example.com - check the name in the certificate against this instead of EMAIL_HOST +EMAIL_ALLOW_SELFSIGNED= # eg. true - valid values: true (allow self-signed), anything else (disallow self-signed) +EMAIL_USERNAME= # eg. me@gmail.com - the username used for authentication. For consumer services, this MUST usually match EMAIL_FROM. +EMAIL_PASSWORD= # eg. password - the password used for authentication +EMAIL_FROM_NAME= # eg. LibreChat - the human-readable address in the From is constructed as "EMAIL_FROM_NAME ". Defaults to APP_TITLE. +``` + +If you want to use one of the predefined services, configure only these variables: + +EMAIL\_SERVICE is the name of the email service you are using (Gmail, Outlook, Yahoo Mail, ProtonMail, iCloud Mail, etc.) as defined in the NodeMailer well-known services linked above. +EMAIL\_USERNAME is the username of the email service (usually, it will be the email address, but in some cases, it can be an actual username used to access the account). +EMAIL\_PASSWORD is the password used to access the email service. This is not the password to access the email account directly, but a password specifically generated for this service. +EMAIL\_FROM is the email address that will appear in the "from" field when a user receives an email. +EMAIL\_FROM\_NAME is the name that will appear in the "from" field when a user receives an email. If left unset, it defaults to the app title. + +If you want to use a generic SMTP service or need advanced configuration for one of the predefined providers, configure these variables: + +EMAIL\_HOST is the hostname to connect to, or an IP address. +EMAIL\_PORT is the port to connect to. Be aware that different ports usually come with different requirements - 25 is for mailserver-to-mailserver, 465 requires encryption at the start of the connection, and 587 allows submission of mail as a user. +EMAIL\_ENCRYPTION defines if encryption is required at the start (`tls`) or started after the connection is set up (`starttls`). If either of these values are set, they are enforced. If they are not set, an encrypted connection is started if available. +EMAIL\_ENCRYPTION\_HOSTNAME allows specification of a hostname against which the certificate is validated. Use this if the mail server does have a valid certificate, but you are connecting with an IP or a different name for some reason. +EMAIL\_ALLOW\_SELFSIGNED defines whether self-signed certificates can be accepted from the server. As the mails being sent contain sensitive information, ONLY use this for testing. + +NOTE: ⚠️ **Failing to perform either of the below setups will result in LibreChat using the unsecured password reset! This allows anyone to reset any password on your server immediately, without mail being sent at all!** The variable EMAIL\_FROM does not support all email providers **but is still required**. To stay updated, check the bug fixes: **[here](https://github.com/danny-avila/LibreChat/tags)** + +### Setup with Gmail + +1. Create a Google Account and enable 2-step verification. +2. In the **[Google Account settings](https://myaccount.google.com/)**, click on the "Security" tab and open "2-step verification." +3. Scroll down and open "App passwords." Choose "Mail" for the app and select "Other" for the device, then give it a random name. +4. Click on "Generate" to create a password, and copy the generated password. +5. In the .env file, modify the variables as follows: + +``` +EMAIL_SERVICE=gmail +EMAIL_USERNAME=your-email +EMAIL_PASSWORD=your-app-password +EMAIL_FROM=email address for the from field, e.g., noreply@librechat.ai +EMAIL_FROM_NAME="My LibreChat Server" +``` + +### Setup with custom mail server + +1. Gather your SMTP login data from your provider. The steps are different for each, but they will usually list values for all variables. +2. In the .env file, modify the variables as follows, assuming some sensible example values: + +``` +EMAIL_HOST=mail.example.com +EMAIL_PORT=587 +EMAIL_ENCRYPTION=starttls +EMAIL_USERNAME=your-email +EMAIL_PASSWORD=your-app-password +EMAIL_FROM=email address for the from field, e.g., noreply@librechat.ai +EMAIL_FROM_NAME="My LibreChat Server" +``` + +--- + +## Social Authentication - Setup and Configuration + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/cacc2ee0-acf9-4d05-883a-ca9952de1165) + +### Discord + +#### Create a new Discord Application + +- Go to **[Discord Developer Portal](https://discord.com/developers)** + +- Create a new Application and give it a name + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/7e7cdfa0-d1d6-4b6b-a8a9-905aaa40d135) + +#### Discord Application Configuration + +- In the OAuth2 general settings add a valid redirect URL: + - Example for localhost: `http://localhost:3080/oauth/discord/callback` + - Example for a domain: `https://example.com/oauth/discord/callback` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/6c56fb92-f4ab-43b9-981b-f98babeeb19d) + +- In `Default Authorization Link`, select `In-app Authorization` and set the scopes to `applications.commands` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/2ce94670-9422-48d2-97e9-ec40bd331573) + +- Save changes and reset the Client Secret + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/3af164fc-66ed-4e5e-9f5a-9bcab3df37b4) +![image](https://github.com/danny-avila/LibreChat/assets/32828263/2ece3935-68e6-4f2e-8656-9721cba5388a) + +#### .env Configuration + +- Paste your `Client ID` and `Client Secret` in the `.env` file: + +```bash +DISCORD_CLIENT_ID=your_client_id +DISCORD_CLIENT_SECRET=your_client_secret +DISCORD_CALLBACK_URL=/oauth/discord/callback +``` + +- Save the `.env` file + +> Note: If using docker, run `docker-compose up -d` to apply the .env configuration changes + +--- + +### Facebook - WIP + +> ⚠️ **Warning: Work in progress, not currently functional** + +> ❗ Note: Facebook Authentication will not work from `localhost` + +#### Create a Facebook Application + +- Go to the **[Facebook Developer Portal](https://developers.facebook.com/)** + +- Click on "My Apps" in the header menu + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/b75ccb8b-d56b-41b7-8b0d-a32c2e762962) + +- Create a new application + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/706f050d-5423-44cc-80f0-120913695d8f) + +- Select "Authenticate and request data from users with Facebook Login" + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/2ebbb571-afe8-429e-ab39-be6e83d12c01) + +- Choose "No, I'm not creating a game" + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/88b5160a-9c72-414a-bbcc-7717b81106f3) + +- Provide an `app name` and `App contact email` and click `Create app` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/e1282c9e-4e7d-4cbe-82c9-cc76967f83e1) + +#### Facebook Application Configuration + +- In the side menu, select "Use cases" and click "Customize" under "Authentication and account creation." + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/39f4bb70-d9dc-4d1c-8443-2666fe56499b) + +- Add the `email permission` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/dfa20879-2cb8-4daf-883d-3790854afca0) + +- Now click `Go to settings` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/512213a2-bd8b-4fd3-96c7-0de6d3222ddd) + +- Ensure that `Client OAuth login`, `Web OAuth login` and `Enforce HTTPS` are **enabled**. + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/3a7d935b-97bf-493b-b909-39ecf9b3432b) + +- Add a `Valid OAuth Redirect URIs` and "Save changes" + - Example for a domain: `https://example.com/oauth/facebook/callback` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/ef8e54ee-a766-4871-9719-d4eff7a770b6) + +- Click `Go back` and select `Basic` in the `App settings` tab + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/0d14f702-5183-422e-a12c-5d1b6031581b) + +- Click "Show" next to the App secret. + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/9a009e37-2bb6-4da6-b5c7-9139c3db6185) + +#### .env Configuration + +- Copy the `App ID` and `App Secret` and paste them into the `.env` file as follows: + +```bash +FACEBOOK_CLIENT_ID=your_app_id +FACEBOOK_CLIENT_SECRET=your_app_secret +FACEBOOK_CALLBACK_URL=/oauth/facebook/callback +``` + +- Save the `.env` file. + +> Note: If using docker, run `docker-compose up -d` to apply the .env configuration changes + +--- + +### GitHub + +#### Create a GitHub Application + +- Go to your **[Github Developer settings](https://github.com/settings/apps)** +- Create a new Github app + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/3a8b88e7-78f8-426e-bfc2-c5e3f8b21ccb) + +#### GitHub Application Configuration + +- Give it a `GitHub App name` and set your `Homepage URL` + - Example for localhost: `http://localhost:3080` + - Example for a domain: `https://example.com` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/f10d497d-460b-410f-9504-08735662648b) + +- Add a valid `Callback URL`: + - Example for localhost: `http://localhost:3080/oauth/github/callback` + - Example for a domain: `https://example.com/oauth/github/callback` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/4e7e6dba-0afb-4ed8-94bf-4c61b0f29240) + +- Uncheck the box labeled `Active` in the `Webhook` section + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/aaeb3ecb-2e76-4ea5-8264-edfbdd53de1a) + +- Scroll down to `Account permissions` and set `Email addresses` to `Access: Read-only` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/3e561aa4-1f9e-4cb7-ace8-dbba8f0c0d55) + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/7b5f99af-7bde-43ee-9b43-6d3ce79ee00a) + +- Click on `Create GitHub App` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/4cc48550-eac3-4970-939b-81a23fa9c7cf) + +#### .env Configuration + +- Click `Generate a new client secret` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/484c7851-71dd-4167-a59e-9a56c4e08c36) + +- Copy the `Client ID` and `Client Secret` in the `.env` file + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/aaf78840-48a9-44e1-9625-4109ed91d965) + +```bash +GITHUB_CLIENT_ID=your_client_id +GITHUB_CLIENT_SECRET=your_client_secret +GITHUB_CALLBACK_URL=/oauth/github/callback +``` + +- Save the `.env` file + +> Note: If using docker, run `docker-compose up -d` to apply the .env configuration changes + +--- + +### Google + +#### Create a Google Application + +- Visit: **[Google Cloud Console](https://cloud.google.com)** and open the `Console` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/a7d290ea-6031-43b3-b367-36ce00e46f20) + +- Create a New Project and give it a name + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/ce71c9ca-7ddd-4021-9133-a872c64c20c4) + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/8abbd41e-8332-4851-898d-9cddb373c527) + +#### Google Application Configuration + +- Select the project you just created and go to `APIs and Services` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/c6265582-2cf6-430f-ae51-1edbdd9f2c48) + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/006e16ba-56b8-452d-b324-5f2d202637ab) + +- Select `Credentials` and click `CONFIGURE CONSENT SCREEN` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/e4285cbb-833f-4366-820d-addf04a2ad77) + +- Select `External` then click `CREATE` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/232d46c0-dd00-4637-b538-3ba3bdbdc0b2) + +- Fill in your App information + +> Note: You can get a logo from your LibreChat folder here: `docs\assets\favicon_package\android-chrome-192x192.png` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/e6c4c8ec-2f02-4af5-9458-c72394d0b7c5) + +- Configure your `App domain` and add your `Developer contact information` then click `SAVE AND CONTINUE` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/6c2aa557-9b9b-412d-bc2b-76a0dc11f394) + +- Configure the `Sopes` + - Add `email`,`profile` and `openid` + - Click `UPDATE` and `SAVE AND CONTINUE` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/46af2fb9-8cfd-41c5-a763-814b308e45c3) + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/4e832970-d392-4c67-bb38-908a5c51660a) + +- Click `SAVE AND CONTINUE` +- Review your app and go back to dashboard + +- Go back to the `Credentials` tab, click on `+ CREATE CREDENTIALS` and select `OAuth client ID` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/beef1982-55a3-4837-8e8c-20bad8d846ba) + +- Select `Web application` and give it a name + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/badde864-f6b5-468f-a72f-bac93326ffa5) + +- Configure the `Authorized JavaScript origins`, you can add both your domain and localhost if you desire + - Example for localhost: `http://localhost:3080` + - Example for a domain: `https://example.com` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/f7e3763a-5f74-4850-8638-44f81693b9ac) + +- Add a valid `Authorized redirect URIs` + - Example for localhost: `http://localhost:3080/oauth/google/callback` + - Example for a domain: `https://example.com/oauth/google/callback` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/0db34b19-d780-4651-9c2f-d33e24a74d55) + +#### .env Configuration + +- Click `CREATE` and copy your `Client ID` and `Client secret` + +![image](https://github.com/danny-avila/LibreChat/assets/138638445/fa8572bf-f482-457a-a285-aec7d41af76b) + +- Add them to your `.env` file: + +```bash +GOOGLE_CLIENT_ID=your_client_id +GOOGLE_CLIENT_SECRET=your_client_secret +GOOGLE_CALLBACK_URL=/oauth/github/callback +``` + +- Save the `.env` file + +> Note: If using docker, run `docker-compose up -d` to apply the .env configuration changes + +--- + +### OpenID with AWS Cognito + +#### Create a new User Pool in Cognito + +- Visit: **[https://console.aws.amazon.com/cognito/](https://console.aws.amazon.com/cognito/)** +- Sign in as Root User +- Click on `Create user pool` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/e9b412c3-2cf1-4f54-998c-d1d6c12581a5) + +#### Configure sign-in experience + +Your Cognito user pool sign-in options should include `User Name` and `Email`. + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/d2cf362d-469e-4993-8466-10282da114c2) + +#### Configure Security Requirements + +You can configure the password requirements now if you desire + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/e125e8f1-961b-4a38-a6b7-ed1faf29c4a3) + +#### Configure sign-up experience + +Choose the attributes required at signup. The minimum required is `name`. If you want to require users to use their full name at sign up use: `given_name` and `family_name` as required attributes. + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/558b8e2c-afbd-4dd1-87f3-c409463b5f7c) + +#### Configure message delivery + +Send email with Cognito can be used for free for up to 50 emails a day + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/fcb2323b-708e-488c-9420-7eb482974648) + +#### Integrate your app + +Select `Use Cognitio Hosted UI` and chose a domain name + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/111b3dd4-3b20-4e3e-80e1-7167d2ad0f62) + +Set the app type to `Confidential client` +Make sure `Generate a client secret` is set. +Set the `Allowed callback URLs` to `https://YOUR_DOMAIN/oauth/openid/callback` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/1f92a532-7c4d-4632-a55d-9d00bf77fc4d) + +Under `Advanced app client settings` make sure `Profile` is included in the `OpenID Connect scopes` (in the bottom) + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/5b035eae-4a8e-482c-abd5-29cee6502eeb) + +#### Review and create +You can now make last minute changes, click on `Create user pool` when you're done reviewing the configuration + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/dc8b2374-9adb-4065-85dc-a087d625372d) + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/67efb1e9-dfe3-4ebd-9ebb-92186c514b5c) + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/9f819175-ace1-44b1-ba68-af21ac9f6735) + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/3e7b8b17-4e12-49af-99cf-78981d6331df) + +#### Get your environment variables + +1. Open your User Pool + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/b658ff2a-d252-4f3d-90a7-9fbde42c01db) + +2. The `User Pool ID` and your AWS region will be used to construct the `OPENID_ISSUER` (see below) + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/dc8ae403-cbff-4aae-9eee-42d7cf3485e7) +![image](https://github.com/danny-avila/LibreChat/assets/32828263/d606f5c8-c60b-4d20-bdb2-d0d69e49ea1e) + +3. Go to the `App Integrations` tab + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/58713bdc-24bc-47de-bdca-020dc321e997) + +4. Open the app client + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/271bf7d2-3df2-43a7-87fc-e50294e49b2e) + +5. Toggle `Show Client Secret` + +![image](https://github.com/danny-avila/LibreChat/assets/32828263/a844fe65-313d-4754-81b4-380336e0e336) + +- Use the `Client ID` for `OPENID_CLIENT_ID` + +- Use the `Client secret` for `OPENID_CLIENT_SECRET` + +- Generate a random string for the `OPENID_SESSION_SECRET` + +> The `OPENID_SCOPE` and `OPENID_CALLBACK_URL` are pre-configured with the correct values + +6. Open the `.env` file at the root of your LibreChat folder and add the following variables with the values you copied: + +```bash +OPENID_CLIENT_ID=Your client ID +OPENID_CLIENT_SECRET=Your client secret +OPENID_ISSUER=https://cognito-idp.[AWS REGION].amazonaws.com/[USER POOL ID]/.well-known/openid-configuration +OPENID_SESSION_SECRET=Any random string +OPENID_SCOPE=openid profile email +OPENID_CALLBACK_URL=/oauth/openid/callback +``` +7. Save the .env file + +> Note: If using docker, run `docker-compose up -d` to apply the .env configuration changes + + +--- + +### OpenID with Azure AD + +1. Go to the [Azure Portal](https://portal.azure.com/) and sign in with your account. +2. In the search box, type "Azure Active Directory" and click on it. +3. On the left menu, click on App registrations and then on New registration. +4. Give your app a name and select Web as the platform type. +5. In the Redirect URI field, enter `http://localhost:3080/oauth/openid/callback` and click on Register. +6. You will see an Overview page with some information about your app. Copy the Application (client) ID and the Directory (tenant) ID and save them somewhere. +7. On the left menu, click on Authentication and check the boxes for Access tokens and ID tokens under Implicit grant and hybrid flows. +8. On the left menu, click on Certificates & Secrets and then on New client secret. Give your secret a name and an expiration date and click on Add. +9. You will see a Value column with your secret. Copy it and save it somewhere. Don't share it with anyone! +10. Open the .env file in your project folder and add the following variables with the values you copied: + +```bash +OPENID_CLIENT_ID=Your Application (client) ID +OPENID_CLIENT_SECRET=Your client secret +OPENID_ISSUER=https://login.microsoftonline.com/Your Directory (tenant ID)/v2.0/ +OPENID_SESSION_SECRET=Any random string +OPENID_SCOPE=openid profile email #DO NOT CHANGE THIS +OPENID_CALLBACK_URL=/oauth/openid/callback # this should be the same for everyone +``` +11. Save the .env file + +> Note: If using docker, run `docker-compose up -d` to apply the .env configuration changes + + +--- \ No newline at end of file diff --git a/docs/install/index.md b/docs/install/index.md new file mode 100644 index 00000000000..e6177b07b40 --- /dev/null +++ b/docs/install/index.md @@ -0,0 +1,27 @@ +--- +title: Installation and Configuration +description: 💻 In-depth guides about installation and configuration +weight: 1 +--- + +# Installation and Configuration + +## **[Installation](./installation/index.md)** + + * 🐳 [Docker Compose (✨ Recommended)](./installation/docker_compose_install.md) + * 🦦 [Container (Podman)](./installation/container_install.md) + * 🐧 [Linux](./installation/linux_install.md) + * 🍎 [Mac](./installation/mac_install.md) + * 🪟 [Windows](./installation/windows_install.md) + +## **[Configuration](./configuration/index.md)** + + * ⚙️ [Environment Variables](./configuration/dotenv.md) + * 🐋 [Docker Compose Override](./configuration/docker_override.md) + * 🤖 [AI Setup](./configuration/ai_setup.md) + * 🚅 [LiteLLM](./configuration/litellm.md) + * 💸 [Free AI APIs](./configuration/free_ai_apis.md) + * 🛂 [Authentication System](./configuration/user_auth_system.md) + * 🍃 [Online MongoDB](./configuration/mongodb.md) + * 🌍 [Default Language](./configuration/default_language.md) + * 🌀 [Miscellaneous](./configuration/misc.md) \ No newline at end of file diff --git a/docs/install/container_install.md b/docs/install/installation/container_install.md similarity index 91% rename from docs/install/container_install.md rename to docs/install/installation/container_install.md index f952d4a99a5..5f47f1e44cd 100644 --- a/docs/install/container_install.md +++ b/docs/install/installation/container_install.md @@ -1,12 +1,16 @@ +--- +title: 🦦 Container (Podman) +description: Install LibreChat using Podman. If you don't like docker compose, don't want a bare-metal installation, but still want to leverage the benefits from the isolation and modularity of containers... +weight: 0 +--- -# Container installation guide +# Container Installation Guide (Podman) If you don't like docker compose, don't want a bare-metal installation, but still want to leverage the benefits from the isolation and modularity of containers - this is the guide you should use. > Likewise, If you are actively developing LibreChat, aren't using the service productively (i.e production environments), you should avoid this guide and look to something easier to work with such as docker compose. -**Important:** `docker` and `podman` commands are for the most part, interoperable and interchangeable. Since podman should be the better "Libre" choice, code instructions below will use (and heavily favor) `podman` - and some commands will need to be tweaked to compensate for this. - +**Important:** `docker` and `podman` commands are for the most part, interoperable and interchangeable. The code instructions below will use (and heavily favor) `podman`. ## Creating the base image @@ -45,7 +49,7 @@ cp ./LibreChat/.env.example .env This will add the env file to the top level directory that we will create the containers, allowing us to pass it easily as via the `--env-file` command argument. -Follow [this guide](https://docs.librechat.ai/install/free_ai_apis.html) to populate the containers with the correct env values for various apis. There are other env values of interest that might be worth changing, documented within the env itself. Afterwords, edit the following lines in the `.env` file. +Follow [this guide](../configuration/ai_setup.md) to populate the containers with the correct env values for various apis. There are other env values of interest that might be worth changing, documented within the env itself. Afterwords, edit the following lines in the `.env` file. ``` HOST=0.0.0.0 @@ -178,7 +182,7 @@ podman volume export librechat-meilisearch-data --output "librechat-meilisearch- podman volume export librechat-mongodb-data --output "librechat-mongodb-backup-$(date +"%d-%m-%Y").tar" ``` -These will leave archive files that you can do what you wish with, including reverting volumes to a previous state if needed. Refer to [podman documentation](https://docs.podman.io/en/latest/markdown/podman-volume-import.1.html) for how to do this. +These will leave archive files that you can do what you wish with, including reverting volumes to a previous state if needed. Refer to the **[official podman documentation](https://docs.podman.io/en/latest/markdown/podman-volume-import.1.html)** for how to do this. ## Updating LibreChat diff --git a/docs/install/docker_compose_install.md b/docs/install/installation/docker_compose_install.md similarity index 56% rename from docs/install/docker_compose_install.md rename to docs/install/installation/docker_compose_install.md index f44b8f36cef..c0f9864839d 100644 --- a/docs/install/docker_compose_install.md +++ b/docs/install/installation/docker_compose_install.md @@ -1,8 +1,15 @@ +--- +title: 🐳 Docker Compose ✨(Recommended) +description: "Docker Compose Installation Guide: Docker Compose installation is recommended for most use cases. It's the easiest, simplest, and most reliable method to get started." +weight: -10 +--- + # Docker Compose Installation Guide Docker Compose installation is recommended for most use cases. It's the easiest, simplest, and most reliable method to get started. -See the video guide for [Windows](windows_install.md#recommended) or [Ubuntu 22.04 LTS](linux_install.md#recommended) +If you prefer to watch a video, we have video guides for [Windows](./windows_install.md#recommended) and [Ubuntu 22.04 LTS](./linux_install.md#recommended) + ## Installation and Configuration ### Preparation @@ -13,7 +20,7 @@ Start by cloning the repository or downloading it to your desired location: ``` ### Docker Installation -Install Docker on your system. [Docker Desktop](https://www.docker.com/products/docker-desktop/) is recommended for managing your Docker containers. +Install Docker on your system. **[Docker Desktop](https://www.docker.com/products/docker-desktop/)** is recommended for managing your Docker containers. ### LibreChat Configuration Before running LibreChat with Docker, you need to configure some settings: @@ -21,15 +28,15 @@ Before running LibreChat with Docker, you need to configure some settings: - Edit the credentials you see in `docker-compose.yml` under the API service as needed. - See my notes below for specific instructions on some of the configuration - Provide all necessary credentials in the `.env` file before the next step. - - Docker will read this env file. See the `.env.example` file for reference. + - Docker will read this env file. See the **[/.env.example](https://github.com/danny-avila/LibreChat/blob/main/.env.example)** file for reference. -#### [API Keys and Tokens Setup](apis_and_tokens.md) (Required) -You must set up at least one of these tokens or APIs to run the app. +#### [AI Setup](../configuration/ai_setup.md) (Required) +At least one AI endpoint should be setup for use. -#### [Manage Your MongoDB Database](../features/manage_your_database.md) (Optional) +#### [Manage Your MongoDB Database](../../features/manage_your_database.md) (Optional) Safely access and manage your MongoDB database using Mongo Express -#### [User Authentication System Setup](../install/user_auth_system.md) (Optional) +#### [User Authentication System Setup](../configuration/user_auth_system.md) (Optional) How to set up the user/auth system and Google login. ### Running LibreChat @@ -75,7 +82,23 @@ docker-compose up ### Config notes for docker-compose.yml file +Modification to the `docker-compose.yml` should be made with `docker-compose.override.yml` whenever possible to prevent conflicts when updating. You can create a new file named `docker-compose.override.yml` in the same directory as your main `docker-compose.yml` file for LibreChat, where you can set your .env variables as needed under `environment`, or modify the default configuration provided by the main `docker-compose.yml`, without the need to directly edit or duplicate the whole file. + +For more info see: + +- Our quick guide: + - **[Docker Override](../configuration/docker_override.md)** + +- The official docker documentation: + - **[docker docs - understanding-multiple-compose-files](https://docs.docker.com/compose/multiple-compose-files/extends/#understanding-multiple-compose-files)** + - **[docker docs - merge-compose-files](https://docs.docker.com/compose/multiple-compose-files/merge/#merge-compose-files)** + - **[docker docs - specifying-multiple-compose-files](https://docs.docker.com/compose/reference/#specifying-multiple-compose-files)** + +- You can also view an example of an override file for LibreChat in your LibreChat folder and on GitHub: + - **[docker-compose.override.example](https://github.com/danny-avila/LibreChat/blob/main/docker-compose.override.yaml.example)** + - Any environment variables set in your compose file will override variables with the same name in your .env file. Note that the following variables are necessary to include in the compose file so they work in the docker environment, so they are included for you. + ```yaml env_file: - .env @@ -91,55 +114,27 @@ docker-compose up environment: - MEILI_HOST=http://meilisearch:7700 - MEILI_HTTP_ADDR=meilisearch:7700 - ``` -- If you'd like to change the app title, edit the following lines (the ones in your .env file are not read during building) -```yaml - args: - APP_TITLE: LibreChat # default, change to your desired app name ``` - If for some reason you're not able to build the app image, you can pull the latest image from **Dockerhub**. -- Comment out the following lines (CTRL+/ on most IDEs, or put a `#` in front each line) - +- Create a new file named `docker-compose.override.yml` in the same directory as your main `docker-compose.yml` with this content: ```yaml - image: node # Comment this & uncomment below to build from docker hub image - build: - context: . - target: node - args: - APP_TITLE: LibreChat # default, change to your desired app name -``` - -- Comment this line in (remove the `#` key) - +version: '3.4' -```yaml - # image: ghcr.io/danny-avila/librechat:latest # Uncomment this & comment above to build from docker hub image +services: + api: + image: ghcr.io/danny-avila/librechat-dev:latest ``` -- **Note:** The latest Dockerhub image is only updated with new release tags, so it may not have the latest changes to the main branch -- You also can't edit the title or toggle google login off as shown above, as these variables are set during build time. -- If you are running APIs in other docker containers that you need access to, you will need to uncomment the following lines -```yaml - # extra_hosts: # if you are running APIs on docker you need access to, you will need to uncomment this line and next - # - "host.docker.internal:host-gateway" -``` +- Then use `docker-compose build` as you would normally - - Usually, these are reverse proxies, which you can set as shown below under `environment:` +- **Note:** There are different Dockerhub images. the `librechat:latest` image is only updated with new release tags, so it may not have the latest changes to the main branch. To get the latest changes you can use `librechat-dev:latest` instead -```yaml - environment: - - HOST=0.0.0.0 - - MONGO_URI=mongodb://mongodb:27017/LibreChat - - CHATGPT_REVERSE_PROXY=http://host.docker.internal:8080/api/conversation # if you are hosting your own chatgpt reverse proxy with docker - - OPENAI_REVERSE_PROXY=http://host.docker.internal:8070/v1/chat/completions # if you are hosting your own chatgpt reverse proxy with docker -``` - ### **[LibreChat on Docker Hub](https://hub.docker.com/r/chatgptclone/app/tags)** -### **[Create a MongoDB database](mongodb.md)** (Not required if you'd like to use the local database installed by Docker) +### **[Create a MongoDB database](../configuration/mongodb.md)** (Not required if you'd like to use the local database installed by Docker) --- diff --git a/docs/install/installation/index.md b/docs/install/installation/index.md new file mode 100644 index 00000000000..1b20ac676b2 --- /dev/null +++ b/docs/install/installation/index.md @@ -0,0 +1,12 @@ +--- +title: Installation +description: 🧑‍💻 This section contains the installation guides for Docker, Podman, Windows, Mac and Linux. +weight: 1 +--- + +# Installation + * 🐳 [Docker Compose (✨ Recommended)](docker_compose_install.md) + * 🦦 [Container (Podman)](container_install.md) + * 🐧 [Linux](linux_install.md) + * 🍎 [Mac](mac_install.md) + * 🪟 [Windows](windows_install.md) \ No newline at end of file diff --git a/docs/install/linux_install.md b/docs/install/installation/linux_install.md similarity index 82% rename from docs/install/linux_install.md rename to docs/install/installation/linux_install.md index 439c5441373..d46db47a97d 100644 --- a/docs/install/linux_install.md +++ b/docs/install/installation/linux_install.md @@ -1,3 +1,8 @@ +--- +title: 🐧 Linux +description: Linux Installation Guides +weight: 0 +--- # Linux Installation Guide ## **Recommended:** @@ -32,13 +37,14 @@ Here are the steps to follow: Note: If you run the command on the same computer and want to access it, navigate to `localhost:3080`. You should see a login page where you can create or sign in to your account. Then you can choose an AI model and start chatting. -- [Manage Your MongoDB Database (optional)](../features/manage_your_database.md) +- [Manage Your MongoDB Database (optional)](../../features/manage_your_database.md) Safely access and manage your MongoDB database using Mongo Express Have fun! ---- -## **[Docker Install](docker_compose_install.md)** (General documentation) +> Note: See the [Docker Compose Install Guide](./docker_compose_install.md) for more details +- 👆 Docker Compose installation is recommended for most use cases. It's the easiest, simplest, and most reliable method to get started. + --- ## **Manual Installation:** @@ -68,17 +74,17 @@ Note: The above command extracts the files to "/usr/local/LibreChat". If you wan ## Enable the Conversation search feature: (optional) -- Download MeiliSearch latest release from: https://github.com/meilisearch/meilisearch/releases -- Copy it to "/usr/local/LibreChat/" -- Rename the file to "meilisearch" -- Open a terminal and navigate to "/usr/local/LibreChat/" +- Download MeiliSearch latest release from: **[github.com/meilisearch](https://github.com/meilisearch/meilisearch/releases)** +- Copy it to `/usr/local/LibreChat/` +- Rename the file to `meilisearch` +- Open a terminal and navigate to `/usr/local/LibreChat/` - Run the following command: ```bash ./meilisearch --master-key=YOUR_MASTER_KEY ``` -Note: Replace "YOUR_MASTER_KEY" with the generated master key, which you saved earlier. +Note: Replace `YOUR_MASTER_KEY` with the generated master key, which you saved earlier. ## Install Node.js: @@ -89,12 +95,12 @@ curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejs ``` -## [Create a MongoDB database](mongodb.md) (Required) +## [Create a MongoDB database](../configuration/mongodb.md) (Required) -## [Get Your API keys and Tokens](apis_and_tokens.md) (Required) -- You must set up at least one of these tokens or APIs to run the app. +## [Setup your AI Endpoints](../configuration/ai_setup.md) (Required) +- At least one AI endpoint should be setup for use. -## [User/Auth System](../install/user_auth_system.md) (Optional) +## [User/Auth System](../configuration/user_auth_system.md) (Optional) - How to set up the user/auth system and Google login. ## Run the project @@ -137,28 +143,6 @@ If you're having issues running this command, you can try running what the scrip Prefix commands with `sudo` according to your environment permissions. -### Docker - -```bash -# Fetch the latest changes from Github -git fetch origin -# Switch to the repo's main branch -git checkout main -# Pull the latest changes to the main branch from Github -git pull origin main -# Prune all LibreChat Docker images -docker rmi librechat:latest -# Remove all unused dangling Docker images -docker image prune -f -# Building a new LibreChat image -docker-compose build - -# Start LibreChat -docker-compose up -``` - -### Local (npm) - ```bash # Bash Terminal diff --git a/docs/install/installation/mac_install.md b/docs/install/installation/mac_install.md new file mode 100644 index 00000000000..f9190bc9c89 --- /dev/null +++ b/docs/install/installation/mac_install.md @@ -0,0 +1,143 @@ +--- +title: 🍎 Mac +description: Mac Installation Guides +weight: 0 +--- + +# Mac Installation Guide +## **Recommended : [Docker Install](docker_compose_install.md)** +- 👆 Docker Compose installation is recommended for most use cases. It's the easiest, simplest, and most reliable method to get started. + +--- + +## **Manual Installation** + +### Install the prerequisites (Required) +- Install Homebrew (if not already installed) by following the instructions on **[brew.sh](https://brew.sh/)** +- Install Node.js and npm by running `brew install node` + +### Download LibreChat (Required) +- Open Terminal and clone the repository by running `git clone https://github.com/danny-avila/LibreChat.git` +- Change into the cloned directory by running `cd LibreChat` +- Create a .env file by running `cp .env.example .env` +- Install dependencies by running: `npm ci` +- Build the client by running: `npm run frontend` + +> You will only need to add your `MONGO_URI` (next step) for LibreChat to work. Make sure LibreChat works with the basic configuration first, you can always come back to the `.env` later for advanced configurations. See: [.env configuration](../configuration/dotenv.md) + +### Create a MongoDB database (Required) +- [Create an online MongoDB database](../configuration/mongodb.md) **or** Install MongoDB by running `brew tap mongodb/brew` and `brew install mongodb-community` +- add your `MONGO_URI` in the .env file (use vscode or any text editor) + +> Choose only one option, online or brew. Both have pros and cons + +### [Setup your AI Endpoints](../configuration/ai_setup.md) (Required) +- At least one AI endpoint should be setup for use. + +### [User/Auth System](../configuration/user_auth_system.md) (Optional) +- Set up the user/auth system and various social logins. + +### **Download MeiliSearch for macOS (Optional):** +- This enables the conversation search feature +- You can download the latest MeiliSearch binary for macOS from their GitHub releases page: **[github.com/meilisearch](https://github.com/meilisearch/meilisearch/releases)** + - Look for the file named `meilisearch-macos-amd64` (or the equivalent for your system architecture) and download it. + +- **Make the binary executable:** + - Open Terminal and navigate to the directory where you downloaded the MeiliSearch binary. Run the following command to make it executable: `chmod +x meilisearch-macos-amd64` + +- **Run MeiliSearch:** + - Now that the binary is executable, you can start MeiliSearch by running the following command: `./meilisearch-macos-amd64 --master-key your_master_key_goes_here` + - Replace `your_master_key_goes_here` with your desired master key! + +- MeiliSearch will start running on the default port, which is 7700. You can now use MeiliSearch in your LibreChat project. + +- Remember to include the MeiliSearch URL and Master Key in your .env file. Your .env file should include the following lines: + +``` +MEILISEARCH_URL=http://127.0.0.1:7700 +MEILISEARCH_KEY=your_master_key_goes_here +``` + +> **Important:** use the same master key here and in your .env file. + +- With MeiliSearch running and configured, the LibreChat project should now have the Conversation search feature enabled. + +### Start LibreChat +- In the LibreChat directory, start the application by running `npm run backend` +- **Visit: http://localhost:3080 & enjoy** + +--- + +### Optional but recommended: + +- Create a script to automate the starting process by creating a new file named `librechat.sh` in the LibreChat directory and pasting the following code: + +``` bash title="librechat.sh" +#!/bin/bash +# Replace "your_master_key_goes_here" with your MeiliSearch Master Key +if [ -x "$(command -v ./meilisearch)" ]; then + ./meilisearch --master-key your_master_key_goes_here & +fi +npm run backend +``` + +- Make the script executable by running: `chmod +x librechat.sh` + +- You can now start LibreChat by running: `./librechat.sh` + +--- + +### Update LibreChat + +- Run `npm run update` from the project directory for a clean installation. + +**If you're having issues running this command, you can try running what the script does manually:** + +```bash +# Terminal on macOS, prefix commands with `sudo` as needed +# Step 1: Get the latest changes +# 1a - Fetch the latest changes from Github +git fetch origin + +# 1b - Switch to the repo's main branch +git checkout main + +# 1c - Pull the latest changes to the main branch from Github +git pull origin main + +# Step 2: Delete all node_modules directories +# 2a - Define the list of directories we will delete +directories=( + "." + "./packages/data-provider" + "./client" + "./api" +) + +# 2b - Loop over each directory and delete the node_modules folder if it exists +for dir in "${directories[@]}"; do + nodeModulesPath="$dir/node_modules" + if [ -d "$nodeModulesPath" ]; then + echo "Deleting node_modules in $dir" + rm -rf "$nodeModulesPath" + fi +done + +# Step 3: Clean the npm cache +npm cache clean --force + +# Step 4: Install dependencies +npm ci + +# Step 5: Build client-side (frontend) code +npm run frontend + +# Start LibreChat +npm run backend +``` + +The above assumes that you're using the default Terminal application on macOS and are executing the commands from the project directory. The commands are written in Bash, which is the default shell for macOS (though newer versions use `zsh` by default, but these commands should work there as well). + +--- + +>⚠️ Note: If you're having trouble, before creating a new issue, please search for similar ones on our [#issues thread on our discord](https://discord.gg/weqZFtD9C4) or our [troubleshooting discussion](https://github.com/danny-avila/LibreChat/discussions/categories/troubleshooting) on our Discussions page. If you don't find a relevant issue, feel free to create a new one and provide as much detail as possible. diff --git a/docs/install/windows_install.md b/docs/install/installation/windows_install.md similarity index 73% rename from docs/install/windows_install.md rename to docs/install/installation/windows_install.md index 8ae911a82ff..728dde1e213 100644 --- a/docs/install/windows_install.md +++ b/docs/install/installation/windows_install.md @@ -1,3 +1,9 @@ +--- +title: 🪟 Windows +description: Windows Installation Guides +weight: 0 +--- + # Windows Installation Guide ## **Recommended:** @@ -21,8 +27,8 @@ In this video we're going to install LibreChat on Windows 11 using Docker and Gi #### Instructions - To install LibreChat, you need Docker desktop and Git. Download them from these links: - - Docker desktop: https://www.docker.com/products/docke... - - Git: https://git-scm.com/download/win + - Docker desktop: **[https://docs.docker.com/desktop/install/windows-install/](https://docs.docker.com/desktop/install/windows-install/)** + - Git: **[https://git-scm.com/download/win](https://git-scm.com/download/win)** - Follow the steps in the video to install and run Docker desktop and Git. - Open a terminal in the root of the C drive and enter these commands: - `git clone https://github.com/danny-avila/LibreChat` @@ -31,45 +37,45 @@ In this video we're going to install LibreChat on Windows 11 using Docker and Gi - `docker-compose up` - Visit http://localhost:3080/ to access LibreChat. Create an account and start chatting. -- [Manage Your MongoDB Database (optional)](../features/manage_your_database.md) +- [Manage Your MongoDB Database (optional)](../../features/manage_your_database.md) Safely access and manage your MongoDB database using Mongo Express Have fun! +> Note: See the [Docker Compose Install Guide](./docker_compose_install.md) for more details + +- 👆 Docker Compose installation is recommended for most use cases. It's the easiest, simplest, and most reliable method to get started. + --- ## **Manual Installation** - Install the prerequisites on your machine 👇 ### Download and Install Node.js (Required) - - - Navigate to https://nodejs.org/en/download and to download the latest Node.js version for your OS (The Node.js installer includes the NPM package manager.) + + - Navigate to **[https://nodejs.org/en/download](https://nodejs.org/en/download)** and to download the latest Node.js version for your OS (The Node.js installer includes the NPM package manager.) ### Download and Install Git (Recommended) - Git: https://git-scm.com/download/win -### [Create a MongoDB database](mongodb.md) (Required) - -### [Get Your API keys and Tokens](apis_and_tokens.md) (Required) -- You must set up at least one of these tokens or APIs to run the app. +### [Create a MongoDB database](../configuration/mongodb.md) (Required) + +### [Setup your AI Endpoints](../configuration/ai_setup.md) (Required) +- At least one AI endpoint should be setup for use. ### Download LibreChat (Required) - - (With Git) Open Terminal (command prompt) and clone the repository by running `git clone https://github.com/danny-avila/LibreChat.git` - - Or download the latest release here: https://github.com/danny-avila/LibreChat/releases/ - - Or by clicking on the green code button in the top of the page and selecting "Download ZIP" - - If you downloaded a zip file, extract the content in "C:/LibreChat/" + - Open Terminal (command prompt) and clone the repository by running `git clone https://github.com/danny-avila/LibreChat.git` - **IMPORTANT : If you install the files somewhere else modify the instructions accordingly** ### Enable the Conversation search feature: (optional) - - - Download MeiliSearch latest release from : https://github.com/meilisearch/meilisearch/releases + + - Download MeiliSearch latest release from : **[github.com/meilisearch](https://github.com/meilisearch/meilisearch/releases)** - Copy it to "C:/LibreChat/" - Rename the file to "meilisearch.exe" - Open it by double clicking on it - Copy the generated Master Key and save it somewhere (You will need it later) - -### [User/Auth System](../install/user_auth_system.md) (Optional) +### [User/Auth System](../configuration/user_auth_system.md) (Optional) - How to set up the user/auth system and Google login. ## Setup and Run LibreChat @@ -81,7 +87,7 @@ Using the command line (in the root directory) ### To use the app: 1. Run `npm run backend` 2. Run `meilisearch --master-key ` (Only if SEARCH=TRUE) -3. Visit http://localhost:3080 (default port) & enjoy +3. Visit `http://localhost:3080` (default port) & enjoy ### Using a batch file @@ -90,7 +96,7 @@ Using the command line (in the root directory) - Paste the following code in a new document - The meilisearch executable needs to be at the root of the LibreChat directory - Put your MeiliSearch master key instead of "``" - - Save the file as "C:/LibreChat/LibreChat.bat" + - Save the file as `C:/LibreChat/LibreChat.bat` - you can make a shortcut of this batch file and put it anywhere ```bat title="LibreChat.bat" @@ -109,28 +115,6 @@ Using the command line (in the root directory) If you're having issues running this command, you can try running what the script does manually: -### Docker - -```bash -# Fetch the latest changes from Github -git fetch origin -# Switch to the repo's main branch -git checkout main -# Pull the latest changes to the main branch from Github -git pull origin main -# Prune all LibreChat Docker images -docker rmi librechat:latest -# Remove all unused dangling Docker images -docker image prune -f -# Building a new LibreChat image -docker-compose build - -# Start LibreChat -docker-compose up -``` - -### Local (npm) - ```powershell # Windows PowerShell terminal diff --git a/docs/install/mac_install.md b/docs/install/mac_install.md deleted file mode 100644 index bce2400174e..00000000000 --- a/docs/install/mac_install.md +++ /dev/null @@ -1,164 +0,0 @@ -# Mac Installation Guide -## **Recommended : [Docker Install](docker_compose_install.md)** - ---- - -## **Manual Installation** - -## Install the prerequisites: - - Install Homebrew (if not already installed) by following the instructions on https://brew.sh/ - - Install Node.js and npm by running `brew install node` - - Install MongoDB (if not using Docker) by running `brew tap mongodb/brew` and `brew install mongodb-community` - - ## Instructions: - - - Open Terminal and clone the repository by running `git clone https://github.com/danny-avila/LibreChat.git` - - Change into the cloned directory by running cd LibreChat - - If using MongoDB Atlas, remove &w=majority from the default connection string -Follow the instructions for setting up proxies, access tokens, and user system: - -## [Create a MongoDB database](mongodb.md) (Required) - -## [Get Your API keys and Tokens](apis_and_tokens.md) (Required) -- You must set up at least one of these tokens or APIs to run the app. - -## [User/Auth System](../install/user_auth_system.md) (Optional) -- How to set up the user/auth system and Google login. - -## Setup Instruction - - Create a .env file in the api directory by running `cp .env.example .env` and edit the file with your preferred text editor, adding the required API keys, access tokens, and MongoDB connection string - - Run npm ci from root directory `npm ci` - - Build the client by running `npm run frontend` - -### **Download MeiliSearch for macOS (optional):** - - You can download the latest MeiliSearch binary for macOS from their GitHub releases page: https://github.com/meilisearch/MeiliSearch/releases. Look for the file named meilisearch-macos-amd64 (or the equivalent for your system architecture) and download it. - -### **Make the binary executable:** - - Open Terminal and navigate to the directory where you downloaded the MeiliSearch binary. Run the following command to make it executable: - -``` -chmod +x meilisearch-macos-amd64 -``` - -### **Run MeiliSearch:** - - Now that the binary is executable, you can start MeiliSearch by running the following command, replacing your_master_key_goes_here with your desired master key: - -``` -./meilisearch-macos-amd64 --master-key your_master_key_goes_here -``` - - - MeiliSearch will start running on the default port, which is 7700. You can now use MeiliSearch in your LibreChat project. - - - Remember to include the MeiliSearch URL and Master Key in your .env file in the api directory. Your .env file should include the following lines: - -``` -MEILISEARCH_URL=http://127.0.0.1:7700 -MEILISEARCH_KEY=your_master_key_goes_here -``` - - - With MeiliSearch running and configured, the LibreChat project should now have the Conversation search feature enabled. - - - In the LibreChat directory, start the application by running `npm run backend` -Visit http://localhost:3080 (default port) & enjoy - -## Optional but recommended: - - - Create a script to automate the starting process by creating a new file named start_chatgpt.sh in the LibreChat directory and pasting the following code: - -``` bash title="LibreChat.sh" -#!/bin/bash -# Replace "your_master_key_goes_here" with your MeiliSearch Master Key -if [ -x "$(command -v ./meilisearch)" ]; then - ./meilisearch --master-key your_master_key_goes_here & -fi -npm run backend -``` - -### **Make the script executable by running** - -``` - chmod +x start_chatgpt.sh -``` - -### **Start LibreChat by running** -``` - ./start_chatgpt.sh -``` - - -## **Update** - -- Run `npm run update` from the project directory for a clean installation. - -If you're having issues running this command, you can try running what the script does manually: - -### Docker - -```bash -# Fetch the latest changes from Github -git fetch origin -# Switch to the repo's main branch -git checkout main -# Pull the latest changes to the main branch from Github -git pull origin main -# Prune all LibreChat Docker images -docker rmi librechat:latest -# Remove all unused dangling Docker images -docker image prune -f -# Building a new LibreChat image -docker-compose build - -# Start LibreChat -docker-compose up -``` - -### Local (npm) - -```bash -# Terminal on macOS, prefix commands with `sudo` as needed - -# Step 1: Get the latest changes - -# Fetch the latest changes from Github -git fetch origin -# Switch to the repo's main branch -git checkout main -# Pull the latest changes to the main branch from Github -git pull origin main - -# Step 2: Delete all node_modules directories -# Define the list of directories we will delete -directories=( - "." - "./packages/data-provider" - "./client" - "./api" -) - -# Loop over each directory and delete the node_modules folder if it exists -for dir in "${directories[@]}"; do - nodeModulesPath="$dir/node_modules" - if [ -d "$nodeModulesPath" ]; then - echo "Deleting node_modules in $dir" - rm -rf "$nodeModulesPath" - fi -done - -# Step 3: Clean the npm cache -npm cache clean --force - -# Step 4: Install dependencies -npm ci - -# Step 5: Build client-side (frontend) code -npm run frontend - -# Start LibreChat -npm run backend -``` - -The above assumes that you're using the default Terminal application on macOS and are executing the commands from the project directory. The commands are written in Bash, which is the default shell for macOS (though newer versions use `zsh` by default, but these commands should work there as well). - ---- - ->⚠️ Note: If you're having trouble, before creating a new issue, please search for similar ones on our [#issues thread on our discord](https://discord.gg/weqZFtD9C4) or our [troubleshooting discussion](https://github.com/danny-avila/LibreChat/discussions/categories/troubleshooting) on our Discussions page. If you don't find a relevant issue, feel free to create a new one and provide as much detail as possible. diff --git a/docs/install/user_auth_system.md b/docs/install/user_auth_system.md deleted file mode 100644 index 30a787e9679..00000000000 --- a/docs/install/user_auth_system.md +++ /dev/null @@ -1,262 +0,0 @@ -# User/Auth System - - ->⚠️ Note: If you're having trouble, before creating a new issue, please search for similar ones on our [#issues thread on our discord](https://discord.gg/weqZFtD9C4) or our [troubleshooting discussion](https://github.com/danny-avila/LibreChat/discussions/categories/troubleshooting) on our Discussions page. If you don't find a relevant issue, feel free to create a new one and provide as much detail as possible. - -## **First Time Setup** - -In order for the auth system to function properly, there are some environment variables that are needed. Note that this information is also included in the [/.env.example](/.env.example) file. - -In /.env, you will need to set the following variables: -```bash -# Change the secrets to a secure, random string -JWT_SECRET=secret -JWT_REFRESH_SECRET=refresh_secret - -# Set the expiration delay for the secure cookie with the JWT token -# Delay is in milliseconds e.g. 7 days is 1000*60*60*24*7 - -# Recommended session expiry is 15 minutes. Make it longer if you want the user to be able to revist the page without logging in for a longer duration of time. - -# Recommended refresh token expiry is 7 days -SESSION_EXPIRY=1000 * 60 * 15 -REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7 - -DOMAIN_SERVER=http://localhost:3080 -DOMAIN_CLIENT=http://localhost:3080 -``` - -## Automated Moderation System (optional) - -The Automated Moderation System uses a scoring mechanism to track user violations. As users commit actions like excessive logins, registrations, or messaging, they accumulate violation scores. Upon reaching a set threshold, the user and their IP are temporarily banned. This system ensures platform security by monitoring and penalizing rapid or suspicious activities. - -To set up the mod system, review [the setup guide](../features/mod_system.md). - -*Please Note: If you are wanting this to work in development mode, you will need to create a file called `.env.development` in the root directory and set `DOMAIN_CLIENT` to `http://localhost:3090` or whatever port is provided by vite when runnning `npm run frontend-dev`* - -Important: When you run the app for the first time, you need to create a new account by clicking on "Sign up" on the login page. The first account you make will be the admin account. The admin account doesn't have any special features right now, but it might be useful if you want to make an admin dashboard to manage other users later. - -⚠️ **__For the first time, you should use a local account (email and password) to sign up and log in.__** - ---- - -## **OAuth2/Social Login** - -## Before enabling Social Authentication, set ALLOW_SOCIAL_LOGIN=true in the .env file - -## Google Authentication - -To enable Google login, you must create an application in the [Google Cloud Console](https://cloud.google.com) and provide the client ID and client secret in the `/.env` file. - -1. Go to "APIs and Services" in your Google Cloud account and click on "Credentials". -2. Click on "Configure consent screen" and select "External" as the user type. -3. Add "profile", "email" and "openid" as the scopes for your app. These are the first three checkboxes when you click on "Add or remove scopes". -4. Click on "Save and continue" and then "Back to dashboard". -5. Click on "Create Credentials" and then "OAuth client ID". -6. Select "Web application" as the application type and give it a name. -7. Add `https://yourdomain`, `http://localhost:3080` and `http://localhost:3090` to the authorized JavaScript origins. -8. Add `https://your-domain/oauth/google/callback` to the authorized redirect URIs. (if you use localhost then use this `http://localhost:3080/oauth/google/callback`) -9. Click on "Create" and copy your client ID and client secret. -10. Paste them into your `/.env` file. -11. Enable the feature in the `/.env` file - ---- - -## Facebook Authentication -### (It only works with a domain, not with localhost) - -1. Go to the [Facebook Developer Portal](https://developers.facebook.com/). -2. Create a new application and select "Authenticate and request data from users with Facebook Login." -3. Choose "No, I'm not creating a game" and provide a name for your application. -4. In the Dashboard tab, go to "Use cases" and click "Customize" under "Authentication and account creation." Add the email permission by clicking "add" under email's permission. -5. In the settings section, click "go to settings." Ensure that "Client OAuth login," "Web OAuth login," and "Enforce HTTPS" are **enabled**. -6. Add `your-domain/oauth/facebook/callback` to the Valid OAuth Redirect URIs (e.g., https://example.com/oauth/facebook/callback). -7. Save changes. In the "App settings" tab, click "show" next to the App secret. -8. Copy the Client ID and Client Secret and paste them into the .env file as follows: (`App ID=Client ID` & `App secret=Client Secret`) - -```bash -FACEBOOK_CLIENT_ID=your_client_id -FACEBOOK_CLIENT_SECRET=your_client_secret -FACEBOOK_CALLBACK_URL=/oauth/facebook/callback # this should be the same for everyone -``` -9. Save the .env file. - -Make sure to replace "your_client_id" and "your_client_secret" with the actual values from your Facebook Developer Portal - ---- - -## OpenID Authentication with Azure AD - -1. Go to the [Azure Portal](https://portal.azure.com/) and sign in with your account. -2. In the search box, type "Azure Active Directory" and click on it. -3. On the left menu, click on App registrations and then on New registration. -4. Give your app a name and select Web as the platform type. -5. In the Redirect URI field, enter `http://localhost:3080/oauth/openid/callback` and click on Register. -6. You will see an Overview page with some information about your app. Copy the Application (client) ID and the Directory (tenant) ID and save them somewhere. -7. On the left menu, click on Authentication and check the boxes for Access tokens and ID tokens under Implicit grant and hybrid flows. -8. On the left menu, click on Certificates & Secrets and then on New client secret. Give your secret a name and an expiration date and click on Add. -9. You will see a Value column with your secret. Copy it and save it somewhere. Don't share it with anyone! -10. Open the .env file in your project folder and add the following variables with the values you copied: - -``` -OPENID_CLIENT_ID=Your Application (client) ID -OPENID_CLIENT_SECRET=Your client secret -OPENID_ISSUER=https://login.microsoftonline.com/Your Directory (tenant ID)/v2.0/ -OPENID_SESSION_SECRET=Any random string -OPENID_SCOPE=openid profile email #DO NOT CHANGE THIS -OPENID_CALLBACK_URL=/oauth/openid/callback # this should be the same for everyone -``` -11. Save the .env file and you're done! You have successfully set up OpenID authentication with Azure AD for your app. - -## OpenID Authentication with AWS Cognito - -1. Create a new User Pool in Cognito: - 1. Ensure your Cognito user pool sign-in options include `User Name` and `Email`. - 2. Ensure that `given_name` and `family_name` are required attributes. - 3. Add an initial app client: - 1. Set the app type to `Confidential client` - 2. Select `Use Cognitio Hosted UI` and chose a domain name - 3. Make sure `Generate a client secret` is set. - 4. Set the `Allowed callback URLs` to `https://YOUR_DOMAIN/oauth/openid/callback` - 5. Under advanced settings make sure `Profile` is included in the `OpenID Connect scopes` -2. Open your User Pool -3. Go to the `App Integrations` tab -4. Open the app client we created above. -5. Use the `User Pool ID`and your AWS region to construct the OPENID_ISSUER (see below) -6. Toggle `Show Client Secret` -6. Use the `Client ID` for `OPENID_CLIENT_ID` -7. Use the `Client secret` for `OPENID_CLIENT_SECRET` -8. Open the .env file in your project folder and add the following variables with the values you copied: - -``` -OPENID_CLIENT_ID=Your client ID -OPENID_CLIENT_SECRET=Your client secret -OPENID_ISSUER=https://cognito-idp.[AWS REGION].amazonaws.com/[USER POOL ID]/.well-known/openid-configuration -OPENID_SESSION_SECRET=Any random string -OPENID_SCOPE=openid profile email -OPENID_CALLBACK_URL=/oauth/openid/callback -``` -9. Save the .env file and you're done! You have successfully set up OpenID authentication with Cognito for your app. - ---- - -## GitHub Authentication - -1. Go to your [Github Developer settings](https://github.com/settings/apps) -2. Create a new Github app -3. Give it a GitHub App name and set in the Homepage URL "your-domain") (example: http://localhost:3080) -4. Add a callback URL and set it as "your-domain/oauth/github/callback" (example: http://localhost:3080/oauth/github/callback) -5. Remove the Active checkbox in the Webhook section -6. Save changes and generate a Client Secret -7. In the Permissions & events tab select, open the Account Permissions and set Email addresses to Read-only -8. Put the Client ID and Client Secret in the .env file: -``` -GITHUB_CLIENT_ID=your_client_id -GITHUB_CLIENT_SECRET=your_client_secret -GITHUB_CALLBACK_URL=/oauth/github/callback # this should be the same for everyone -``` -9. Save the .env file ---- - -## Discord Authentication - -1. Go to [Discord Developer Portal](https://discord.com/developers) -2. Create a new Application and give it a name -4. In the OAuth2 general settings add a redirect URL and set it as "your-domain/oauth/discord/callback" (example: http://localhost:3080/oauth/discord/callback) -5. in the Default Authorization Link set applications.commands -6. Save changes and reset the Client Secret -7. Put the Client ID and Client Secret in the .env file: -``` -DISCORD_CLIENT_ID=your_client_id -DISCORD_CLIENT_SECRET=your_client_secret -DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for everyone -``` -8. Save the .env file - ---- -## **Email and Password Reset** - -### General setup - -in the .env file modify these variables: - -``` -EMAIL_SERVICE= # eg. gmail - see https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/ -EMAIL_HOST= # eg. example.com - if EMAIL_SERVICE is not set, connect to this server. -EMAIL_PORT=25 # eg. 25 - mail server port to connect to with EMAIL_HOST (usually 25, 465, 587) -EMAIL_ENCRYPTION= # eg. starttls - valid values: starttls (force STARTTLS), tls (obligatory TLS), anything else (use STARTTLS if available) -EMAIL_ENCRYPTION_HOSTNAME= # eg. example.com - check the name in the certificate against this instead of EMAIL_HOST -EMAIL_ALLOW_SELFSIGNED= # eg. true - valid values: true (allow self-signed), anything else (disallow self-signed) -EMAIL_USERNAME= # eg. me@gmail.com - the username used for authentication. For consumer services, this MUST usually match EMAIL_FROM. -EMAIL_PASSWORD= # eg. password - the password used for authentication -EMAIL_FROM_NAME= # eg. LibreChat - the human-readable address in the From is constructed as "EMAIL_FROM_NAME ". Defaults to APP_TITLE. -``` - -If you want to use one of the predefined services, configure only these variables: - -EMAIL\_SERVICE is the name of the email service you are using (Gmail, Outlook, Yahoo Mail, ProtonMail, iCloud Mail, etc.) as defined in the NodeMailer well-known services linked above. -EMAIL\_USERNAME is the username of the email service (usually, it will be the email address, but in some cases, it can be an actual username used to access the account). -EMAIL\_PASSWORD is the password used to access the email service. This is not the password to access the email account directly, but a password specifically generated for this service. -EMAIL\_FROM is the email address that will appear in the "from" field when a user receives an email. -EMAIL\_FROM\_NAME is the name that will appear in the "from" field when a user receives an email. If left unset, it defaults to the app title. - -If you want to use a generic SMTP service or need advanced configuration for one of the predefined providers, configure these variables: - -EMAIL\_HOST is the hostname to connect to, or an IP address. -EMAIL\_PORT is the port to connect to. Be aware that different ports usually come with different requirements - 25 is for mailserver-to-mailserver, 465 requires encryption at the start of the connection, and 587 allows submission of mail as a user. -EMAIL\_ENCRYPTION defines if encryption is required at the start (`tls`) or started after the connection is set up (`starttls`). If either of these values are set, they are enforced. If they are not set, an encrypted connection is started if available. -EMAIL\_ENCRYPTION\_HOSTNAME allows specification of a hostname against which the certificate is validated. Use this if the mail server does have a valid certificate, but you are connecting with an IP or a different name for some reason. -EMAIL\_ALLOW\_SELFSIGNED defines whether self-signed certificates can be accepted from the server. As the mails being sent contain sensitive information, ONLY use this for testing. - -NOTE: ⚠️ **Failing to perform either of the below setups will result in LibreChat using the unsecured password reset! This allows anyone to reset any password on your server immediately, without mail being sent at all!** The variable EMAIL\_FROM does not support all email providers **but is still required**. To stay updated, check the bug fixes [here](https://github.com/danny-avila/LibreChat/tags). - -### Setup with Gmail - -1. Create a Google Account and enable 2-step verification. -2. In the [Google Account settings](https://myaccount.google.com/), click on the "Security" tab and open "2-step verification." -3. Scroll down and open "App passwords." Choose "Mail" for the app and select "Other" for the device, then give it a random name. -4. Click on "Generate" to create a password, and copy the generated password. -5. In the .env file, modify the variables as follows: - -``` -EMAIL_SERVICE=gmail -EMAIL_USERNAME=your-email -EMAIL_PASSWORD=your-app-password -EMAIL_FROM=email address for the from field, e.g., noreply@librechat.ai -EMAIL_FROM_NAME="My LibreChat Server" -``` - -### Setup with custom mail server - -1. Gather your SMTP login data from your provider. The steps are different for each, but they will usually list values for all variables. -2. In the .env file, modify the variables as follows, assuming some sensible example values: - -``` -EMAIL_HOST=mail.example.com -EMAIL_PORT=587 -EMAIL_ENCRYPTION=starttls -EMAIL_USERNAME=your-email -EMAIL_PASSWORD=your-app-password -EMAIL_FROM=email address for the from field, e.g., noreply@librechat.ai -EMAIL_FROM_NAME="My LibreChat Server" -``` - -## **Disable User Registration** - -To disable or re-enable registration, open up the root `.env` file and set `ALLOW_REGISTRATION=true` or `ALLOW_REGISTRATION=false` depending on if you want registration open or closed. - -To disable or re-enable social registration, open up the root `.env` file and set `ALLOW_SOCIAL_REGISTRATION=true` or `ALLOW_SOCIAL_REGISTRATION=false` depending on if you want social registration open or closed. - -**NOTE: OpenID does not support the ability to disable only registration.** - -### ⚠️***Warning*** - -If you previously implemented your own user system using the original scaffolding that was provided, you will no longer see conversations and presets by switching to the new user system. This is because of a design flaw in the scaffolding implementation that was problematic for the inclusion of social login. - -### For user updating from an older version of the app: - -When the first account is registered, the application will automatically migrate any conversations and presets that you created before the user system was implemented to that account. -if you use login for the first time with a social login account (eg. Google, facebook, etc.), the conversations and presets that you created before the user system was implemented will NOT be migrated to that account. - -## **Manual User Registration** -You can use `npm run create-user` to create a user. If you can't get npm to work, try `sudo docker exec -ti LibreChat sh` first to "ssh" into the container. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css deleted file mode 100644 index 9a4d540d58e..00000000000 --- a/docs/stylesheets/extra.css +++ /dev/null @@ -1,6 +0,0 @@ -/*example youtube color scheme*/ -[data-md-color-scheme="youtube"] { - --md-primary-fg-color: #eee; - --md-primary-bg-color: #555; - --md-accent-fg-color: #f00; - } \ No newline at end of file diff --git a/index.html b/index.html index 54a97a799bc..7445c2bd10c 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + LibreChat LibreChat
diff --git a/package-lock.json b/package-lock.json index 1cbbf411fa0..806b828ce55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "LibreChat", - "version": "0.6.1", + "version": "0.6.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "LibreChat", - "version": "0.6.1", + "version": "0.6.5", "license": "ISC", "workspaces": [ "api", @@ -38,13 +38,14 @@ }, "api": { "name": "@librechat/backend", - "version": "0.6.1", + "version": "0.6.5", "license": "ISC", "dependencies": { "@anthropic-ai/sdk": "^0.5.4", "@azure/search-documents": "^12.0.0", "@keyv/mongo": "^2.1.8", - "@keyv/redis": "^2.8.0", + "@keyv/redis": "^2.8.1", + "@langchain/google-genai": "^0.0.2", "axios": "^1.3.4", "bcryptjs": "^2.4.3", "cheerio": "^1.0.0-rc.12", @@ -57,6 +58,7 @@ "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^6.9.0", "express-session": "^1.17.3", + "firebase": "^10.6.0", "googleapis": "^126.0.1", "handlebars": "^4.7.7", "html": "^1.0.0", @@ -65,7 +67,9 @@ "jsonwebtoken": "^9.0.0", "keyv": "^4.5.4", "keyv-file": "^0.2.0", - "langchain": "^0.0.186", + "klona": "^2.0.6", + "langchain": "^0.0.213", + "librechat-data-provider": "*", "lodash": "^4.17.21", "meilisearch": "^0.33.0", "module-alias": "^2.2.3", @@ -87,8 +91,10 @@ "pino": "^8.12.1", "sharp": "^0.32.6", "tiktoken": "^1.0.10", + "traverse": "^0.6.7", "ua-parser-js": "^1.0.36", - "winston": "^3.10.0", + "winston": "^3.11.0", + "winston-daily-rotate-file": "^4.7.1", "zod": "^3.22.4" }, "devDependencies": { @@ -97,67 +103,44 @@ "supertest": "^6.3.3" } }, - "api/node_modules/@types/node": { - "version": "18.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", - "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "api/node_modules/ansi-styles": { + "api/node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "api/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16.0.0" } }, - "api/node_modules/cohere-ai": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cohere-ai/-/cohere-ai-6.0.0.tgz", - "integrity": "sha512-u1KmPw2PWbTQbbZXmdVs610N/zYyh/V6mL/nDXqx1Gl6X7IH84z9gK3jNv/f69Uzc007xe3zHmMYk80WlkHEmA==" + "api/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } }, - "api/node_modules/langchain": { - "version": "0.0.186", - "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.0.186.tgz", - "integrity": "sha512-uXDipmw9aUrUmDNcFr2XH9ORmshWIlIb/qFKneS1K3X5upMUg7TSbaBxqV9WxuuenLUSYaoTcTy7P/pKkbqXPg==", + "api/node_modules/@langchain/community": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.0.12.tgz", + "integrity": "sha512-mcm6FxxnLxSx9PiYvehGGwvcHjsVR5WXfYOwymojf/6d0apyewjOLzKsR3xx0HJVtCs8pff7NZSdDoE+jj8OcA==", "dependencies": { - "@anthropic-ai/sdk": "^0.6.2", - "ansi-styles": "^5.0.0", - "binary-extensions": "^2.2.0", - "camelcase": "6", - "decamelize": "^1.2.0", - "expr-eval": "^2.0.2", + "@langchain/core": "~0.1.5", + "@langchain/openai": "~0.0.9", "flat": "^5.0.2", - "js-tiktoken": "^1.0.7", - "js-yaml": "^4.1.0", - "jsonpointer": "^5.0.1", - "langchainhub": "~0.0.6", "langsmith": "~0.0.48", - "ml-distance": "^4.0.0", - "openai": "^4.17.0", - "openapi-types": "^12.1.3", - "p-queue": "^6.6.2", - "p-retry": "4", "uuid": "^9.0.0", - "yaml": "^2.2.1", - "zod": "^3.22.3", - "zod-to-json-schema": "^3.20.4" + "zod": "^3.22.3" }, "engines": { "node": ">=18" @@ -168,29 +151,27 @@ "@aws-sdk/client-dynamodb": "^3.310.0", "@aws-sdk/client-kendra": "^3.352.0", "@aws-sdk/client-lambda": "^3.310.0", - "@aws-sdk/client-s3": "^3.310.0", "@aws-sdk/client-sagemaker-runtime": "^3.310.0", "@aws-sdk/client-sfn": "^3.310.0", "@aws-sdk/credential-provider-node": "^3.388.0", - "@azure/storage-blob": "^12.15.0", - "@clickhouse/client": "^0.0.14", + "@clickhouse/client": "^0.2.5", "@cloudflare/ai": "^1.0.12", + "@datastax/astra-db-ts": "0.1.2", "@elastic/elasticsearch": "^8.4.0", "@getmetal/metal-sdk": "*", "@getzep/zep-js": "^0.9.0", - "@gomomento/sdk": "^1.47.1", - "@gomomento/sdk-core": "^1.47.1", - "@gomomento/sdk-web": "^1.47.1", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", - "@google-cloud/storage": "^6.10.1", + "@gradientai/nodejs-sdk": "^1.2.0", "@huggingface/inference": "^2.6.4", "@mozilla/readability": "*", - "@notionhq/client": "^2.2.10", "@opensearch-project/opensearch": "*", "@pinecone-database/pinecone": "^1.1.0", "@planetscale/database": "^1.8.0", "@qdrant/js-client-rest": "^1.2.0", "@raycast/api": "^1.55.2", + "@rockset/client": "^0.9.1", "@smithy/eventstream-codec": "^2.0.5", "@smithy/protocol-http": "^3.0.6", "@smithy/signature-v4": "^2.0.10", @@ -204,52 +185,37 @@ "@vercel/kv": "^0.2.3", "@vercel/postgres": "^0.5.0", "@writerai/writer-sdk": "^0.40.2", - "@xata.io/client": "^0.25.1", + "@xata.io/client": "^0.28.0", "@xenova/transformers": "^2.5.4", "@zilliz/milvus2-sdk-node": ">=2.2.7", - "apify-client": "^2.7.1", - "assemblyai": "^2.0.2", - "axios": "*", "cassandra-driver": "^4.7.2", - "cheerio": "^1.0.0-rc.12", "chromadb": "*", "closevector-common": "0.1.0-alpha.1", "closevector-node": "0.1.0-alpha.10", "closevector-web": "0.1.0-alpha.16", "cohere-ai": ">=6.0.0", "convex": "^1.3.1", - "d3-dsv": "^2.0.0", - "epub2": "^3.0.1", + "discord.js": "^14.14.1", "faiss-node": "^0.5.1", - "fast-xml-parser": "^4.2.7", "firebase-admin": "^11.9.0", "google-auth-library": "^8.9.0", "googleapis": "^126.0.1", "hnswlib-node": "^1.4.2", "html-to-text": "^9.0.5", - "ignore": "^5.2.0", "ioredis": "^5.3.2", "jsdom": "*", - "llmonitor": "^0.5.8", + "llmonitor": "^0.5.9", "lodash": "^4.17.21", - "mammoth": "*", "mongodb": "^5.2.0", "mysql2": "^3.3.3", "neo4j-driver": "*", "node-llama-cpp": "*", - "notion-to-md": "^3.1.0", - "pdf-parse": "1.1.1", - "peggy": "^3.0.2", "pg": "^8.11.0", "pg-copy-streams": "^6.0.5", "pickleparser": "^0.2.1", - "playwright": "^1.32.1", "portkey-ai": "^0.1.11", - "puppeteer": "^19.7.2", "redis": "^4.6.4", "replicate": "^0.18.0", - "sonix-speech-recognition": "^2.1.1", - "srt-parser-2": "^1.2.2", "typeorm": "^0.3.12", "typesense": "^1.5.3", "usearch": "^1.1.1", @@ -257,9 +223,7 @@ "voy-search": "0.6.2", "weaviate-ts-client": "^1.4.0", "web-auth-library": "^1.0.3", - "ws": "^8.14.2", - "youtube-transcript": "^1.0.6", - "youtubei.js": "^5.8.0" + "ws": "^8.14.2" }, "peerDependenciesMeta": { "@aws-crypto/sha256-js": { @@ -277,9 +241,6 @@ "@aws-sdk/client-lambda": { "optional": true }, - "@aws-sdk/client-s3": { - "optional": true - }, "@aws-sdk/client-sagemaker-runtime": { "optional": true }, @@ -289,15 +250,15 @@ "@aws-sdk/credential-provider-node": { "optional": true }, - "@azure/storage-blob": { - "optional": true - }, "@clickhouse/client": { "optional": true }, "@cloudflare/ai": { "optional": true }, + "@datastax/astra-db-ts": { + "optional": true + }, "@elastic/elasticsearch": { "optional": true }, @@ -313,13 +274,10 @@ "@gomomento/sdk-core": { "optional": true }, - "@gomomento/sdk-web": { - "optional": true - }, "@google-ai/generativelanguage": { "optional": true }, - "@google-cloud/storage": { + "@gradientai/nodejs-sdk": { "optional": true }, "@huggingface/inference": { @@ -328,9 +286,6 @@ "@mozilla/readability": { "optional": true }, - "@notionhq/client": { - "optional": true - }, "@opensearch-project/opensearch": { "optional": true }, @@ -346,6 +301,9 @@ "@raycast/api": { "optional": true }, + "@rockset/client": { + "optional": true + }, "@smithy/eventstream-codec": { "optional": true }, @@ -394,21 +352,9 @@ "@zilliz/milvus2-sdk-node": { "optional": true }, - "apify-client": { - "optional": true - }, - "assemblyai": { - "optional": true - }, - "axios": { - "optional": true - }, "cassandra-driver": { "optional": true }, - "cheerio": { - "optional": true - }, "chromadb": { "optional": true }, @@ -427,18 +373,12 @@ "convex": { "optional": true }, - "d3-dsv": { - "optional": true - }, - "epub2": { + "discord.js": { "optional": true }, "faiss-node": { "optional": true }, - "fast-xml-parser": { - "optional": true - }, "firebase-admin": { "optional": true }, @@ -454,9 +394,6 @@ "html-to-text": { "optional": true }, - "ignore": { - "optional": true - }, "ioredis": { "optional": true }, @@ -469,9 +406,6 @@ "lodash": { "optional": true }, - "mammoth": { - "optional": true - }, "mongodb": { "optional": true }, @@ -484,15 +418,6 @@ "node-llama-cpp": { "optional": true }, - "notion-to-md": { - "optional": true - }, - "pdf-parse": { - "optional": true - }, - "peggy": { - "optional": true - }, "pg": { "optional": true }, @@ -502,27 +427,15 @@ "pickleparser": { "optional": true }, - "playwright": { - "optional": true - }, "portkey-ai": { "optional": true }, - "puppeteer": { - "optional": true - }, "redis": { "optional": true }, "replicate": { "optional": true }, - "sonix-speech-recognition": { - "optional": true - }, - "srt-parser-2": { - "optional": true - }, "typeorm": { "optional": true }, @@ -546,112 +459,463 @@ }, "ws": { "optional": true - }, - "youtube-transcript": { - "optional": true - }, - "youtubei.js": { - "optional": true } } }, - "api/node_modules/langchain/node_modules/@anthropic-ai/sdk": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.6.8.tgz", - "integrity": "sha512-z4gDFrBf+W2wOVvwA3CA+5bfKOxQhPeXQo7+ITWj3r3XPulIMEasVT0KrD41G+anr5Yc3d2PKvXKB6b1LSon5w==", + "api/node_modules/@types/node": { + "version": "18.19.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.4.tgz", + "integrity": "sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==", "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "digest-fetch": "^1.3.0", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7", - "web-streams-polyfill": "^3.2.1" + "undici-types": "~5.26.4" } }, - "api/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "api/node_modules/cohere-ai": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cohere-ai/-/cohere-ai-6.0.0.tgz", + "integrity": "sha512-u1KmPw2PWbTQbbZXmdVs610N/zYyh/V6mL/nDXqx1Gl6X7IH84z9gK3jNv/f69Uzc007xe3zHmMYk80WlkHEmA==" + }, + "api/node_modules/fast-xml-parser": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } ], + "optional": true, + "peer": true, + "dependencies": { + "strnum": "^1.0.5" + }, "bin": { - "uuid": "dist/bin/uuid" + "fxparser": "src/cli/cli.js" } }, - "api/node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "api/node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "optional": true, + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "client": { - "name": "@librechat/frontend", - "version": "0.6.1", - "license": "ISC", + "api/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "optional": true, + "peer": true, "dependencies": { - "@headlessui/react": "^1.7.13", - "@radix-ui/react-alert-dialog": "^1.0.2", - "@radix-ui/react-checkbox": "^1.0.3", - "@radix-ui/react-dialog": "^1.0.2", - "@radix-ui/react-dropdown-menu": "^2.0.2", - "@radix-ui/react-hover-card": "^1.0.5", - "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-label": "^2.0.0", - "@radix-ui/react-popover": "^1.0.7", - "@radix-ui/react-separator": "^1.0.3", - "@radix-ui/react-slider": "^1.1.1", - "@radix-ui/react-switch": "^1.0.3", - "@radix-ui/react-tabs": "^1.0.3", - "@radix-ui/react-toast": "^1.1.5", - "@radix-ui/react-tooltip": "^1.0.6", - "@tanstack/react-query": "^4.28.0", - "@zattoo/use-double-click": "1.2.0", - "axios": "^1.3.4", - "class-variance-authority": "^0.6.0", - "clsx": "^1.2.1", - "copy-to-clipboard": "^3.3.3", - "cross-env": "^7.0.3", - "downloadjs": "^1.4.7", - "export-from-json": "^1.7.2", - "filenamify": "^6.0.0", - "html-to-image": "^1.11.11", - "image-blob-reduce": "^4.1.0", - "librechat-data-provider": "*", - "lodash": "^4.17.21", - "lucide-react": "^0.220.0", - "rc-input-number": "^7.4.2", - "react": "^18.2.0", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", - "react-dom": "^18.2.0", - "react-flip-toolkit": "^7.1.0", - "react-hook-form": "^7.43.9", - "react-lazy-load-image-component": "^1.6.0", - "react-markdown": "^8.0.6", - "react-router-dom": "^6.11.2", - "react-textarea-autosize": "^8.4.0", - "react-transition-group": "^4.4.5", - "recoil": "^0.7.7", - "rehype-highlight": "^6.0.0", - "rehype-katex": "^6.0.2", - "rehype-raw": "^6.1.1", - "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1", - "remark-supersub": "^1.0.0", - "tailwind-merge": "^1.9.1", - "tailwindcss-animate": "^1.0.5", - "tailwindcss-radix": "^2.8.0", - "url": "^0.11.0", - "zod": "^3.22.4" + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" }, - "devDependencies": { + "engines": { + "node": ">=12" + } + }, + "api/node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "optional": true, + "peer": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "api/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "api/node_modules/langchain": { + "version": "0.0.213", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.0.213.tgz", + "integrity": "sha512-nQDOJXvtIAIuUzamCiF1AWyi2GH9FSDPR+3XulJUEpdU60aSFPZ9GBiWdu+dVHXeAmm8C0iCVi0+3GWLJrUoXA==", + "dependencies": { + "@anthropic-ai/sdk": "^0.9.1", + "@langchain/community": "~0.0.12", + "@langchain/core": "~0.1.5", + "@langchain/openai": "~0.0.9", + "binary-extensions": "^2.2.0", + "expr-eval": "^2.0.2", + "js-tiktoken": "^1.0.7", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langchainhub": "~0.0.6", + "langsmith": "~0.0.48", + "ml-distance": "^4.0.0", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^9.0.0", + "yaml": "^2.2.1", + "zod": "^3.22.3", + "zod-to-json-schema": "3.20.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.310.0", + "@aws-sdk/client-sagemaker-runtime": "^3.310.0", + "@aws-sdk/client-sfn": "^3.310.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/storage-blob": "^12.15.0", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", + "@gomomento/sdk-web": "^1.51.1", + "@google-ai/generativelanguage": "^0.2.1", + "@google-cloud/storage": "^6.10.1", + "@notionhq/client": "^2.2.10", + "@pinecone-database/pinecone": "^1.1.0", + "@supabase/supabase-js": "^2.10.0", + "@vercel/kv": "^0.2.3", + "@xata.io/client": "^0.28.0", + "apify-client": "^2.7.1", + "assemblyai": "^4.0.0", + "axios": "*", + "cheerio": "^1.0.0-rc.12", + "chromadb": "*", + "convex": "^1.3.1", + "d3-dsv": "^2.0.0", + "epub2": "^3.0.1", + "fast-xml-parser": "^4.2.7", + "google-auth-library": "^8.9.0", + "googleapis": "^126.0.1", + "html-to-text": "^9.0.5", + "ignore": "^5.2.0", + "ioredis": "^5.3.2", + "jsdom": "*", + "mammoth": "^1.6.0", + "mongodb": "^5.2.0", + "node-llama-cpp": "*", + "notion-to-md": "^3.1.0", + "officeparser": "^4.0.4", + "pdf-parse": "1.1.1", + "peggy": "^3.0.2", + "playwright": "^1.32.1", + "puppeteer": "^19.7.2", + "pyodide": "^0.24.1", + "redis": "^4.6.4", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.2", + "typeorm": "^0.3.12", + "vectordb": "^0.1.4", + "weaviate-ts-client": "^1.4.0", + "web-auth-library": "^1.0.3", + "ws": "^8.14.2", + "youtube-transcript": "^1.0.6", + "youtubei.js": "^5.8.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@gomomento/sdk": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@gomomento/sdk-web": { + "optional": true + }, + "@google-ai/generativelanguage": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "convex": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "epub2": { + "optional": true + }, + "faiss-node": { + "optional": true + }, + "fast-xml-parser": { + "optional": true + }, + "google-auth-library": { + "optional": true + }, + "googleapis": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "node-llama-cpp": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "peggy": { + "optional": true + }, + "playwright": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "pyodide": { + "optional": true + }, + "redis": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "vectordb": { + "optional": true + }, + "weaviate-ts-client": { + "optional": true + }, + "web-auth-library": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtube-transcript": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "api/node_modules/langchain/node_modules/@anthropic-ai/sdk": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.9.1.tgz", + "integrity": "sha512-wa1meQ2WSfoY8Uor3EdrJq0jTiZJoKoSii2ZVWRY1oN4Tlr5s59pADg9T79FTbPe1/se5c3pBeZgJL63wmuoBA==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "digest-fetch": "^1.3.0", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + } + }, + "api/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "api/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "api/node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, + "api/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true, + "peer": true + }, + "client": { + "name": "@librechat/frontend", + "version": "0.6.5", + "license": "ISC", + "dependencies": { + "@headlessui/react": "^1.7.13", + "@radix-ui/react-alert-dialog": "^1.0.2", + "@radix-ui/react-checkbox": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.2", + "@radix-ui/react-dropdown-menu": "^2.0.2", + "@radix-ui/react-hover-card": "^1.0.5", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.0.0", + "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slider": "^1.1.1", + "@radix-ui/react-switch": "^1.0.3", + "@radix-ui/react-tabs": "^1.0.3", + "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-tooltip": "^1.0.6", + "@tanstack/react-query": "^4.28.0", + "@zattoo/use-double-click": "1.2.0", + "axios": "^1.3.4", + "class-variance-authority": "^0.6.0", + "clsx": "^1.2.1", + "copy-to-clipboard": "^3.3.3", + "cross-env": "^7.0.3", + "downloadjs": "^1.4.7", + "export-from-json": "^1.7.2", + "filenamify": "^6.0.0", + "html-to-image": "^1.11.11", + "image-blob-reduce": "^4.1.0", + "librechat-data-provider": "*", + "lodash": "^4.17.21", + "lucide-react": "^0.220.0", + "rc-input-number": "^7.4.2", + "react": "^18.2.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dom": "^18.2.0", + "react-flip-toolkit": "^7.1.0", + "react-hook-form": "^7.43.9", + "react-lazy-load-image-component": "^1.6.0", + "react-markdown": "^8.0.6", + "react-router-dom": "^6.11.2", + "react-textarea-autosize": "^8.4.0", + "react-transition-group": "^4.4.5", + "recoil": "^0.7.7", + "rehype-highlight": "^6.0.0", + "rehype-katex": "^6.0.2", + "rehype-raw": "^6.1.1", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-supersub": "^1.0.0", + "tailwind-merge": "^1.9.1", + "tailwindcss-animate": "^1.0.5", + "tailwindcss-radix": "^2.8.0", + "url": "^0.11.0", + "zod": "^3.22.4" + }, + "devDependencies": { "@babel/plugin-transform-runtime": "^7.22.15", "@babel/preset-env": "^7.22.15", "@babel/preset-react": "^7.22.15", @@ -1743,105 +2007,363 @@ }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", + "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz", + "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.17.tgz", + "integrity": "sha512-bxH77R5gjH3Nkde6/LuncQoLaP16THYPscurp1S8z7S9ZgezCyV3G8Hc+TZiCmY8pz4fp8CvKSgtJMW0FkLAxA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.17" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", + "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.17.tgz", + "integrity": "sha512-nAhoheCMlrqU41tAojw9GpVEKDlTS8r3lzFmF0lP52LwblCPbuFSO7nGIZoIcoU5NIm1ABrna0cJExE4Ay6l2Q==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.17" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", - "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "color-convert": "^1.9.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, "engines": { - "node": ">=6.9.0" + "node": ">=4" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { - "node": ">=6.9.0" + "node": ">=0.8.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, "engines": { - "node": ">=6.9.0" + "node": ">=4" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz", - "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==", + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=4" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=6.9.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", + "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1850,336 +2372,369 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", + "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.15" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.17", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.17.tgz", - "integrity": "sha512-bxH77R5gjH3Nkde6/LuncQoLaP16THYPscurp1S8z7S9ZgezCyV3G8Hc+TZiCmY8pz4fp8CvKSgtJMW0FkLAxA==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.17" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/helper-plugin-utils": "^7.12.13" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", "dev": true, - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.22.17", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.17.tgz", - "integrity": "sha512-nAhoheCMlrqU41tAojw9GpVEKDlTS8r3lzFmF0lP52LwblCPbuFSO7nGIZoIcoU5NIm1ABrna0cJExE4Ay6l2Q==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.17" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helpers": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", - "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "dependencies": { - "color-name": "1.1.3" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "engines": { - "node": ">=0.8.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "engines": { - "node": ">=4" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, - "bin": { - "parser": "bin/babel-parser.js" + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", - "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", - "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz", + "integrity": "sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2188,52 +2743,69 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz", + "integrity": "sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", + "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", + "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" @@ -2242,36 +2814,53 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz", + "integrity": "sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-jsx": { + "node_modules/@babel/plugin-transform-dotall-regex": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", "dev": true, "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2281,85 +2870,109 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", + "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", + "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", + "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", + "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -2368,13 +2981,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2383,13 +2996,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", + "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { "node": ">=6.9.0" @@ -2398,28 +3012,28 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { + "node_modules/@babel/plugin-transform-modules-amd": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", "dev": true, "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2429,16 +3043,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { + "node_modules/@babel/plugin-transform-modules-commonjs": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz", - "integrity": "sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz", + "integrity": "sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-simple-access": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2447,15 +3060,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", + "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.9", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2464,12 +3078,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { + "node_modules/@babel/plugin-transform-modules-umd": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", "dev": true, "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2479,28 +3094,28 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz", - "integrity": "sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw==", + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", "dev": true, "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-class-properties": { + "node_modules/@babel/plugin-transform-new-target": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2510,38 +3125,49 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", - "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", + "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.12.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes": { + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", + "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", - "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", + "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/compat-data": "^7.22.9", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -2550,23 +3176,30 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, "engines": { - "node": ">=4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", + "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -2575,13 +3208,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { + "node_modules/@babel/plugin-transform-optional-chaining": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz", - "integrity": "sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz", + "integrity": "sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -2590,13 +3225,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", + "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2606,12 +3240,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { + "node_modules/@babel/plugin-transform-private-methods": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", "dev": true, "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2621,14 +3256,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { + "node_modules/@babel/plugin-transform-private-property-in-object": { "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", - "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", + "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", "dev": true, "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -2637,13 +3274,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { + "node_modules/@babel/plugin-transform-property-literals": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2653,14 +3289,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", - "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2669,13 +3304,17 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { + "node_modules/@babel/plugin-transform-react-jsx": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", - "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz", + "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -2684,14 +3323,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { + "node_modules/@babel/plugin-transform-react-jsx-development": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2701,14 +3353,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", - "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2717,12 +3368,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { + "node_modules/@babel/plugin-transform-react-pure-annotations": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", + "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", "dev": true, "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2732,14 +3384,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", - "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "regenerator-transform": "^0.15.2" }, "engines": { "node": ">=6.9.0" @@ -2748,10 +3400,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { + "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -2763,14 +3415,18 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz", + "integrity": "sha512-tEVLhk8NRZSmwQ0DJtxxhTrCht1HVo8VaMzYT4w6lwyKBuHsgoioAUA7/6eT2fRfc5/23fuGdlwIxXhRVgWr4g==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.5", + "babel-plugin-polyfill-corejs3": "^0.8.3", + "babel-plugin-polyfill-regenerator": "^0.5.2", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -2779,15 +3435,22 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz", - "integrity": "sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==", + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2796,16 +3459,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", - "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", "dev": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.9", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2814,13 +3475,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { + "node_modules/@babel/plugin-transform-sticky-regex": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -2830,26 +3490,25 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "node_modules/@babel/plugin-transform-template-literals": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-new-target": { + "node_modules/@babel/plugin-transform-typeof-symbol": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -2861,14 +3520,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", - "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz", + "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==", "dev": true, "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/plugin-syntax-typescript": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2877,14 +3538,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", - "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2893,17 +3553,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", - "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2912,14 +3569,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-super": { + "node_modules/@babel/plugin-transform-unicode-regex": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2928,31 +3585,108 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", - "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { + "node_modules/@babel/preset-env": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz", - "integrity": "sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A==", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.15.tgz", + "integrity": "sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag==", "dev": true, "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.15", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.15", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.11", + "@babel/plugin-transform-classes": "^7.22.15", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.15", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.11", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.11", + "@babel/plugin-transform-for-of": "^7.22.15", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.11", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.15", + "@babel/plugin-transform-modules-systemjs": "^7.22.11", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", + "@babel/plugin-transform-numeric-separator": "^7.22.11", + "@babel/plugin-transform-object-rest-spread": "^7.22.15", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.11", + "@babel/plugin-transform-optional-chaining": "^7.22.15", + "@babel/plugin-transform-parameters": "^7.22.15", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.10", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.10", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "@babel/types": "^7.22.15", + "babel-plugin-polyfill-corejs2": "^0.4.5", + "babel-plugin-polyfill-corejs3": "^0.8.3", + "babel-plugin-polyfill-regenerator": "^0.5.2", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -2961,47 +3695,41 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", - "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", - "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "node_modules/@babel/preset-react": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.15.tgz", + "integrity": "sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-react-display-name": "^7.22.5", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -3010,13 +3738,17 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "node_modules/@babel/preset-typescript": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz", + "integrity": "sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.15", + "@babel/plugin-transform-typescript": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -3025,567 +3757,725 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", - "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", - "dev": true, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", + "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { + "node_modules/@babel/template": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz", - "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "node_modules/@babel/traverse": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "dev": true, "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.22.5" + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.4.tgz", + "integrity": "sha512-zXMGsJetbLoXe+gjEES07MEGjL0Uy3hMxmnGtVBrRpVKr5KV9OgCB09zr/vLrsEtoVQTgJFewxaU8IYSAE4tjg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "node_modules/@csstools/color-helpers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-3.0.2.tgz", + "integrity": "sha512-NMVs/l7Y9eIKL5XjbCHEgGcG8LOUT2qVcRjX6EzkCdlvftHVKr2tHIPzHavfrULRZ5Q2gxrJ9f44dAlj6fX97Q==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.1.3.tgz", + "integrity": "sha512-7mJZ8gGRtSQfQKBQFi5N0Z+jzNC0q8bIkwojP1W0w+APzEqHu5wJoGVsvKxVnVklu9F8tW1PikbBRseYnAdv+g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0" } }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", - "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "node_modules/@csstools/css-color-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.3.1.tgz", + "integrity": "sha512-cehc/DQCyb4hL4fspvyL7WiY+uAy8Iuaz0yTyndC/AyBmxkNpgtSgCSsr0aR4vkaSFVZfNNVlKbjHFwOsPGB1Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@csstools/color-helpers": "^3.0.2", + "@csstools/css-calc": "^1.1.3" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.1.tgz", + "integrity": "sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-tokenizer": "^2.2.0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "node_modules/@csstools/css-tokenizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.0.tgz", + "integrity": "sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", + "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz", - "integrity": "sha512-tEVLhk8NRZSmwQ0DJtxxhTrCht1HVo8VaMzYT4w6lwyKBuHsgoioAUA7/6eT2fRfc5/23fuGdlwIxXhRVgWr4g==", + "node_modules/@csstools/postcss-cascade-layers": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-3.0.1.tgz", + "integrity": "sha512-dD8W98dOYNOH/yX4V4HXOhfCOnvVAg8TtsL+qCGNoKXuq5z2C/d026wGWgySgC8cajXXo/wNezS31Glj5GcqrA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.5", - "babel-plugin-polyfill-corejs3": "^0.8.3", - "babel-plugin-polyfill-regenerator": "^0.5.2", - "semver": "^6.3.1" + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "node_modules/@csstools/postcss-color-function": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-2.2.3.tgz", + "integrity": "sha512-b1ptNkr1UWP96EEHqKBWWaV5m/0hgYGctgA/RVZhONeP1L3T/8hwoqDm9bB23yVCfOgE9U93KI9j06+pEkJTvw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@csstools/css-color-parser": "^1.2.0", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "node_modules/@csstools/postcss-color-mix-function": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-1.0.3.tgz", + "integrity": "sha512-QGXjGugTluqFZWzVf+S3wCiRiI0ukXlYqCi7OnpDotP/zaVTyl/aqZujLFzTOXy24BoWnu89frGMc79ohY5eog==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@csstools/css-color-parser": "^1.2.0", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-2.0.2.tgz", + "integrity": "sha512-iKYZlIs6JsNT7NKyRjyIyezTCHLh4L4BBB3F5Nx7Dc4Z/QmBgX+YJFuUSar8IM6KclGiAUFGomXFdYxAwJydlA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-3.0.6.tgz", + "integrity": "sha512-rBOBTat/YMmB0G8VHwKqDEx+RZ4KCU9j42K8LwS0IpZnyThalZZF7BCSsZ6TFlZhcRZKlZy3LLFI2pLqjNVGGA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@csstools/css-color-parser": "^1.2.0", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "node_modules/@csstools/postcss-hwb-function": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-2.2.2.tgz", + "integrity": "sha512-W5Y5oaJ382HSlbdGfPf60d7dAK6Hqf10+Be1yZbd/TNNrQ/3dDdV1c07YwOXPQ3PZ6dvFMhxbIbn8EC3ki3nEg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@csstools/css-color-parser": "^1.2.0", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz", - "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==", + "node_modules/@csstools/postcss-ic-unit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-2.0.4.tgz", + "integrity": "sha512-9W2ZbV7whWnr1Gt4qYgxMWzbevZMOvclUczT5vk4yR6vS53W/njiiUhtm/jh/BKYwQ1W3PECZjgAd2dH4ebJig==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.22.5" + "@csstools/postcss-progressive-custom-properties": "^2.3.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-3.2.1.tgz", + "integrity": "sha512-AtANdV34kJl04Al62is3eQRk/BfOfyAvEmRJvbt+nx5REqImLC+2XhuE6skgkcPli1l8ONS67wS+l1sBzySc3Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-1.0.1.tgz", + "integrity": "sha512-eO9z2sMLddvlfFEW5Fxbjyd03zaO7cJafDurK4rCqyRt9P7aaWwha0LcSzoROlcZrw1NBV2JAp2vMKfPMQO1xw==", "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "node_modules/@csstools/postcss-logical-resize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-1.0.1.tgz", + "integrity": "sha512-x1ge74eCSvpBkDDWppl+7FuD2dL68WP+wwP2qvdUcKY17vJksz+XoE1ZRV38uJgS6FNUwC0AxrPW5gy3MxsDHQ==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-1.0.3.tgz", + "integrity": "sha512-6zqcyRg9HSqIHIPMYdt6THWhRmE5/tyHKJQLysn2TeDf/ftq7Em9qwMTx98t2C/7UxIsYS8lOiHHxAVjWn2WUg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@csstools/css-tokenizer": "^2.1.1" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "postcss": "^8.4" } }, - "node_modules/@babel/preset-env": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.15.tgz", - "integrity": "sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag==", + "node_modules/@csstools/postcss-media-minmax": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.7.tgz", + "integrity": "sha512-5LGLdu8cJgRPmvkjUNqOPKIKeHbyQmoGKooB5Rh0mp5mLaNI9bl+IjFZ2keY0cztZYsriJsGf6Lu8R5XetuwoQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.15", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.15", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.11", - "@babel/plugin-transform-classes": "^7.22.15", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.15", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.11", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.11", - "@babel/plugin-transform-for-of": "^7.22.15", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.11", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.15", - "@babel/plugin-transform-modules-systemjs": "^7.22.11", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-numeric-separator": "^7.22.11", - "@babel/plugin-transform-object-rest-spread": "^7.22.15", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.22.15", - "@babel/plugin-transform-parameters": "^7.22.15", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.10", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.10", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.22.15", - "babel-plugin-polyfill-corejs2": "^0.4.5", - "babel-plugin-polyfill-corejs3": "^0.8.3", - "babel-plugin-polyfill-regenerator": "^0.5.2", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" + "@csstools/css-calc": "^1.1.3", + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0", + "@csstools/media-query-list-parser": "^2.1.4" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "postcss": "^8.4" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-1.0.4.tgz", + "integrity": "sha512-IwyTbyR8E2y3kh6Fhrs251KjKBJeUPV5GlnUKnpU70PRFEN2DolWbf2V4+o/B9+Oj77P/DullLTulWEQ8uFtAA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@csstools/css-parser-algorithms": "^2.2.0", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "postcss": "^8.4" } }, - "node_modules/@babel/preset-react": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.15.tgz", - "integrity": "sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==", + "node_modules/@csstools/postcss-nested-calc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-2.0.2.tgz", + "integrity": "sha512-jbwrP8rN4e7LNaRcpx3xpMUjhtt34I9OV+zgbcsYAAk6k1+3kODXJBf95/JMYWhu9g1oif7r06QVUgfWsKxCFw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-react-display-name": "^7.22.5", - "@babel/plugin-transform-react-jsx": "^7.22.15", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz", - "integrity": "sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A==", + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-2.0.1.tgz", + "integrity": "sha512-TQT5g3JQ5gPXC239YuRK8jFceXF9d25ZvBkyjzBGGoW5st5sPXFVQS8OjYb9IJ/K3CdfK4528y483cgS2DJR/w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.15", - "@babel/plugin-transform-typescript": "^7.22.15" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "node_modules/@csstools/postcss-oklab-function": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-2.2.3.tgz", + "integrity": "sha512-AgJ2rWMnLCDcbSMTHSqBYn66DNLBym6JpBpCaqmwZ9huGdljjDRuH3DzOYzkgQ7Pm2K92IYIq54IvFHloUOdvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "regenerator-runtime": "^0.14.0" + "@csstools/css-color-parser": "^1.2.0", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-2.3.0.tgz", + "integrity": "sha512-Zd8ojyMlsL919TBExQ1I0CTpBDdyCpH/yOdqatZpuC3sd22K4SwC7+Yez3Q/vmXMWSAl+shjNeFZ7JMyxMjK+Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-1.0.2.tgz", + "integrity": "sha512-juCoVInkgH2TZPfOhyx6tIal7jW37L/0Tt+Vcl1LoxqQA9sxcg3JWYZ98pl1BonDnki6s/M7nXzFQHWsWMeHgw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@csstools/css-color-parser": "^1.2.0", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-2.0.2.tgz", + "integrity": "sha512-6Pvo4uexUCXt+Hz5iUtemQAcIuCYnL+ePs1khFR6/xPgC92aQLJ0zGHonWoewiBE+I++4gXK3pr+R1rlOFHe5w==", "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, "engines": { - "node": ">=4" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.1.1.tgz", + "integrity": "sha512-YCvdF0GCZK35nhLgs7ippcxDlRVe5QsSht3+EghqTjnYnyl3BbWIN6fYQ1dKWYTJ+7Bgi41TgqQFfJDcp9Xy/w==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "engines": { - "node": ">=0.1.90" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.4.tgz", - "integrity": "sha512-zXMGsJetbLoXe+gjEES07MEGjL0Uy3hMxmnGtVBrRpVKr5KV9OgCB09zr/vLrsEtoVQTgJFewxaU8IYSAE4tjg==", + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-2.2.4.tgz", + "integrity": "sha512-zPN56sQkS/7YTCVZhOBVCWf7AiNge8fXDl7JVaHLz2RyT4pnyK2gFjckWRLpO0A2xkm1lCgZ0bepYZTwAVd/5A==", "dev": true, "funding": [ { @@ -3597,18 +4487,21 @@ "url": "https://opencollective.com/csstools" } ], + "dependencies": { + "@csstools/color-helpers": "^2.1.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" + "postcss": "^8.4" } }, - "node_modules/@csstools/color-helpers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-3.0.2.tgz", - "integrity": "sha512-NMVs/l7Y9eIKL5XjbCHEgGcG8LOUT2qVcRjX6EzkCdlvftHVKr2tHIPzHavfrULRZ5Q2gxrJ9f44dAlj6fX97Q==", + "node_modules/@csstools/postcss-text-decoration-shorthand/node_modules/@csstools/color-helpers": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-2.1.0.tgz", + "integrity": "sha512-OWkqBa7PDzZuJ3Ha7T5bxdSVfSCfTq6K1mbAhbO1MD+GSULGjrp45i5RudyJOedstSarN/3mdwu9upJE7gDXfw==", "dev": true, "funding": [ { @@ -3621,1179 +4514,1234 @@ } ], "engines": { - "node": "^14 || ^16 || >=18" + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-2.1.1.tgz", + "integrity": "sha512-XcXmHEFfHXhvYz40FtDlA4Fp4NQln2bWTsCwthd2c+MCnYArUYU3YaMqzR5CrKP3pMoGYTBnp5fMqf1HxItNyw==", + "dev": true, + "dependencies": { + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-2.0.1.tgz", + "integrity": "sha512-oJ9Xl29/yU8U7/pnMJRqAZd4YXNCfGEdcP4ywREuqm/xMqcgDNDppYRoCGDt40aaZQIEKBS79LytUDN/DHf0Ew==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@dqbd/tiktoken": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@dqbd/tiktoken/-/tiktoken-1.0.7.tgz", + "integrity": "sha512-bhR5k5W+8GLzysjk8zTMVygQZsgvf7W1F0IlL4ZQ5ugjo5rCyiwGM5d8DYriXspytfu98tv59niang3/T+FoDw==" + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", + "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", + "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", + "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@csstools/css-calc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.1.3.tgz", - "integrity": "sha512-7mJZ8gGRtSQfQKBQFi5N0Z+jzNC0q8bIkwojP1W0w+APzEqHu5wJoGVsvKxVnVklu9F8tW1PikbBRseYnAdv+g==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", + "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "darwin" ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" + "node": ">=12" } }, - "node_modules/@csstools/css-color-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.3.1.tgz", - "integrity": "sha512-cehc/DQCyb4hL4fspvyL7WiY+uAy8Iuaz0yTyndC/AyBmxkNpgtSgCSsr0aR4vkaSFVZfNNVlKbjHFwOsPGB1Q==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", + "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "darwin" ], - "dependencies": { - "@csstools/color-helpers": "^3.0.2", - "@csstools/css-calc": "^1.1.3" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" + "node": ">=12" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.1.tgz", - "integrity": "sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", + "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "freebsd" ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^2.2.0" + "node": ">=12" } }, - "node_modules/@csstools/css-tokenizer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.0.tgz", - "integrity": "sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", + "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "freebsd" ], "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=12" } }, - "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", - "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", + "node_modules/@esbuild/linux-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", + "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", + "cpu": [ + "arm" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "linux" ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" + "node": ">=12" } }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-3.0.1.tgz", - "integrity": "sha512-dD8W98dOYNOH/yX4V4HXOhfCOnvVAg8TtsL+qCGNoKXuq5z2C/d026wGWgySgC8cajXXo/wNezS31Glj5GcqrA==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", + "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-color-function": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-2.2.3.tgz", - "integrity": "sha512-b1ptNkr1UWP96EEHqKBWWaV5m/0hgYGctgA/RVZhONeP1L3T/8hwoqDm9bB23yVCfOgE9U93KI9j06+pEkJTvw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", + "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", + "cpu": [ + "ia32" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "linux" ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.0", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/postcss-progressive-custom-properties": "^2.3.0" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-1.0.3.tgz", - "integrity": "sha512-QGXjGugTluqFZWzVf+S3wCiRiI0ukXlYqCi7OnpDotP/zaVTyl/aqZujLFzTOXy24BoWnu89frGMc79ohY5eog==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", + "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", + "cpu": [ + "loong64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", + "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.0", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/postcss-progressive-custom-properties": "^2.3.0" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-2.0.2.tgz", - "integrity": "sha512-iKYZlIs6JsNT7NKyRjyIyezTCHLh4L4BBB3F5Nx7Dc4Z/QmBgX+YJFuUSar8IM6KclGiAUFGomXFdYxAwJydlA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", + "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-3.0.6.tgz", - "integrity": "sha512-rBOBTat/YMmB0G8VHwKqDEx+RZ4KCU9j42K8LwS0IpZnyThalZZF7BCSsZ6TFlZhcRZKlZy3LLFI2pLqjNVGGA==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", + "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", + "cpu": [ + "riscv64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "linux" ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.0", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/postcss-progressive-custom-properties": "^2.3.0" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-2.2.2.tgz", - "integrity": "sha512-W5Y5oaJ382HSlbdGfPf60d7dAK6Hqf10+Be1yZbd/TNNrQ/3dDdV1c07YwOXPQ3PZ6dvFMhxbIbn8EC3ki3nEg==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", + "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", + "cpu": [ + "s390x" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "linux" ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.0", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-2.0.4.tgz", - "integrity": "sha512-9W2ZbV7whWnr1Gt4qYgxMWzbevZMOvclUczT5vk4yR6vS53W/njiiUhtm/jh/BKYwQ1W3PECZjgAd2dH4ebJig==", + "node_modules/@esbuild/linux-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", + "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "linux" ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^2.3.0", - "postcss-value-parser": "^4.2.0" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-3.2.1.tgz", - "integrity": "sha512-AtANdV34kJl04Al62is3eQRk/BfOfyAvEmRJvbt+nx5REqImLC+2XhuE6skgkcPli1l8ONS67wS+l1sBzySc3Q==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", + "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "netbsd" ], - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-1.0.1.tgz", - "integrity": "sha512-eO9z2sMLddvlfFEW5Fxbjyd03zaO7cJafDurK4rCqyRt9P7aaWwha0LcSzoROlcZrw1NBV2JAp2vMKfPMQO1xw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", + "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-1.0.1.tgz", - "integrity": "sha512-x1ge74eCSvpBkDDWppl+7FuD2dL68WP+wwP2qvdUcKY17vJksz+XoE1ZRV38uJgS6FNUwC0AxrPW5gy3MxsDHQ==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", + "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-1.0.3.tgz", - "integrity": "sha512-6zqcyRg9HSqIHIPMYdt6THWhRmE5/tyHKJQLysn2TeDf/ftq7Em9qwMTx98t2C/7UxIsYS8lOiHHxAVjWn2WUg==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", + "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@csstools/css-tokenizer": "^2.1.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", + "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.7.tgz", - "integrity": "sha512-5LGLdu8cJgRPmvkjUNqOPKIKeHbyQmoGKooB5Rh0mp5mLaNI9bl+IjFZ2keY0cztZYsriJsGf6Lu8R5XetuwoQ==", + "node_modules/@esbuild/win32-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", + "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "optional": true, + "os": [ + "win32" ], - "dependencies": { - "@csstools/css-calc": "^1.1.3", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-1.0.4.tgz", - "integrity": "sha512-IwyTbyR8E2y3kh6Fhrs251KjKBJeUPV5GlnUKnpU70PRFEN2DolWbf2V4+o/B9+Oj77P/DullLTulWEQ8uFtAA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "dependencies": { - "@csstools/css-parser-algorithms": "^2.2.0", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/media-query-list-parser": "^2.1.1" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^14 || ^16 || >=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "postcss": "^8.4" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-2.0.2.tgz", - "integrity": "sha512-jbwrP8rN4e7LNaRcpx3xpMUjhtt34I9OV+zgbcsYAAk6k1+3kODXJBf95/JMYWhu9g1oif7r06QVUgfWsKxCFw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-2.0.1.tgz", - "integrity": "sha512-TQT5g3JQ5gPXC239YuRK8jFceXF9d25ZvBkyjzBGGoW5st5sPXFVQS8OjYb9IJ/K3CdfK4528y483cgS2DJR/w==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.2.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14 || ^16 || >=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-2.2.3.tgz", - "integrity": "sha512-AgJ2rWMnLCDcbSMTHSqBYn66DNLBym6JpBpCaqmwZ9huGdljjDRuH3DzOYzkgQ7Pm2K92IYIq54IvFHloUOdvA==", + "node_modules/@eslint/js": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.0", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/postcss-progressive-custom-properties": "^2.3.0" - }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-2.3.0.tgz", - "integrity": "sha512-Zd8ojyMlsL919TBExQ1I0CTpBDdyCpH/yOdqatZpuC3sd22K4SwC7+Yez3Q/vmXMWSAl+shjNeFZ7JMyxMjK+Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@fastify/ajv-compiler": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz", + "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==", "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" } }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-1.0.2.tgz", - "integrity": "sha512-juCoVInkgH2TZPfOhyx6tIal7jW37L/0Tt+Vcl1LoxqQA9sxcg3JWYZ98pl1BonDnki6s/M7nXzFQHWsWMeHgw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dependencies": { - "@csstools/css-color-parser": "^1.2.0", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/postcss-progressive-custom-properties": "^2.3.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/@fastify/busboy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", + "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=14" } }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-2.0.2.tgz", - "integrity": "sha512-6Pvo4uexUCXt+Hz5iUtemQAcIuCYnL+ePs1khFR6/xPgC92aQLJ0zGHonWoewiBE+I++4gXK3pr+R1rlOFHe5w==", - "dev": true, + "node_modules/@fastify/cors": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.4.0.tgz", + "integrity": "sha512-MlVvMTenltToByTpLwlWtO+7dQ3l2J+1OpmGrx9JpSNWo1d+dhfNCOi23zHhxdFhtpDzfwGwCsKu9DTeG7k7nQ==", "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^14 || ^16 || >=18" + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.5" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", + "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==" + }, + "node_modules/@fastify/error": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.0.tgz", + "integrity": "sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.0.tgz", + "integrity": "sha512-Locv8gAqx0e+GX/0SI3dzmBY5e9kjVDtD+3zCFLJ0tH2hJwuCAiL+5WkHuxKj92rqQj/rvkBUCfA1ewlX2hehg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz", + "integrity": "sha512-4MqpVLFkGK7NJf/5wPEEP7ePBJatwYpyjgJ+wQHQGHfzaCDgntOnl9rL2vbVGGKCnRqWtZDIWhctB86UWXaX2Q==", + "dependencies": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" }, "peerDependencies": { - "postcss": "^8.4" + "@firebase/app-compat": "0.x" } }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.1.1.tgz", - "integrity": "sha512-YCvdF0GCZK35nhLgs7ippcxDlRVe5QsSht3+EghqTjnYnyl3BbWIN6fYQ1dKWYTJ+7Bgi41TgqQFfJDcp9Xy/w==", - "dev": true, + "node_modules/@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==" + }, + "node_modules/@firebase/app": { + "version": "0.9.23", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.23.tgz", + "integrity": "sha512-CA5pQ88We3FhyuesGKn1thaPBsJSGJGm6AlFToOmEJagWqBeDoNJqBkry/BsHnCs9xeYWWIprKxvuFmAFkdqoA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.0.tgz", + "integrity": "sha512-dRDnhkcaC2FspMiRK/Vbp+PfsOAEP6ZElGm9iGFJ9fDqHoPs0HOPn7dwpJ51lCFi1+2/7n5pRPGhqF/F03I97g==", "dependencies": { - "@csstools/css-calc": "^1.1.1", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" }, "peerDependencies": { - "postcss": "^8.4" + "@firebase/app": "0.x" } }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-2.2.4.tgz", - "integrity": "sha512-zPN56sQkS/7YTCVZhOBVCWf7AiNge8fXDl7JVaHLz2RyT4pnyK2gFjckWRLpO0A2xkm1lCgZ0bepYZTwAVd/5A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@firebase/app-check-compat": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.7.tgz", + "integrity": "sha512-cW682AxsyP1G+Z0/P7pO/WT2CzYlNxoNe5QejVarW2o5ZxeWSSPAiVEwpEpQR/bUlUmdeWThYTMvBWaopdBsqw==", "dependencies": { - "@csstools/color-helpers": "^2.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" + "@firebase/app-check": "0.8.0", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" }, "peerDependencies": { - "postcss": "^8.4" + "@firebase/app-compat": "0.x" } }, - "node_modules/@csstools/postcss-text-decoration-shorthand/node_modules/@csstools/color-helpers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-2.1.0.tgz", - "integrity": "sha512-OWkqBa7PDzZuJ3Ha7T5bxdSVfSCfTq6K1mbAhbO1MD+GSULGjrp45i5RudyJOedstSarN/3mdwu9upJE7gDXfw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz", + "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.23.tgz", + "integrity": "sha512-UCv0LEzcoqAgY+sLsau7aOZz0CJNLN2gESY68bHKmukNXEN6onLPxBKJzn68CsZZGcdiIEXwvrum1riWNPe9Gw==", + "dependencies": { + "@firebase/app": "0.9.23", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" } }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-2.1.1.tgz", - "integrity": "sha512-XcXmHEFfHXhvYz40FtDlA4Fp4NQln2bWTsCwthd2c+MCnYArUYU3YaMqzR5CrKP3pMoGYTBnp5fMqf1HxItNyw==", - "dev": true, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "node_modules/@firebase/auth": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.4.0.tgz", + "integrity": "sha512-SfFXZCHDbY+7oSR52NSwx0U7LjYiA+N8imloxphCf3/F+MFty/+mhdjSXGtrJYd0Gbud/qcyedfn2XnWJeIB/g==", "dependencies": { - "@csstools/css-calc": "^1.1.1", - "@csstools/css-parser-algorithms": "^2.1.1", - "@csstools/css-tokenizer": "^2.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, "peerDependencies": { - "postcss": "^8.4" + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } } }, - "node_modules/@csstools/postcss-unset-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-2.0.1.tgz", - "integrity": "sha512-oJ9Xl29/yU8U7/pnMJRqAZd4YXNCfGEdcP4ywREuqm/xMqcgDNDppYRoCGDt40aaZQIEKBS79LytUDN/DHf0Ew==", - "dev": true, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "node_modules/@firebase/auth-compat": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.9.tgz", + "integrity": "sha512-Fw03i7vduIciEBG4imLtA1duJbljgkfbxiBo/EuekcB+BnPxHp+e8OGMUfemPYeO7Munj6kUC9gr5DelsQkiNA==", + "dependencies": { + "@firebase/auth": "1.4.0", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, "peerDependencies": { - "postcss": "^8.4" + "@firebase/app-compat": "0.x" } }, - "node_modules/@csstools/selector-specificity": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", - "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", - "dev": true, - "engines": { - "node": "^14 || ^16 || >=18" + "node_modules/@firebase/auth-compat/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "engines": { + "node": "4.x || >=6.0.0" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.10" + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } + "node_modules/@firebase/auth-compat/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/@dqbd/tiktoken": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@dqbd/tiktoken/-/tiktoken-1.0.7.tgz", - "integrity": "sha512-bhR5k5W+8GLzysjk8zTMVygQZsgvf7W1F0IlL4ZQ5ugjo5rCyiwGM5d8DYriXspytfu98tv59niang3/T+FoDw==" + "node_modules/@firebase/auth-compat/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", - "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/auth-compat/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", - "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", - "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", - "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@firebase/auth/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { - "node": ">=12" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", - "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } + "node_modules/@firebase/auth/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", - "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/auth/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@firebase/auth/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", - "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", - "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/database": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.1.tgz", + "integrity": "sha512-VAhF7gYwunW4Lw/+RQZvW8dlsf2r0YYqV9W0Gi2Mz8+0TGg1mBJWoUtsHfOr8kPJXhcLsC4eP/z3x6L/Fvjk/A==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", - "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/database-compat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.1.tgz", + "integrity": "sha512-ky82yLIboLxtAIWyW/52a6HLMVTzD2kpZlEilVDok73pNPLjkJYowj8iaIWK5nTy7+6Gxt7d00zfjL6zckGdXQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "1.0.1", + "@firebase/database-types": "1.0.0", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", - "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/database-types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.0.tgz", + "integrity": "sha512-SjnXStoE0Q56HcFgNQ+9SsmJc0c8TqGARdI/T44KXy+Ets3r6x/ivhQozT66bMnCEjJRywYoxNurRTMlZF8VNg==", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", - "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@firebase/firestore": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.3.2.tgz", + "integrity": "sha512-K4TwMbgArWw+XAEUYX/vtk+TVy9n1uLeJKSrQeb89lwfkfyFINGLPME6YleaS0ovD1ziLM5/0WgL1CR4s53fDg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.10.3", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, "engines": { - "node": ">=12" + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", - "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/firestore-compat": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.22.tgz", + "integrity": "sha512-M166UvFvRri0CK/+5N0MIeXJVxR6BsX0/96xFT506DxRPIFezLjLcvfddtyFgfe0CtyQWoxBXt060uWUg3d/sw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "4.3.2", + "@firebase/firestore-types": "3.0.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", - "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/firestore-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.0.tgz", + "integrity": "sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", - "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@firebase/firestore/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { - "node": ">=12" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", - "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/firestore/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@firebase/firestore/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@firebase/firestore/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", - "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/functions": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", + "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", - "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/functions-compat": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.5.tgz", + "integrity": "sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/functions": "0.10.0", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", - "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/@firebase/functions-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" + }, + "node_modules/@firebase/functions/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { - "node": ">=12" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@firebase/functions/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@firebase/functions/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@firebase/functions/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", - "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", - "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/installations-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", + "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", - "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/installations-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "peerDependencies": { + "@firebase/app-types": "0.x" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", - "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/installations/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "dependencies": { + "tslib": "^2.1.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, + "node_modules/@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", + "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@firebase/app-compat": "0.x" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" + }, + "node_modules/@firebase/messaging/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, + "node_modules/@firebase/performance-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", + "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.4", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", + "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@fastify/ajv-compiler": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz", - "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==", + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", + "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", "dependencies": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "fast-uri": "^2.0.0" + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@fastify/ajv-compiler/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" + }, + "node_modules/@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "node_modules/@firebase/storage-compat": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", + "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-types": "0.8.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } }, - "node_modules/@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", - "engines": { - "node": ">=14" + "node_modules/@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" } }, - "node_modules/@fastify/cors": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.4.0.tgz", - "integrity": "sha512-MlVvMTenltToByTpLwlWtO+7dQ3l2J+1OpmGrx9JpSNWo1d+dhfNCOi23zHhxdFhtpDzfwGwCsKu9DTeG7k7nQ==", + "node_modules/@firebase/storage/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dependencies": { - "fastify-plugin": "^4.0.0", - "mnemonist": "0.39.5" + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@fastify/deepmerge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", - "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==" + "node_modules/@firebase/storage/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/@fastify/error": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.0.tgz", - "integrity": "sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==" + "node_modules/@firebase/storage/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", - "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "node_modules/@firebase/storage/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dependencies": { - "fast-json-stringify": "^5.7.0" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "dependencies": { + "tslib": "^2.1.0" } }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.3.tgz", + "integrity": "sha512-+ZplYUN3HOpgCfgInqgdDAbkGGVzES1cs32JJpeqoh87SkRobGXElJx+1GZSaDqzFL+bYiX18qEcBK76mYs8uA==" + }, "node_modules/@floating-ui/core": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz", @@ -4811,23 +5759,60 @@ "@floating-ui/utils": "^0.1.1" } }, - "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "node_modules/@floating-ui/react-dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz", + "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ==" + }, + "node_modules/@google/generative-ai": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.1.1.tgz", + "integrity": "sha512-cbzKa8mT9YkTrT4XUuENIuvlqiJjwDgcD2Ks4L99Az9dWLgdXn8xnETEAZLOpqzoGx+1PuATZqlUnVRAeLbMgA==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.13", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.13.tgz", + "integrity": "sha512-OEZZu9v9AA+7/tghMDE8o5DAMD5THVnwSqDWuh7PPYO5287rTyqy0xEHT6/e4pbqSrhyLPdQFsam4TwFQVVIIw==", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", + "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", "dependencies": { - "@floating-ui/dom": "^1.5.1" + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.4", + "yargs": "^17.7.2" }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" } }, - "node_modules/@floating-ui/utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz", - "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ==" - }, "node_modules/@headlessui/react": { "version": "1.7.17", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz", @@ -5451,9 +6436,9 @@ } }, "node_modules/@keyv/redis": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-2.8.0.tgz", - "integrity": "sha512-6k7wG/KKSIGpruKlsEB4sFjECJEyQsuJbWoWdoq9Uv2L6Mm/SEqEidekRZI/QljE1A4WQkFsIE8hHl1Oc3UNGg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-2.8.1.tgz", + "integrity": "sha512-JjoNXtAcjT0r0CIWi69rVoXdwPD2nHqyWR80XHmsP1Psuzml1icNtehq6ZqKJjLLgLzH3DvQoWrXkmhxxerf3g==", "dependencies": { "ioredis": "^5.3.2" }, @@ -5461,6 +6446,87 @@ "node": ">= 14" } }, + "node_modules/@langchain/core": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.1.5.tgz", + "integrity": "sha512-5RdZpbadMYXTKdZrWZ6JeYT09BZssprg+komE+FbXxW+kbGE0QJJaj8dnzPNdzVCAhDLQQTOV2CAM+vOLsaxzQ==", + "dependencies": { + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.8", + "langsmith": "~0.0.48", + "ml-distance": "^4.0.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^9.0.0", + "zod": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@langchain/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/core/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/google-genai": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.0.2.tgz", + "integrity": "sha512-Q6zgVeZ0IzD976LGhwl86RwyTn6zpdwltVTYGEEag3AyT3zDzALPiyEfORFxublQjIVeIoTiyDJ9MT9nXb1xwg==", + "dependencies": { + "@google/generative-ai": "^0.1.0", + "@langchain/core": "~0.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/openai": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.0.9.tgz", + "integrity": "sha512-Py7rJijOjNtb9pj5He+E9uAj9d8PCX+8Ix4LvY7cUTMCDlfkhngw2DhJ8wlk23u1IvunakdnnN74pfbO2JJBrw==", + "dependencies": { + "@langchain/core": "~0.1.5", + "js-tiktoken": "^1.0.7", + "openai": "^4.19.0", + "zod": "^3.22.3", + "zod-to-json-schema": "3.20.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@librechat/backend": { "resolved": "api", "link": true @@ -5675,6 +6741,60 @@ "node": ">=4.2.0" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@radix-ui/number": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", @@ -6819,6 +7939,38 @@ "node": ">=14.0.0" } }, + "node_modules/@rollup/plugin-alias": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.0.tgz", + "integrity": "sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==", + "dev": true, + "dependencies": { + "slash": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-alias/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "25.0.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz", @@ -6878,6 +8030,26 @@ "node": ">=12" } }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "15.2.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz", @@ -6959,9 +8131,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", - "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -6972,7 +8144,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -8886,7 +10058,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -10114,7 +11286,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -10127,14 +11298,12 @@ "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -10143,7 +11312,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10157,7 +11325,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -11608,7 +12775,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -12485,6 +13651,13 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "optional": true, + "peer": true + }, "node_modules/fast-uri": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", @@ -12560,6 +13733,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -12627,6 +13811,14 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "dependencies": { + "moment": "^2.29.1" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -12793,6 +13985,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.6.0.tgz", + "integrity": "sha512-bnYwHwZ6zB+dM6mGQPEXcFHtAT2WoVzG6H4SIR8HzURVGKJxBW+TqfP3qcJQjTZV3tDqDTo/XZkVmoU/SovV8A==", + "dependencies": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-compat": "0.2.6", + "@firebase/app": "0.9.23", + "@firebase/app-check": "0.8.0", + "@firebase/app-check-compat": "0.3.7", + "@firebase/app-compat": "0.2.23", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "1.4.0", + "@firebase/auth-compat": "0.4.9", + "@firebase/database": "1.0.1", + "@firebase/database-compat": "1.0.1", + "@firebase/firestore": "4.3.2", + "@firebase/firestore-compat": "0.3.22", + "@firebase/functions": "0.10.0", + "@firebase/functions-compat": "0.3.5", + "@firebase/installations": "0.6.4", + "@firebase/installations-compat": "0.2.4", + "@firebase/messaging": "0.12.4", + "@firebase/messaging-compat": "0.2.4", + "@firebase/performance": "0.6.4", + "@firebase/performance-compat": "0.2.4", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-compat": "0.2.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-compat": "0.3.2", + "@firebase/util": "1.9.3" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -13107,7 +14332,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -13326,6 +14550,22 @@ "node": ">=14" } }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "optional": true, + "peer": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/googleapis": { "version": "126.0.1", "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-126.0.1.tgz", @@ -13901,6 +15141,11 @@ "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -13974,6 +15219,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "node_modules/identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -15793,9 +17043,9 @@ } }, "node_modules/js-tiktoken": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.7.tgz", - "integrity": "sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.8.tgz", + "integrity": "sha512-r7XK3E9/I+SOrbAGqb39pyO/rHAS1diAOSRAvaaLfHgXjkUSK9AiSd+r84Vn2f/GvXJYRAxKj8NHrUvqlaH5qg==", "dependencies": { "base64-js": "^1.5.1" } @@ -16099,6 +17349,14 @@ "node": ">=6" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -16387,6 +17645,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -16653,6 +17916,11 @@ "node": ">=0.8.0" } }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -17845,6 +19113,14 @@ "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==" }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/mongodb": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.8.1.tgz", @@ -18151,6 +19427,16 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-html-parser": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", @@ -20963,6 +22249,29 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -21883,7 +23192,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -23590,6 +24898,14 @@ "node": ">=12" } }, + "node_modules/traverse": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", + "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -23940,9 +25256,9 @@ "dev": true }, "node_modules/undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz", + "integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -24710,6 +26026,27 @@ "node": ">=12" } }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/webworkify": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.5.0.tgz", @@ -24871,11 +26208,11 @@ } }, "node_modules/winston": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", - "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", + "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", "dependencies": { - "@colors/colors": "1.5.0", + "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", @@ -24891,6 +26228,31 @@ "node": ">= 12.0.0" } }, + "node_modules/winston-daily-rotate-file": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz", + "integrity": "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^2.0.1", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-daily-rotate-file/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/winston-transport": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", @@ -24917,6 +26279,14 @@ "node": ">= 6" } }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/winston/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -25187,7 +26557,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -25210,7 +26579,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -25228,7 +26596,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -25236,14 +26603,12 @@ "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -25252,7 +26617,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -25283,11 +26647,11 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz", - "integrity": "sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==", + "version": "3.20.3", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.20.3.tgz", + "integrity": "sha512-/Q3wnyxAfCt94ZcrGiXXoiAfRqasxl9CX64LZ9fj+4dKH68zulUtU0uk1WMxQPfAxQ0ZI70dKzcoW7hHj+DwSQ==", "peerDependencies": { - "zod": "^3.21.4" + "zod": "^3.20.0" } }, "node_modules/zwitch": { @@ -25301,7 +26665,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.3.0", + "version": "0.3.4", "license": "ISC", "dependencies": { "axios": "^1.3.4", @@ -25312,7 +26676,9 @@ "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", + "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^25.0.2", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.1.0", "@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-terser": "^0.4.4", diff --git a/package.json b/package.json index b31892444ea..77ddd822650 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "LibreChat", - "version": "0.6.1", + "version": "0.6.5", "description": "", "workspaces": [ "api", @@ -10,6 +10,7 @@ "scripts": { "update": "node config/update.js", "add-balance": "node config/add-balance.js", + "list-balances": "node config/list-balances.js", "rebuild:package-lock": "node config/packages", "reinstall": "node config/update.js -l -g", "b:reinstall": "bun config/update.js -b -l -g", @@ -25,6 +26,7 @@ "upgrade": "node config/upgrade.js", "create-user": "node config/create-user.js", "ban-user": "node config/ban-user.js", + "delete-user": "node config/delete-user.js", "backend": "cross-env NODE_ENV=production node api/server/index.js", "backend:dev": "cross-env NODE_ENV=development npx nodemon api/server/index.js", "backend:stop": "node config/stop-backend.js", @@ -53,7 +55,8 @@ "b:client:dev": "cd client && bun run b:dev", "b:test:client": "cd client && bun run b:test", "b:test:api": "cd api && bun run b:test", - "b:balance": "bun config/add-balance.js" + "b:balance": "bun config/add-balance.js", + "b:list-balances": "bun config/list-balances.js" }, "repository": { "type": "git", diff --git a/packages/data-provider/.gitignore b/packages/data-provider/.gitignore index 40b878db5b1..7b961825b33 100644 --- a/packages/data-provider/.gitignore +++ b/packages/data-provider/.gitignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +test_bundle/ diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index e0af1a0ced9..339e60732f0 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.3.1", + "version": "0.3.4", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", @@ -21,6 +21,7 @@ "clean": "rimraf dist", "build": "npm run clean && rollup -c --silent --bundleConfigAsCjs", "build:watch": "rollup -c -w", + "rollup:api": "npx rollup -c server-rollup.config.js --bundleConfigAsCjs", "test": "jest --coverage --watch", "test:ci": "jest --coverage --ci", "verify": "npm run test:ci", @@ -46,7 +47,9 @@ "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", + "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^25.0.2", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.1.0", "@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-terser": "^0.4.4", diff --git a/packages/data-provider/server-rollup.config.js b/packages/data-provider/server-rollup.config.js new file mode 100644 index 00000000000..fc83037d4fe --- /dev/null +++ b/packages/data-provider/server-rollup.config.js @@ -0,0 +1,40 @@ +import path from 'path'; +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import alias from '@rollup/plugin-alias'; +import json from '@rollup/plugin-json'; + +const rootPath = path.resolve(__dirname, '../../'); +const rootServerPath = path.resolve(__dirname, '../../api'); +const entryPath = path.resolve(rootPath, 'api/server/index.js'); + +console.log('entryPath', entryPath); + +// Define your custom aliases here +const customAliases = { + entries: [{ find: '~', replacement: rootServerPath }], +}; + +export default { + input: entryPath, + output: { + file: 'test_bundle/bundle.js', + format: 'cjs', + }, + plugins: [ + alias(customAliases), + resolve({ + preferBuiltins: true, + extensions: ['.js', '.json', '.node'], + }), + commonjs(), + json(), + ], + external: (id) => { + // More selective external function + if (/node_modules/.test(id)) { + return !id.startsWith('langchain/'); + } + return false; + }, +}; diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts index 380a0dafaf4..6c7f90c672c 100644 --- a/packages/data-provider/src/api-endpoints.ts +++ b/packages/data-provider/src/api-endpoints.ts @@ -69,3 +69,5 @@ export const assistants = (id?: string) => `/api/assistants${id ? `/${id}` : ''} export const files = () => '/api/files'; export const images = () => `${files()}/images`; + +export const avatar = () => `${images()}/avatar`; diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index b02565652bc..74ad766575e 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -197,6 +197,10 @@ export const uploadImage = (data: FormData): Promise => { return request.postMultiPart(endpoints.images(), data); }; +export const uploadAvatar = (data: FormData): Promise => { + return request.postMultiPart(endpoints.avatar(), data); +}; + export const deleteFiles = async (files: f.BatchFile[]): Promise => request.deleteWithOptions(endpoints.files(), { data: { files }, diff --git a/packages/data-provider/src/keys.ts b/packages/data-provider/src/keys.ts index 6da00f7fc4f..ec150d95873 100644 --- a/packages/data-provider/src/keys.ts +++ b/packages/data-provider/src/keys.ts @@ -24,4 +24,5 @@ export enum MutationKeys { updatePreset = 'updatePreset', deletePreset = 'deletePreset', logoutUser = 'logoutUser', + avatarUpload = 'avatarUpload', } diff --git a/packages/data-provider/src/react-query/react-query-service.ts b/packages/data-provider/src/react-query/react-query-service.ts index 186da05e63c..4d7ad00fdf5 100644 --- a/packages/data-provider/src/react-query/react-query-service.ts +++ b/packages/data-provider/src/react-query/react-query-service.ts @@ -347,6 +347,11 @@ export const useLoginUserMutation = (): UseMutationResult< return useMutation((payload: t.TLoginUser) => dataService.login(payload), { onMutate: () => { queryClient.removeQueries(); + localStorage.removeItem('lastConversationSetup'); + localStorage.removeItem('lastSelectedModel'); + localStorage.removeItem('lastSelectedTools'); + localStorage.removeItem('filesToDelete'); + localStorage.removeItem('lastAssistant'); }, }); }; @@ -375,11 +380,6 @@ export const useRefreshTokenMutation = (): UseMutationResult< return useMutation(() => request.refreshToken(), { onMutate: () => { queryClient.removeQueries(); - localStorage.removeItem('lastConversationSetup'); - localStorage.removeItem('lastSelectedModel'); - localStorage.removeItem('lastSelectedTools'); - localStorage.removeItem('filesToDelete'); - localStorage.removeItem('lastAssistant'); }, }); }; diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 64467a8fe18..4698ea6a556 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -22,6 +22,48 @@ export const defaultEndpoints: EModelEndpoint[] = [ EModelEndpoint.anthropic, ]; +export const defaultModels = { + [EModelEndpoint.google]: [ + 'gemini-pro', + 'gemini-pro-vision', + 'chat-bison', + 'chat-bison-32k', + 'codechat-bison', + 'codechat-bison-32k', + 'text-bison', + 'text-bison-32k', + 'text-unicorn', + 'code-gecko', + 'code-bison', + 'code-bison-32k', + ], + [EModelEndpoint.anthropic]: [ + 'claude-2.1', + 'claude-2', + 'claude-1.2', + 'claude-1', + 'claude-1-100k', + 'claude-instant-1', + 'claude-instant-1-100k', + ], + [EModelEndpoint.openAI]: [ + 'gpt-3.5-turbo-16k-0613', + 'gpt-3.5-turbo-16k', + 'gpt-4-1106-preview', + 'gpt-3.5-turbo', + 'gpt-3.5-turbo-1106', + 'gpt-4-vision-preview', + 'gpt-4', + 'gpt-3.5-turbo-instruct-0914', + 'gpt-3.5-turbo-0613', + 'gpt-3.5-turbo-0301', + 'gpt-3.5-turbo-instruct', + 'gpt-4-0613', + 'text-davinci-003', + 'gpt-4-0314', + ], +}; + export const alternateName = { [EModelEndpoint.openAI]: 'OpenAI', [EModelEndpoint.assistant]: 'Assistants', @@ -33,6 +75,11 @@ export const alternateName = { [EModelEndpoint.anthropic]: 'Anthropic', }; +export enum AuthKeys { + GOOGLE_SERVICE_KEY = 'GOOGLE_SERVICE_KEY', + GOOGLE_API_KEY = 'GOOGLE_API_KEY', +} + export const endpointSettings = { [EModelEndpoint.google]: { model: { @@ -43,6 +90,8 @@ export const endpointSettings = { max: 2048, step: 1, default: 1024, + maxGeminiPro: 8192, + defaultGeminiPro: 8192, }, temperature: { min: 0, @@ -87,27 +136,18 @@ export const modularEndpoints = new Set([ export const supportsFiles = { [EModelEndpoint.openAI]: true, + [EModelEndpoint.google]: true, [EModelEndpoint.assistant]: true, + [EModelEndpoint.azureOpenAI]: true, }; -export const openAIModels = [ - 'gpt-3.5-turbo-16k-0613', - 'gpt-3.5-turbo-16k', - 'gpt-4-1106-preview', - 'gpt-3.5-turbo', - 'gpt-3.5-turbo-1106', - 'gpt-4-vision-preview', - 'gpt-4', - 'gpt-3.5-turbo-instruct-0914', - 'gpt-3.5-turbo-0613', - 'gpt-3.5-turbo-0301', - 'gpt-3.5-turbo-instruct', - 'gpt-4-0613', - 'text-davinci-003', - 'gpt-4-0314', -]; +export const supportsBalanceCheck = { + [EModelEndpoint.openAI]: true, + [EModelEndpoint.azureOpenAI]: true, + [EModelEndpoint.gptPlugins]: true, +}; -export const visionModels = ['gpt-4-vision', 'llava-13b']; +export const visionModels = ['gpt-4-vision', 'llava-13b', 'gemini-pro-vision']; export const eModelEndpointSchema = z.nativeEnum(EModelEndpoint); @@ -189,7 +229,6 @@ export const tMessageSchema = z.object({ .default(() => new Date().toISOString()), current: z.boolean().optional(), unfinished: z.boolean().optional(), - submitting: z.boolean().optional(), searchResult: z.boolean().optional(), finish_reason: z.string().optional(), }); @@ -309,17 +348,31 @@ export const googleSchema = tConversationSchema topP: true, topK: true, }) - .transform((obj) => ({ - ...obj, - model: obj.model ?? google.model.default, - modelLabel: obj.modelLabel ?? null, - promptPrefix: obj.promptPrefix ?? null, - examples: obj.examples ?? [{ input: { content: '' }, output: { content: '' } }], - temperature: obj.temperature ?? google.temperature.default, - maxOutputTokens: obj.maxOutputTokens ?? google.maxOutputTokens.default, - topP: obj.topP ?? google.topP.default, - topK: obj.topK ?? google.topK.default, - })) + .transform((obj) => { + const isGeminiPro = obj?.model?.toLowerCase()?.includes('gemini-pro'); + + const maxOutputTokensMax = isGeminiPro + ? google.maxOutputTokens.maxGeminiPro + : google.maxOutputTokens.max; + const maxOutputTokensDefault = isGeminiPro + ? google.maxOutputTokens.defaultGeminiPro + : google.maxOutputTokens.default; + + let maxOutputTokens = obj.maxOutputTokens ?? maxOutputTokensDefault; + maxOutputTokens = Math.min(maxOutputTokens, maxOutputTokensMax); + + return { + ...obj, + model: obj.model ?? google.model.default, + modelLabel: obj.modelLabel ?? null, + promptPrefix: obj.promptPrefix ?? null, + examples: obj.examples ?? [{ input: { content: '' }, output: { content: '' } }], + temperature: obj.temperature ?? google.temperature.default, + maxOutputTokens, + topP: obj.topP ?? google.topP.default, + topK: obj.topK ?? google.topK.default, + }; + }) .catch(() => ({ model: google.model.default, modelLabel: null, @@ -579,6 +632,8 @@ export const getResponseSender = (endpointOption: TEndpointOption): string => { if (endpoint === EModelEndpoint.google) { if (modelLabel) { return modelLabel; + } else if (model && model.includes('gemini')) { + return 'Gemini'; } else if (model && model.includes('code')) { return 'Codey'; } diff --git a/packages/data-provider/src/types/files.ts b/packages/data-provider/src/types/files.ts index 242ad323ed5..95f329f6528 100644 --- a/packages/data-provider/src/types/files.ts +++ b/packages/data-provider/src/types/files.ts @@ -10,6 +10,10 @@ export type FileUploadResponse = { width: number; }; +export type AvatarUploadResponse = { + url: string; +}; + export type FileUploadBody = { formData: FormData; file_id: string; @@ -21,6 +25,12 @@ export type UploadMutationOptions = { onError?: (error: unknown, variables: FileUploadBody, context?: unknown) => void; }; +export type UploadAvatarOptions = { + onSuccess?: (data: AvatarUploadResponse, variables: FormData, context?: unknown) => void; + onMutate?: (variables: FormData) => void | Promise; + onError?: (error: unknown, variables: FormData, context?: unknown) => void; +}; + export type DeleteFilesResponse = { message: string; result: Record;