Compare commits

...

609 Commits

Author SHA1 Message Date
Ventz Petkov
37ae484fbc 🚅 docs: Updated Example for LiteLLM ports and Volume mount (#2941)
* Added necessary "ports" section for it to work by default
* Added (commented out) example GCP Vertex volume mount for auth config and for ENV variable.
2024-06-01 08:51:18 -04:00
Danny Avila
8939d8af37 🦙 feat: Add LLama 3 System Context Length (#2938) 2024-05-31 12:16:08 -04:00
Danny Avila
f9a0166352 🔄 refactor(EditPresetDialog): Update Model on Endpoint Change (#2936)
* refactor(EditPresetDialog): dynamically update current editable preset model on endpoint change

* feat: Add null check for models in EditPresetDialog

* chore(AlertDialogPortal): typing

* fix(EditPresetDialog): prevent Unknown endpoint edge case for custom endpoints
2024-05-31 11:43:14 -04:00
Danny Avila
248dfb8b5b 🐛 fix: Resolve Preset Button Disappearing in Mobile View (#2935)
* refactor: Update import paths for ExportAndShareMenu component and add localization

* fix: mobile view for export/share button
2024-05-31 08:46:09 -04:00
Danny Avila
b8e35002f4 🗣️ fix: Set Audio Run ID at Top of Autoplayback Request (#2926) 2024-05-30 22:01:40 -04:00
Danny Avila
8318f26d66 🔉 feat: TTS/STT rate limiters (#2925)
* fix: remove double initialization of speech routes

* refactor(useMessageHelpers): more consistent latestMessage updates based on unique textKey and early returns when setting

* feat: TTS/STT rate limiters

* chore: remove console log

* fix: make modular chat true by default
2024-05-30 18:39:21 -04:00
Danny Avila
08d6bea359 🛠️ refactor: Improve Logging and Error Handling in ToolService and useSSE (#2922)
* refactor(ToolService): streamline logging and tool error handling, also ensure generated outputs always have `output` field

* refactor(useSSE): error message for server connection issue

* refactor: add back capture group of sensitive information.js

* hotfix: cohere chinese character unicode issue, return aggregated reply
2024-05-30 12:58:43 -04:00
Arthur Barrett
a6058c5669 🔧 chore: Update OpenIDStrategy Logging (#2911) 2024-05-30 10:48:03 -04:00
Danny Avila
e0402b71f0 🎨 style: Focus Outlines (#2913)
* style: add back focus ring removal as the outer/inner containers maintain focus stylings

* style: add focus outline to default buttonVariants class
2024-05-29 23:07:52 -04:00
Yuichi Oneda
a618266905 🔒 feature(auth): LDAP Authentication (#2859)
* 🔧 chore: npm install passport-ldapauth

*  feat(auth): add ldap authentication support

* chore: merge conflict fix

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2024-05-29 17:46:20 -04:00
Danny Avila
d5a7806e32 ⬆️ chore: bump @langchain/google-vertexai to v0.0.17 (#2905) 2024-05-29 10:47:05 -04:00
Danny Avila
e2cb2905e7 🖱️ feat: Minor Accessibility Changes (#2903)
* feat: focus rings for dialog buttons

* chore: temp: soft removal of removeFocusOutlines

* feat: allow tabbing of endpoint menu
2024-05-29 10:14:30 -04:00
Danny Avila
3f600f0d3f ⬇️ fix: JSON LibreChat Imports (#2897)
* chore: remove unused code

* refactor: Update NewChatButtonIcon component to use JSX syntax

The NewChatButtonIcon component in the Nav folder has been updated to use JSX syntax instead of calling the Icon function directly. This change improves code readability and maintainability.

* remove use memo

* refactor: allow passing `select` to messages db query

* fix: initial fix for non-recursive messages

* ci: first pass, importers test rewrite

* fix(groupConversationsByDate): handle edge case of conversation.updatedAt being null

* fix: correctly handle non-recursive uploads

* feat: imports non-recursive conversations with branches correctly

* feat: support retaining original options on import

* refactor: Allow `messageTree` field for Import of non-recursive conversations
2024-05-29 09:15:05 -04:00
Yuichi Oneda
c9e7d4ac18 🚑 fix(export): Export Issue with New Chat (#2777)
* 🚑 fix: re-fetch messages when exporting

* Revert "🚑 fix: re-fetch messages when exporting"

This reverts commit 693b86e955.

* 🚑 fix: use the same logic to get export data as useChatHelper

* refactor(useExportConversation): use query cache to build messages tree on request

* chore: organize imports

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2024-05-28 18:10:33 -04:00
Danny Avila
40685f6eb4 🚀 refactor: Enhance Custom Endpoints, Message Logic, and Payload Handling (#2895)
* chore: use node-fetch for OpenAIClient fetch key for non-crashing usage of AbortController in Bun runtime

* chore: variable order

* fix(useSSE): prevent finalHandler call in abortConversation to update messages/conversation after user navigated away

* chore: params order

* refactor: organize intermediate message logic and ensure correct variables are passed

* fix: Add stt and tts routes before upload limiters, prevent bans

* fix(abortRun): temp fix to delete unfinished messages to avoid message thread parent relationship issues

* refactor: Update AnthropicClient to use node-fetch for fetch key and add proxy support

* fix(gptPlugins): ensure parentMessageId/messageId relationship is maintained

* feat(BaseClient): custom fetch function to analyze/edit payloads just before sending (also prevents abortController crash on Bun runtime)

* feat: `directEndpoint` and `titleMessageRole` custom endpoint options

* chore: Bump version to 0.6.6 in data-provider package.json
2024-05-28 14:52:12 -04:00
Yuichi Oneda
0ee060d730 🚑 fix: Prevent Infinite Re-Rendering of the Password Reset UI (#2887)
* 🔧 fix: prevent unnecessary re-rendering of components using useLocalize hook

The useLocalize hook now uses useCallback to create a memoized version of the localize function. This will prevent unnecessary recalculations when the language value changes.

* 🚑 fix: not reset the bodyText if it has a value set.
2024-05-28 14:07:08 -04:00
Danny Avila
5dc5d875ba 🤖 feat: Private Assistants (#2881)
* feat: add configuration for user private assistants

* filter private assistant message requests

* add test for privateAssistants

* add privateAssistants configuration to tests

* fix: destructuring error when assistants config is not added

* chore: revert chat controller changes

* chore: add payload type, add metadata types

* feat: validateAssistant

* refactor(fetchAssistants): allow for flexibility

* feat: validateAuthor

* refactor: return all assistants to ADMIN role

* feat: add assistant doc on assistant creation

* refactor(listAssistants): use `listAllAssistants` to exhaustively fetch all assistants

* chore: add suggestion to tts error

* refactor(validateAuthor): attempt database check first

* refactor: author validation when patching/deleting assistant

---------

Co-authored-by: Leon Juenemann <leon.juenemann@maibornwolff.de>
2024-05-28 08:27:45 -04:00
Yuichi Oneda
9f2538fcd9 ♻️ refactor: Login and Registration component Improvement (#2716)
* ♻️ refactor: Login form improvement

* display error message when API is down
* add loading animation to Login form while fetching data
* optimize startupConfig to fetch data only on initial render to prevent unnecessary API calls

* 🚑 fix: clear authentication error messages on successful login

* ♻️ refactor: componentize duplicate codes on registration and login screens

* chore: update types

* refactor: layout rendering order

* refactor: startup title fix

* refactor: reset/request-reset-password under new AuthLayout

* ci: fix Login.spec.ts

* ci: fix registration.spec.tsx

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2024-05-28 08:25:07 -04:00
Danny Avila
2b7a973a33 😎 fix: Azure Bug, short default max_tokens response using gpt-4-vision-preview (#2885) 2024-05-27 22:56:26 -04:00
Danny Avila
c704a23749 ⚛️ fix(atomWithLocalStorage): Handle Parsing Error (#2883) 2024-05-27 21:23:46 -04:00
Danny Avila
eb5733083e 🔈 fix(tts): update min value for playback rate (#2880)
* 🔈 fix: update min value for playback rate in TTS component

* fix: prevent playbackRate from being set if less than or equal to 0
2024-05-27 12:51:45 -04:00
Danny Avila
b80f38e49e 🔢 style: ol Counter Fix (#2867) 2024-05-26 21:13:12 -04:00
Yuichi Oneda
4369e75ca7 🚑 fix: resolve missing data in infinite queries (#2852)
* An issue where the InfiniteQuery was missing data
* Post add/delete operations, inconsistencies between client-side data structures and the database could lead to data being missed or duplicated.
* To address this, implemented normalization of client data following add/delete operations.
* performed refetching of data in the last page when necessary to ensure consistency.
2024-05-24 12:38:38 -04:00
Danny Avila
35ba4ba1a4 🔊 fix(tts): NotAllowedError (mobile/safari), Unsupported MediaSource type (firefox), Hide Audio Element (#2854)
* fix: hide audio element on mobile

* chore: add tts docs link

* fix: select voice option on first render

* fix: NotAllowedError, prevent async playback for mobile triggers, consolidate MessageAudio code, user user-triggered unmutes

* fix: Firefox/unsupported type for MediaSource hack

* refactor(STT): make icon red when recording. consolidate logic to AudioRecorder component

* fix: revert Redis changes to use separate client for sessions
2024-05-24 12:18:11 -04:00
Arthur Barrett
dcd2e3e62d 🔧 fix(redis): Resolve Redis Standalone vs Cluster mode discrepancy for social logins (#2848) 2024-05-24 09:54:33 -04:00
Danny Avila
514a502b9c ⏯️ fix(tts): Resolve Voice Selection and Manual Playback Issues (#2845)
* fix: voice setting for autoplayback TTS

* fix(useTextToSpeechExternal): resolve stateful playback issues and consolidate state logic

* refactor: initialize tts voice and provider schema once per request

* fix(tts): edge case, longer text inputs. TODO: use continuous stream for longer text inputs

* fix(tts): pause global audio on conversation change

* refactor: keyvMongo ban cache to allow db updates for unbanning, to prevent server restart

* chore: eslint fix

* refactor: make ban cache exclusively keyvMongo
2024-05-23 16:27:36 -04:00
Danny Avila
8e66683577 🗣️ fix(tts): Add Text Parser for Message Content Parts (#2840)
* fix: manual TTS trigger for message content parts

* ci(streamAudio): processChunks test
2024-05-22 23:27:37 -04:00
suzuki.sh
dc1778b11f 🐋 chore: add restart: always to meilisearch service in docker-compose.yml (#2788) 2024-05-22 23:20:35 -04:00
nidasfly
795bb9c568 🌏 refactor: Improve Title Prompt for Multilingual Titles (#2832) 2024-05-22 23:18:59 -04:00
gjz
a937650df6 🔧 fix: Recognize azureAssistants Endpoint Config (#2824)
* fix bug: azureAssistants endpoint config

* refactor(assistants): make default value last

* refactor(AppService): make assistantsConfigSetup potentially undefined value last arg

* chore: Update JSDocs params assistants.js

* Update assistants.js

---------

Co-authored-by: 彭修照 <pengxiuzhao.uh@haier.com>
Co-authored-by: Danny Avila <danacordially@gmail.com>
2024-05-22 23:17:52 -04:00
bsu3338
6cf1c85363 🐞 fix: add LocalAI to GET /voices (#2837) 2024-05-22 22:42:27 -04:00
Danny Avila
b3e03b75d0 🔉 feat: Speech-to-text / Text-to-speech (initial support) (#2836)
* Update TextChat.jsx

* Update SubmitButton.jsx

* Update TextChat.jsx

* Update SubmitButton.jsx

* Create ListeningIcon.tsx

* Update index.ts

* Update SubmitButton.jsx

* Update TextChat.jsx

* Update ListeningIcon.tsx

* Update ListeningIcon.tsx

* Create SpeechRecognition.tsx

* Update TextChat.jsx

* Update TextChat.jsx

* Update SpeechRecognition.tsx

* Update TextChat.jsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update SubmitButton.jsx

* Update TextChat.jsx

* Update SpeechRecognition.tsx

* Create SpeechSynthesis.tsx

* Update index.jsx

* Update SpeechSynthesis.tsx

* Update SpeechRecognition.tsx

* Update TextChat.jsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update TextChat.jsx

* Squashed commit of the following:

commit 28230d9305
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Sun Sep 3 02:44:26 2023 +0200

    feat: delete button confirm (#875)

    * base for confirm delete

    * more like OpenAI

commit 2b54e3f9fe
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Fri Sep 1 14:20:51 2023 -0400

    update: install script (#858)

commit 1cd0fd9d5a
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Fri Sep 1 08:12:35 2023 -0400

    doc: Hugging Face Deployment (#867)

    * docs: update ToC

    * docs: update ToC

    * update huggingface.md

    * update render.md

    * update huggingface.md

    * update mongodb.md

    * update huggingface.md

    * update README.md

commit aeeb3d3050
Author: Mu Yuan <yuanmu.email@gmail.com>
Date:   Thu Aug 31 07:21:27 2023 +0800

    Update Zh.tsx (#862)

    * Update Zh.tsx

    Changed the translation of several words to make it more relevant to Chinese usage habits.

    * Update Zh.tsx

    Changed the translation of several words to make it more relevant to Chinese usage habits

commit 80e2e2675b
Author: Raí <140329135+itzraiss@users.noreply.github.com>
Date:   Mon Aug 28 18:05:46 2023 -0300

    Translation of 'com_ui_pay_per_call:' to Spanish and Portuguese that were missing. (#857)

    * Update Br.tsx

    * Update Es.tsx

    * Update Br.tsx

    * Update Es.tsx

commit 3574d0b823
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 28 14:49:26 2023 -0400

    docs: make_your_own.md formatting fix for mkdocs (#855)

commit d672ac690d
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 28 14:24:10 2023 -0400

    Release v0.5.8 (#854)

    * chore: add 'api' image to tag release workflow

    * docs: update DO deployment docs to include instruction about latest stable release, as well as security best practices

    * Release v0.5.8

    * docs: Update digitalocean.md with firewall section images

    * docs: make_your_own.md formatting fix for mkdocs

commit d3e7627046
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 28 12:03:08 2023 -0400

    refactor(plugins): Improve OpenAPI handling, Show Multiple Plugins, & Other Improvements (#845)

    * feat(PluginsClient.js): add conversationId to options object in the constructor
    feat(PluginsClient.js): add support for Code Interpreter plugin
    feat(PluginsClient.js): add support for Code Interpreter plugin in the availableTools manifest
    feat(CodeInterpreter.js): add CodeInterpreterTools module
    feat(CodeInterpreter.js): add RunCommand class
    feat(CodeInterpreter.js): add ReadFile class
    feat(CodeInterpreter.js): add WriteFile class
    feat(handleTools.js): add support for loading Code Interpreter plugin

    * chore(api): update langchain dependency to version 0.0.123

    * fix(CodeInterpreter.js): add support for extracting environment from code
    fix(WriteFile.js): add support for extracting environment from data
    fix(extractionChain.js): add utility functions for creating extraction chain from Zod schema
    fix(handleTools.js): refactor getOpenAIKey function to handle user-provided API key
    fix(handleTools.js): pass model and openAIApiKey to CodeInterpreter constructor

    * fix(tools): rename CodeInterpreterTools to E2BTools
    fix(tools): rename code_interpreter pluginKey to e2b_code_interpreter

    * chore(PluginsClient.js): comment out unused import and function findMessageContent
    feat(PluginsClient.js): add support for CodeSherpa plugin
    feat(PluginsClient.js): add CodeSherpaTools to available tools
    feat(PluginsClient.js): update manifest.json to include CodeSherpa plugin
    feat(CodeSherpaTools.js): create RunCode and RunCommand classes for CodeSherpa plugin

    feat(E2BTools.js): Add E2BTools module for extracting environment from code and running commands, reading and writing files
    fix(codesherpa.js): Remove codesherpa module as it is no longer needed

    feat(handleTools.js): add support for CodeSherpaTools in loadTools function
    feat(loadToolSuite.js): create loadToolSuite utility function to load a suite of tools

    * feat(PluginsClient.js): add support for CodeSherpa v2 plugin
    feat(PluginsClient.js): add CodeSherpa v1 plugin to available tools
    feat(PluginsClient.js): add CodeSherpa v2 plugin to available tools
    feat(PluginsClient.js): update manifest.json for CodeSherpa v1 plugin
    feat(PluginsClient.js): update manifest.json for CodeSherpa v2 plugin
    feat(CodeSherpa.js): implement CodeSherpa plugin for interactive code and shell command execution
    feat(CodeSherpaTools.js): implement RunCode and RunCommand plugins for CodeSherpa v1
    feat(CodeSherpaTools.js): update RunCode and RunCommand plugins for CodeSherpa v2

    fix(handleTools.js): add CodeSherpa import statement
    fix(handleTools.js): change pluginKey from 'codesherpa' to 'codesherpa_tools'
    fix(handleTools.js): remove model and openAIApiKey from options object in e2b_code_interpreter tool
    fix(handleTools.js): remove openAIApiKey from options object in codesherpa_tools tool
    fix(loadToolSuite.js): remove model and openAIApiKey parameters from loadToolSuite function

    * feat(initializeFunctionsAgent.js): add prefix to agentArgs in initializeFunctionsAgent function

    The prefix is added to the agentArgs in the initializeFunctionsAgent function. This prefix is used to provide instructions to the agent when it receives any instructions from a webpage, plugin, or other tool. The agent will notify the user immediately and ask them if they wish to carry out or ignore the instructions.

    * feat(PluginsClient.js): add ChatTool to the list of tools if it meets the conditions
    feat(tools/index.js): import and export ChatTool
    feat(ChatTool.js): create ChatTool class with necessary properties and methods

    * fix(initializeFunctionsAgent.js): update PREFIX message to include sharing all output from the tool
    fix(E2BTools.js): update descriptions for RunCommand, ReadFile, and WriteFile plugins to provide more clarity and context

    * chore: rebuild package-lock after rebase

    * chore: remove deleted file from rebase

    * wip: refactor plugin message handling to mirror chat.openai.com, handle incoming stream for plugin use

    * wip: new plugin handling

    * wip: show multiple plugins handling

    * feat(plugins): save new plugins array

    * chore: bump langchain

    * feat(experimental): support streaming in between plugins

    * refactor(PluginsClient): factor out helper methods to avoid bloating the class, refactor(gptPlugins): use agent action for mapping the name of action

    * fix(handleTools): fix tests by adding condition to return original toolFunctions map

    * refactor(MessageContent): Allow the last index to be last in case it has text (may change with streaming)

    * feat(Plugins): add handleParsingErrors, useful when LLM does not invoke function params

    * chore: edit out experimental codesherpa integration

    * refactor(OpenAPIPlugin): rework tool to be 'function-first', as the spec functions are explicitly passed to agent model

    * refactor(initializeFunctionsAgent): improve error handling and system message

    * refactor(CodeSherpa, Wolfram): optimize token usage by delegating bulk of instructions to system message

    * style(Plugins): match official style with input/outputs

    * chore: remove unnecessary console logs used for testing

    * fix(abortMiddleware): render markdown when message is aborted

    * feat(plugins): add BrowserOp

    * refactor(OpenAPIPlugin): improve prompt handling

    * fix(useGenerations): hide edit button when message is submitting/streaming

    * refactor(loadSpecs): optimize OpenAPI spec loading by only loading requested specs instead of all of them

    * fix(loadSpecs): will retain original behavior when no tools are passed to the function

    * fix(MessageContent): ensure cursor only shows up for last message and last display index
    fix(Message): show legacy plugin and pass isLast to Content

    * chore: remove console.logs

    * docs: update docs based on breaking changes and new features
    refactor(structured/SD): use description_for_model for detailed prompting

    * docs(azure): make plugins section more clear

    * refactor(structured/SD): change default payload to SD-WebUI to prefer realism and config for SDXL

    * refactor(structured/SD): further improve system message prompt

    * docs: update breaking changes after rebase

    * refactor(MessageContent): factor out EditMessage, types, Container to separate files, rename Content -> Markdown

    * fix(CodeInterpreter): linting errors

    * chore: reduce browser console logs from message streams

    * chore: re-enable debug logs for plugins/langchain to help with user troubleshooting

    * chore(manifest.json): add [Experimental] tag to CodeInterpreter plugins, which are not intended as the end-all be-all implementation of this feature for Librechat

commit 66b8580487
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Mon Aug 28 09:18:25 2023 -0400

    docs: third-party tools (#848)

    * docs: third-party tools

    * docs: third-party tools

    * Update third-party.md

    * Update third-party.md

    ---------

    Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>

commit 9791a78161
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Mon Aug 28 15:14:05 2023 +0200

    adjust the animation (#843)

commit 3797ec6082
Author: Ronith <87087292+ronith256@users.noreply.github.com>
Date:   Mon Aug 28 18:43:50 2023 +0530

    feat: Add Code Interpreter Plugin (#837)

    * feat: Add Code Interpreter Plugin

    Adds a Simple Code Interpreter Plugin.
    ## Features:
    - Runs code using local Python Environment

    ## Issues
    - Code execution is not sandboxed.

    * Add Docker Sandbox for Python Server

commit e2397076a2
Author: Alex Zhang <ztc2011@gmail.com>
Date:   Mon Aug 28 00:55:34 2023 +0800

    🌐: Chinese Translation (#846)

commit 50c15c704f
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Sat Aug 26 19:36:59 2023 -0400

    Language translation: Polish (#840)

    * Language translation: Polish

    * Language translation: Polish

    * Revert changes in language-contributions.md

commit 29d3640546
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Sat Aug 26 19:36:25 2023 -0400

    docs: updates (#841)

commit 39c626aa8e
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 25 09:29:19 2023 -0400

    fix: isEdited edge case where latest Message is not saved due to aborting too quickly

commit ae5c06f381
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 25 09:13:50 2023 -0400

    fix(chatGPTBrowser): render markdown formatting by setting isCreatedByUser, fix(useMessageHandler): avoid double appearance of cursor by setting latest message at initial response creation time

commit 9ef1686e18
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Thu Aug 24 20:24:47 2023 -0400

    Update mkdocs.yml

commit 5bbe411569
Author: Flynn <dev@flynnbuckingham.com>
Date:   Thu Aug 24 20:20:37 2023 -0400

    Add podman installation instructions. Update dockerfile to stub env (#819)

    * Added podman container installation docs. Updated dockerfile to stub env file if not present in source

    * Fix typos

commit 887fec99ca
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Fri Aug 25 02:11:27 2023 +0200

    🌐: Russian Translation (#830)

commit 007d51ede1
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Fri Aug 25 02:10:48 2023 +0200

    feat: facebook login (#820)

    * Facebook strategy

    * Update user_auth_system.md

    * Update user_auth_system.md

commit a569020312
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Thu Aug 24 21:59:11 2023 +0200

    Fix Meilisearch error and refactor of the server index.js (#832)

    * fix meilisearch error at startup

    * limit the nesting

    * disable useless console log

    * fix(indexSync.js): removed redundant searchEnabled

    * refactor(index.js): moved configureSocialLogins to a new file

    * refactor(socialLogins.js): removed unnecessary conditional

commit 37347d4683
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Wed Aug 23 16:14:17 2023 -0400

    fix(registration): Make Username optional (#831)

    * fix(User.js): update validation schema for username field, allow empty string as a valid value
    fix(validators.js): update validation schema for username field, allow empty string as a valid value
    fix(Registration.tsx, validators.js): update validation rules for name and username fields, change minimum length to 2 and maximum length to 80, assure they match and allow empty string as a valid value
    fix(Eng.tsx): update localization string for com_auth_username, indicate that it is optional

    * fix(User.js): update regex pattern for username validation to allow special characters @#$%&*()
    fix(validators.js): update regex pattern for username validation to allow special characters @#$%&*()

    * fix(Registration.spec.tsx): fix validation error message for username length requirement

commit d38e463d34
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Wed Aug 23 13:44:40 2023 -0400

    fix(bingAI): markdown and error formatting for final stream response (#829)

    * fix(bingAI): markdown formatting for final stream response due to new strict payload validation on the frontend

    * fix: add missing prop to bing Error response

commit 7dc27b10f1
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Tue Aug 22 18:44:59 2023 -0400

    feat: Edit AI Messages, Edit Messages in Place (#825)

    * refactor: replace lodash import with specific function import

    fix(api): esm imports to cjs

    * refactor(Messages.tsx): convert to TS, out-source scrollToDiv logic to a custom hook
    fix(ScreenshotContext.tsx): change Ref to RefObject in ScreenshotContextType
    feat(useScrollToRef.ts): add useScrollToRef hook for scrolling to a ref with throttle
    fix(Chat.tsx): update import path for Messages component
    fix(Search.tsx): update import path for Messages component

    * chore(types.ts): add TAskProps and TOptions types
    refactor(useMessageHandler.ts): use TAskFunction type for ask function signature

    * refactor(Message/Content): convert to TS, move Plugin component to Content dir

    * feat(MessageContent.tsx): add MessageContent component for displaying and editing message content
    feat(index.ts): export MessageContent component from Messages/Content directory

    * wip(Message.jsx): conversion and use of new component in progress

    * refactor: convert Message.jsx to TS and fix typing/imports based on changes

    * refactor: add typed props and refactor MultiMessage to TS, fix typing issues resulting from the conversion

    * edit message in progress

    * feat: complete edit AI message logic, refactor continue logic

    * feat(middleware): add validateMessageReq middleware
    feat(routes): add validation for message requests using validateMessageReq middleware
    feat(routes): add create, read, update, and delete routes for messages

    * feat: complete frontend logic for editing messages in place
    feat(messages.js): update route for updating a specific message
    - Change the route for updating a message to include the messageId in the URL
    - Update the request handler to use the messageId from the request parameters and the text from the request body
    - Call the updateMessage function with the updated parameters

    feat(MessageContent.tsx): add functionality to update a message
    - Import the useUpdateMessageMutation hook from the data provider
    - Destructure the conversationId, parentMessageId, and messageId from the message object
    - Create a mutation function using the useUpdateMessageMutation hook
    - Implement the updateMessage function to call the mutation function with the updated message parameters
    - Update the messages state to reflect the updated message text

    feat(api-endpoints.ts): update messages endpoint to include messageId
    - Update the messages endpoint to include the messageId as an optional parameter

    feat(data-service.ts): add updateMessage function
    - Implement the updateMessage function to make a PUT request to

    * fix(messages.js): make updateMessage function asynchronous and await its execution

    * style(EditIcon): make icon active for AI message

    * feat(gptPlugins/anthropic): add edit support

    * fix(validateMessageReq.js): handle case when conversationId is 'new' and return empty array
    feat(Message.tsx): pass message prop to SiblingSwitch component
    refactor(SiblingSwitch.tsx): convert to TS

    * fix(useMessageHandler.ts): remove message from currentMessages if isContinued is true
    feat(useMessageHandler.ts): add support for submission messages in setMessages
    fix(useServerStream.ts): remove unnecessary conditional in setMessages
    fix(useServerStream.ts): remove isContinued variable from submission

    * fix(continue): switch to continued message generation when continuing an earlier branch in conversation

    * fix(abortMiddleware.js): fix condition to check partialText length
    chore(abortMiddleware.js): add error logging when abortMessage fails

    * refactor(MessageHeader.tsx): convert to TS
    fix(Plugin.tsx): add default value for className prop in Plugin component

    * refactor(MultiMessage.tsx): remove commented out code
    docs(MultiMessage.tsx): update comment to clarify when siblingIdx is reset

    * fix(GenerationButtons): optimistic state for continue button

    * fix(MessageContent.tsx): add data-testid attribute to message text editor
    fix(messages.spec.ts): update waitForServerStream function to include edit endpoint check
    feat(messages.spec.ts): add test case for editing messages

    * fix(HoverButtons & Message & useGenerations): Refactor edit functionality and related conditions

    - Update enterEdit function signature and prop
    - Create and utilize hideEditButton variable
    - Enhance conditions for edit button visibility and active state
    - Update button event handlers
    - Introduce isEditableEndpoint in useGenerations and refine continueSupported condition.

    * fix(useGenerations.ts): fix condition for hideEditButton to include error and searchResult
    chore(data-provider): bump version to 0.1.6
    fix(types.ts): add status property to TError type

    * chore: bump @dqbd/tiktoken to 1.0.7

    * fix(abortMiddleware.js): add required isCreatedByUser property to the error response object

    * refactor(Message.tsx): remove unnecessary props from SiblingSwitch component, as setLatestMessage is firing on every switch already
    refactor(SiblingSwitch.tsx): remove unused imports and code

    * chore(BaseClient.js): move console.debug statements back inside if block

commit db77163f5d
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Tue Aug 22 14:15:14 2023 +0200

    docs: update chimeragpt (#826)

    * Update free_ai_apis.md

    * Update free_ai_apis.md

commit 4a4e803df3
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Mon Aug 21 20:15:18 2023 +0200

    style(Dialog): Improved Close Button ("X") position (#824)

commit 909b00c752
Author: Daniel Avila <messagedaniel@protonmail.com>
Date:   Sun Aug 20 21:04:36 2023 -0400

    fix(HoverButtons): light/dark styling to match official site

commit 61dcb4d307
Author: Naosuke Yokoe <ankerasoy@gmail.com>
Date:   Sat Aug 19 20:11:31 2023 +0900

    feat: Azure Cognitive Search Plugin (#815)

    * feat(AzureCognitiveSearchPlugin)

    * feat(tools/AzureCognitiveSearch.js): Add a new plugin (not structured
      version)
    * feat(tools/structured/AzureCognitiveSearch.js): Add a new plugin (structured version)
    * feat(tools/manifest.json, tools/index.js, tools/util/handleTools.js):
      Add configurations for the plugin
    * feat(api/package.json, package-lock.json): Installed a new package for the
      plugin (@azure/search-documents)
    * feat(.env.example): Add new environment variables for the plugin

    Here is the link to the corresponding discussion page:
    https://github.com/danny-avila/LibreChat/discussions/567

    * docs(AzureCognitiveSearchPlugin)

    * docs(features/plugins/azure_cognitive_search.md): Add a new document
      for the plugin

    * (fix:.env.example)

    * reverted extra whitespaces removed by the editor

    * docs(mkdocs.yml)

    * Add the Azure Cognitive Search Plugin's documentation item to
    mkdocs.yml.

commit 3c7f67fa76
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 18 12:40:33 2023 -0400

    fix(abortMiddleware): handle early abort error where userMessage.conversationId is undefined. In this case, the userId will be used as the abortKey

commit c74c68a135
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 18 12:10:30 2023 -0400

    refactor(MessageHandler -> useServerStream): convert all relating files to TS and correct typings based on this change: properly refactor MessageHandler to a custom hook, where it's passed a submission object to instantiate the stream. This is the bare minimum groundwork for potentially having multiple streams running, which would be a big project to modularize a lot of the global state into maps/multiple streams, particular useful for having multiple views in place

commit 8b4d3c2c21
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 18 12:04:29 2023 -0400

    refactor(routes): convert to TS

commit d612cfcb45
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 18 12:02:39 2023 -0400

    chore(Auth): reorder exports in Auth component
    fix(PluginAuthForm): handle case when pluginKey is null or undefined
    fix(PluginStoreDialog): handle case when getAvailablePluginFromKey is null or undefined
    fix(AuthContext): make authConfig optional in AuthContextProvider
    feat(hooks): add useServerStream hook
    fix(conversation): setSubmission to null instead of empty object
    fix(preset): specify type for presets atom
    fix(search): specify type for isSearchEnabled atom
    fix(submission): specify type for submission atom

commit c40b95f424
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Fri Aug 18 16:11:00 2023 +0200

    feat: Disable Registration with social login (#813)

    * Google, Github and Discord

    * update .env.example with ALLOW_SOCIAL_REGISTRATION

    * fix some conflict

    * refactor strategy

    * Update user_auth_system.md

    * Update user_auth_system.md

commit 46ed5aaccd
Author: Patrick <psarnowski@gmail.com>
Date:   Fri Aug 18 09:38:24 2023 -0400

    Show the response scores from Bing. (#814)

commit 1dacfa49f0
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Thu Aug 17 20:32:31 2023 +0200

    update profile picture (#792)

commit afd43afb60
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Thu Aug 17 12:50:05 2023 -0400

    feat(GPT/Anthropic): Continue Regenerating & Generation Buttons (#808)

    * feat(useMessageHandler.js/ts): Refactor and add features to handle user messages, support multiple endpoints/models, generate placeholder responses, regeneration, and stopGeneration function

    fix(conversation.ts, buildTree.ts): Import TMessage type, handle null parentMessageId

    feat(schemas.ts): Update and add schemas for various AI services, add default values, optional fields, and endpoint-to-schema mapping, create parseConvo function

    chore(useMessageHandler.js, schemas.ts): Remove unused imports, variables, and chatGPT enum

    * wip: add generation buttons

    * refactor(cleanupPreset.ts): simplify cleanupPreset function
    refactor(getDefaultConversation.js): remove unused code and simplify getDefaultConversation function

    feat(utils): add getDefaultConversation function

    This commit adds a new utility function called `getDefaultConversation` to the `client/src/utils/getDefaultConversation.ts` file. This function is responsible for generating a default conversation object based on the provided parameters.

    The `getDefaultConversation` function takes in an object with the following properties:
    - `conversation`: The conversation object to be used as a base.
    - `endpointsConfig`: The configuration object containing information about the available endpoints.
    - `preset`: An optional preset object that can be used to override the default behavior.

    The function first tries to determine the target endpoint based on the preset object. If a valid endpoint is found, it is used as the target endpoint. If not, the function tries to retrieve the last conversation setup from the local storage and uses its endpoint if it is valid. If neither the preset nor the local storage contains a valid endpoint, the function falls back to a default endpoint.

    Once the target endpoint is determined,

    * fix(utils): remove console.error statement in buildDefaultConversation function
    fix(schemas): add default values for catch blocks in openAISchema, googleSchema, bingAISchema, anthropicSchema, chatGPTBrowserSchema, and gptPluginsSchema

    * fix: endpoint not changing on change of preset from other endpoint, wip: refactor

    * refactor: preset items to TSX

    * refactor: convert resetConvo to TS

    * refactor(getDefaultConversation.ts): move defaultEndpoints array to the top of the file for better readability
    refactor(getDefaultConversation.ts): extract getDefaultEndpoint function for better code organization and reusability

    * feat(svg): add ContinueIcon component
    feat(svg): add RegenerateIcon component
    feat(svg): add ContinueIcon and RegenerateIcon components to index.ts

    * feat(Button.tsx): add onClick and className props to Button component
    feat(GenerationButtons.tsx): add logic to display Regenerate or StopGenerating button based on isSubmitting and messages
    feat(Regenerate.tsx): create Regenerate component with RegenerateIcon and handleRegenerate function
    feat(StopGenerating.tsx): create StopGenerating component with StopGeneratingIcon and handleStopGenerating function

    * fix(TextChat.jsx): reorder imports and variables for better readability
    fix(TextChat.jsx): fix typo in condition for isNotAppendable variable
    fix(TextChat.jsx): remove unused handleStopGenerating function
    fix(ContinueIcon.tsx): remove unnecessary closing tags for polygon elements
    fix(useMessageHandler.ts): add missing type annotations for handleStopGenerating and handleRegenerate functions
    fix(useMessageHandler.ts): remove unused variables in return statement

    * fix(getDefaultConversation.ts): refactor code to use getLocalStorageItems function
    feat(getLocalStorageItems.ts): add utility function to retrieve items from local storage

    * fix(OpenAIClient.js): add support for streaming result in sendCompletion method
    feat(OpenAIClient.js): add finish_reason metadata to opts in sendCompletion method
    feat(Message.js): add finish_reason field to Message model
    feat(messageSchema.js): add finish_reason field to messageSchema
    feat(openAI.js): parse chatGptLabel and promptPrefix from req.body and pass rest of the modelOptions to endpointOption
    feat(openAI.js): add addMetadata function to store metadata in ask function
    feat(openAI.js): add metadata to response if available
    feat(schemas.ts): add finish_reason field to tMessageSchema

    * feat(types.ts): add TOnClick and TGenButtonProps types for button components
    feat(Continue.tsx): create Continue component for generating button
    feat(GenerationButtons.tsx): update GenerationButtons component to use Continue component
    feat(Regenerate.tsx): create Regenerate component for regenerating button
    feat(Stop.tsx): create Stop component for stop generating button

    * feat(MessageHandler.jsx): add MessageHandler component to handle messages and conversations
    fix(Root.jsx): fix import paths for Nav and MessageHandler components

    * feat(useMessageHandler.ts): add support for generation parameter in ask function
    feat(useMessageHandler.ts): add support for isEdited parameter in ask function
    feat(useMessageHandler.ts): add support for continueGeneration function
    fix(createPayload.ts): replace endpoint URL when isEdited parameter is true

    * chore(client): set skipLibCheck to true in tsconfig.json

    * fix(useMessageHandler.ts): remove unused clientId variable
    fix(schemas.ts): make clientId field in tMessageSchema nullable and optional

    * wip: edit route for continue generation

    * refactor(api): move handlers to root of routes dir

    * fix(useMessageHandler.ts): initialize currentMessages to an empty array if messages is null
    fix(useMessageHandler.ts): update initialResponse text to use responseText variable
    fix(useMessageHandler.ts): update setMessages logic for isRegenerate case
    fix(MessageHandler.jsx): update setMessages logic for cancelHandler, createdHandler, and finalHandler

    * fix(schemas.ts): make createdAt and updatedAt fields optional and set default values using new Date().toISOString()
    fix(schemas.ts): change type annotation of TMessage from infer to input

    * refactor(useMessageHandler.ts): rename AskProps type to TAskProps
    refactor(useMessageHandler.ts): remove generation property from ask function arguments
    refactor(useMessageHandler.ts): use nullish coalescing operator (??) instead of logical OR (||)
    refactor(useMessageHandler.ts): pass the responseMessageId to message prop of submission

    * fix(BaseClient.js): use nullish coalescing operator (??) instead of logical OR (||) for default values

    * fix(BaseClient.js): fix responseMessageId assignment in handleStartMethods method
    feat(BaseClient.js): add support for isEdited flag in sendMessage method
    feat(BaseClient.js): add generation to responseMessage text in sendMessage method

    * fix(openAI.js): remove unused imports and commented out code
    feat(openAI.js): add support for generation parameter in request body
    fix(openAI.js): remove console.log statement
    fix(openAI.js): remove unused variables and parameters
    fix(openAI.js): update response text in case of error
    fix(openAI.js): handle error and abort message in case of error
    fix(handlers.js): add generation parameter to createOnProgress function
    fix(useMessageHandler.ts): update responseText variable to use generation parameter

    * refactor(api/middleware): move inside server dir

    * refactor: add endpoint specific, modular functions to build options and initialize clients, create server/utils, move middleware, separate utils into api general utils and server specific utils

    * fix(abortMiddleware.js): import getConvo and getConvoTitle functions from models
    feat(abortMiddleware.js): add abortAsk function to abortController to handle aborting of requests
    fix(openAI.js): import buildOptions and initializeClient functions from endpoints/openAI
    refactor(openAI.js): use getAbortData function to get data for abortAsk function

    * refactor: move endpoint specific logic to an endpoints dir

    * refactor(PluginService.js): fix import path for encrypt and decrypt functions in PluginService.js

    * feat(openAI): add new endpoint for adding a title to a conversation

    - Added a new file `addTitle.js` in the `api/server/routes/endpoints/openAI` directory.
    - The `addTitle.js` file exports a function `addTitle` that takes in request parameters and performs the following actions:
      - If the `parentMessageId` is `'00000000-0000-0000-0000-000000000000'` and `newConvo` is true, it proceeds with the following steps:
        - Calls the `titleConvo` function from the `titleConvo` module, passing in the necessary parameters.
        - Calls the `saveConvo` function from the `saveConvo` module, passing in the user ID and conversation details.
    - Updated the `index.js` file in the `api/server/routes/endpoints/openAI` directory to export the `addTitle` function.
    - This change adds

    * fix(abortMiddleware.js): remove console.log statement
    refactor(gptPlugins.js): update imports and function parameters
    feat(gptPlugins.js): add support for abortController and getAbortData
    refactor(openAI.js): update imports and function parameters
    feat(openAI.js): add support for abortController and getAbortData

    fix(openAI.js): refactor code to use modularized functions and middleware
    fix(buildOptions.js): refactor code to use destructuring and update variable names

    * refactor(askChatGPTBrowser.js, bingAI.js, google.js): remove duplicate code for setting response headers
    feat(askChatGPTBrowser.js, bingAI.js, google.js): add setHeaders middleware to set response headers

    * feat(middleware): validateEndpoint, refactor buildOption to only be concerned of endpointOption

    * fix(abortMiddleware.js): add 'finish_reason' property with value 'incomplete' to responseMessage object
    fix(abortMessage.js): remove console.log statement for aborted message
    fix(handlers.js): modify tokens assignment to handle empty generation string and trailing space

    * fix(BaseClient.js): import addSpaceIfNeeded function from server/utils
    fix(BaseClient.js): add space before generation in text property
    fix(index.js): remove getCitations and citeText exports
    feat(buildEndpointOption.js): add buildEndpointOption middleware
    fix(index.js): import buildEndpointOption middleware
    fix(anthropic.js): remove buildOptions function and use endpointOption from req.body
    fix(gptPlugins.js): remove buildOptions function and use endpointOption from req.body
    fix(openAI.js): remove buildOptions function and use endpointOption from req.body

    feat(utils): add citations.js and handleText.js modules
    fix(utils): fix import statements in index.js module

    * refactor(gptPlugins.js): use getResponseSender function from librechat-data-provider

    * feat(gptPlugins): complete 'continue generating'

    * wip: anthropic continue regen

    * feat(middleware): add validateRegistration middleware

    A new middleware function called `validateRegistration` has been added to the list of exported middleware functions in `index.js`. This middleware is responsible for validating registration data before allowing the registration process to proceed.

    * feat(Anthropic): complete continue regen

    * chore: add librechat-data-provider to api/package.json

    * fix(ci): backend-review will mock meilisearch, also installs data-provider as now needed

    * chore(ci): remove unneeded SEARCH env var

    * style(GenerationButtons): make text shorter for sake of space economy, even though this diverges from chat.openai.com

    * style(GenerationButtons/ScrollToBottom): adjust visibility/position based on screen size

    * chore(client): 'Editting' typo

    * feat(GenerationButtons.tsx): add support for endpoint prop in GenerationButtons component
    feat(OptionsBar.tsx): pass endpoint prop to GenerationButtons component
    feat(useGenerations.ts): create useGenerations hook to handle generation logic
    fix(schemas.ts): add searchResult field to tMessageSchema

    * refactor(HoverButtons): convert to TSX and utilize new useGenerations hook

    * fix(abortMiddleware): handle error with res headers set, or abortController not found, to ensure proper API error is sent to the client, chore(BaseClient): remove console log for onStart message meant for debugging

    * refactor(api): remove librechat-data-provider dep for now as it complicates deployed docker build stage, re-use code in CJS, located in server/endpoints/schemas

    * chore: remove console.logs from test files

    * ci: add backend tests for AnthropicClient, focusing on new buildMessages logic

    * refactor(FakeClient): use actual BaseClient sendMessage method for testing

    * test(BaseClient.test.js): add test for loading chat history
    test(BaseClient.test.js): add test for sendMessage logic with isEdited flag

    * fix(buildEndpointOption.js): add support for azureOpenAI in buildFunction object
    wip(endpoints.js): fetch Azure models from Azure OpenAI API if opts.azure is true

    * fix(Button.tsx): add data-testid attribute to button component
    fix(SelectDropDown.tsx): add data-testid attribute to Listbox.Button component
    fix(messages.spec.ts): add waitForServerStream function to consolidate logic for awaiting the server response
    feat(messages.spec.ts): add test for stopping and continuing message and improve browser/page context order and closing

    * refactor(onProgress): speed up time to save initial message for editable routes

    * chore: disable AI message editing (for now), was accidentally allowed

    * refactor: ensure continue is only supported for latest message style: improve styling in dark mode and across all hover buttons/icons, including making edit icon for AI invisible (for now)

    * fix: add test id to generation buttons so they never resolve to 2+ items

    * chore(package.json): add 'packages/' to the list of ignored directories
    chore(data-provider/package.json): bump version to 0.1.5

commit ae5b7d3d53
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Tue Aug 15 18:27:54 2023 -0400

    fix(PluginsClient.js): fix ChatOpenAI Azure Config Issue (#812)

    * fix(PluginsClient.js): fix issue with creating LLM when using Azure

    * chore(PluginsClient.js): omit azure logging

    * refactor(PluginsClient.js): simplify assignment of azure variable

    The code was simplified by directly assigning the value of `this.azure` to the `azure` variable using object destructuring. This makes the code cleaner and more concise.

commit b85f3bf91e
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Tue Aug 15 18:42:24 2023 +0200

    update from lang to localize (#810)

commit 80aab73bf6
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Mon Aug 14 19:19:04 2023 -0400

    chore: rebuilt package-lock file

commit bbe4931a97
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Mon Aug 14 19:13:24 2023 -0400

    refactor(ScreenshotContext): use html-to-image for lighter bundle, faster processing

commit 74802dd720
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Mon Aug 14 17:51:03 2023 +0200

    chore: Translation Fixes, Lint Error Corrections, and Additional Translations (#788)

    * fix translation and small lint error

    * changed from localize to useLocalize hook

    * changed to useLocalize

commit b64cc71d88
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 14 10:23:00 2023 -0400

    chore(docker-compose.yml): comment out meilisearch ports in docker-compose.yml (#807)

commit 89f260bc78
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 14 10:12:00 2023 -0400

    fix(CodeBlock.tsx): fix copy-to-clipboard functionality. The code has been updated to use the copy function from the copy-to-clipboard library instead of the (#806)

    avigator.clipboard.writeText method. This should fix the issue with browser incompatibility with navigator SDK and allow users to copy code from the CodeBlock component successfully.

commit d00c7354cd
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 14 09:45:44 2023 -0400

    fix: Corrected Registration Validation, Case-Insensitive Variable Handling, Playwright workflow (#805)

    * feat(auth.js): add validation for registration endpoint using validateRegistration middleware
    feat(validateRegistration.js): add middleware to validate registration based on ALLOW_REGISTRATION environment variable

    * fix(config.js): fix registrationEnabled and socialLoginEnabled variables to handle case-insensitive environment variable values

    * refactor(validateRegistration.js): remove console.log statement

    * chore(playwright.yml): skip browser download during yarn install
    chore(playwright.yml): place Playwright binaries to node_modules/@playwright/test
    chore(playwright.yml): install Playwright dependencies using npx playwright install-deps
    chore(playwright.yml): install Playwright chromium browser using npx playwright install chromium
    chore(playwright.yml): install @playwright/test@latest using npm install -D @playwright/test@latest
    chore(playwright.yml): run Playwright tests using npm run e2e:ci

    * chore(playwright.yml): change npm install order and update comment

    The order of the npm install commands in the "Install Playwright Browsers" step has been changed to first install @playwright/test@latest and then install chromium. Additionally, the comment explaining the PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD variable has been updated to mention npm install instead of yarn install.

    * chore(playwright.yml): remove commented out code for caching and add separate steps for installing Playwright dependencies and browsers

commit 1aa4b34dc6
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Fri Aug 11 19:02:52 2023 +0200

    added the dot (.) username rules (#787)

* Create VolumeMuteIcon.tsx

* Create VolumeIcon.tsx

* Update index.ts

* Update SubmitButton.jsx

* Update SubmitButton.jsx

* Update TextChat.jsx

* Update TextChat.jsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update TextChat.jsx

* Update SpeechRecognition.tsx

* Update TextChat.jsx

* Update HoverButtons.tsx

* Update useServerStream.ts

* Update useServerStream.ts

* Update HoverButtons.tsx

* Update useServerStream.ts

* Update useServerStream.ts

* Update HoverButtons.tsx

* Update VolumeIcon.tsx

* Update VolumeMuteIcon.tsx

* Update HoverButtons.tsx

* Update SpeechSynthesis.tsx

* Update HoverButtons.tsx

* Update HoverButtons.tsx

* Update SpeechSynthesis.tsx

* Update SpeechSynthesis.tsx

* Update HoverButtons.tsx

* Update SpeechSynthesis.tsx

* Update package.json

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Update SpeechRecognition.tsx

* Squashed commit of the following:

commit 1019529634
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 23:12:14 2023 -0500

    Update SpeechRecognition.tsx

commit 67f111ccd0
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 23:08:48 2023 -0500

    Update SpeechRecognition.tsx

commit 0b35dbe196
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 23:04:50 2023 -0500

    Update SpeechRecognition.tsx

commit 6686126dc0
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 22:49:08 2023 -0500

    Update SpeechRecognition.tsx

commit 5b80ddfba7
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 22:45:02 2023 -0500

    Update package.json

commit 39e84efa81
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 22:35:48 2023 -0500

    Update SpeechSynthesis.tsx

commit 4c6d067cb9
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 22:24:29 2023 -0500

    Update HoverButtons.tsx

commit c5ce576fb8
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 22:13:20 2023 -0500

    Update SpeechSynthesis.tsx

commit d95fa19539
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 22:11:38 2023 -0500

    Update SpeechSynthesis.tsx

commit c794f07678
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 22:03:34 2023 -0500

    Update HoverButtons.tsx

commit 7ae0e7e97c
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 21:59:45 2023 -0500

    Update HoverButtons.tsx

commit e9882dedad
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 21:58:07 2023 -0500

    Update SpeechSynthesis.tsx

commit 95cf300782
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 21:44:49 2023 -0500

    Update HoverButtons.tsx

commit 37c828d7fb
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 21:30:34 2023 -0500

    Update VolumeMuteIcon.tsx

commit 6133531737
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 21:29:54 2023 -0500

    Update VolumeIcon.tsx

commit 4b4afcdd37
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 21:20:14 2023 -0500

    Update HoverButtons.tsx

commit 609d1dfefb
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 20:49:52 2023 -0500

    Update useServerStream.ts

commit 875ce4b77e
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 20:48:26 2023 -0500

    Update useServerStream.ts

commit 8ed04e496b
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 20:37:59 2023 -0500

    Update HoverButtons.tsx

commit 4b30c132df
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 20:14:01 2023 -0500

    Update useServerStream.ts

commit c041c329cf
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 20:07:14 2023 -0500

    Update useServerStream.ts

commit 3e36c16817
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 19:36:21 2023 -0500

    Update HoverButtons.tsx

commit c7eea96759
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 19:28:03 2023 -0500

    Update TextChat.jsx

commit 5542f8e85d
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 19:21:50 2023 -0500

    Update SpeechRecognition.tsx

commit 9a27e56f8b
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 19:16:01 2023 -0500

    Update TextChat.jsx

commit 7f101bd122
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 19:09:51 2023 -0500

    Update SpeechRecognition.tsx

commit d405454bf5
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 19:03:34 2023 -0500

    Update SpeechRecognition.tsx

commit 6033eb3ed1
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 19:01:06 2023 -0500

    Update TextChat.jsx

commit 9a3e67fcd2
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 18:53:19 2023 -0500

    Update TextChat.jsx

commit 6583877cb3
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 17:53:18 2023 -0500

    Update SubmitButton.jsx

commit 8d5114bfae
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 17:39:20 2023 -0500

    Update SubmitButton.jsx

commit 29a5b55883
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 17:28:03 2023 -0500

    Update index.ts

commit b03001d01d
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 17:25:43 2023 -0500

    Create VolumeIcon.tsx

commit 863af2c959
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 17:21:43 2023 -0500

    Create VolumeMuteIcon.tsx

commit ad3c78f867
Merge: ed4b25b2 28230d93
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Sun Sep 3 16:49:56 2023 -0500

    Merge branch 'danny-avila:main' into Speech-September

commit ed4b25b2c1
Author: bsu3338 <bsu3338@yahoo.com>
Date:   Sun Sep 3 16:49:03 2023 -0500

    Squashed commit of the following:

    commit 28230d9305
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Sun Sep 3 02:44:26 2023 +0200

        feat: delete button confirm (#875)

        * base for confirm delete

        * more like OpenAI

    commit 2b54e3f9fe
    Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
    Date:   Fri Sep 1 14:20:51 2023 -0400

        update: install script (#858)

    commit 1cd0fd9d5a
    Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
    Date:   Fri Sep 1 08:12:35 2023 -0400

        doc: Hugging Face Deployment (#867)

        * docs: update ToC

        * docs: update ToC

        * update huggingface.md

        * update render.md

        * update huggingface.md

        * update mongodb.md

        * update huggingface.md

        * update README.md

    commit aeeb3d3050
    Author: Mu Yuan <yuanmu.email@gmail.com>
    Date:   Thu Aug 31 07:21:27 2023 +0800

        Update Zh.tsx (#862)

        * Update Zh.tsx

        Changed the translation of several words to make it more relevant to Chinese usage habits.

        * Update Zh.tsx

        Changed the translation of several words to make it more relevant to Chinese usage habits

    commit 80e2e2675b
    Author: Raí <140329135+itzraiss@users.noreply.github.com>
    Date:   Mon Aug 28 18:05:46 2023 -0300

        Translation of 'com_ui_pay_per_call:' to Spanish and Portuguese that were missing. (#857)

        * Update Br.tsx

        * Update Es.tsx

        * Update Br.tsx

        * Update Es.tsx

    commit 3574d0b823
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Mon Aug 28 14:49:26 2023 -0400

        docs: make_your_own.md formatting fix for mkdocs (#855)

    commit d672ac690d
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Mon Aug 28 14:24:10 2023 -0400

        Release v0.5.8 (#854)

        * chore: add 'api' image to tag release workflow

        * docs: update DO deployment docs to include instruction about latest stable release, as well as security best practices

        * Release v0.5.8

        * docs: Update digitalocean.md with firewall section images

        * docs: make_your_own.md formatting fix for mkdocs

    commit d3e7627046
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Mon Aug 28 12:03:08 2023 -0400

        refactor(plugins): Improve OpenAPI handling, Show Multiple Plugins, & Other Improvements (#845)

        * feat(PluginsClient.js): add conversationId to options object in the constructor
        feat(PluginsClient.js): add support for Code Interpreter plugin
        feat(PluginsClient.js): add support for Code Interpreter plugin in the availableTools manifest
        feat(CodeInterpreter.js): add CodeInterpreterTools module
        feat(CodeInterpreter.js): add RunCommand class
        feat(CodeInterpreter.js): add ReadFile class
        feat(CodeInterpreter.js): add WriteFile class
        feat(handleTools.js): add support for loading Code Interpreter plugin

        * chore(api): update langchain dependency to version 0.0.123

        * fix(CodeInterpreter.js): add support for extracting environment from code
        fix(WriteFile.js): add support for extracting environment from data
        fix(extractionChain.js): add utility functions for creating extraction chain from Zod schema
        fix(handleTools.js): refactor getOpenAIKey function to handle user-provided API key
        fix(handleTools.js): pass model and openAIApiKey to CodeInterpreter constructor

        * fix(tools): rename CodeInterpreterTools to E2BTools
        fix(tools): rename code_interpreter pluginKey to e2b_code_interpreter

        * chore(PluginsClient.js): comment out unused import and function findMessageContent
        feat(PluginsClient.js): add support for CodeSherpa plugin
        feat(PluginsClient.js): add CodeSherpaTools to available tools
        feat(PluginsClient.js): update manifest.json to include CodeSherpa plugin
        feat(CodeSherpaTools.js): create RunCode and RunCommand classes for CodeSherpa plugin

        feat(E2BTools.js): Add E2BTools module for extracting environment from code and running commands, reading and writing files
        fix(codesherpa.js): Remove codesherpa module as it is no longer needed

        feat(handleTools.js): add support for CodeSherpaTools in loadTools function
        feat(loadToolSuite.js): create loadToolSuite utility function to load a suite of tools

        * feat(PluginsClient.js): add support for CodeSherpa v2 plugin
        feat(PluginsClient.js): add CodeSherpa v1 plugin to available tools
        feat(PluginsClient.js): add CodeSherpa v2 plugin to available tools
        feat(PluginsClient.js): update manifest.json for CodeSherpa v1 plugin
        feat(PluginsClient.js): update manifest.json for CodeSherpa v2 plugin
        feat(CodeSherpa.js): implement CodeSherpa plugin for interactive code and shell command execution
        feat(CodeSherpaTools.js): implement RunCode and RunCommand plugins for CodeSherpa v1
        feat(CodeSherpaTools.js): update RunCode and RunCommand plugins for CodeSherpa v2

        fix(handleTools.js): add CodeSherpa import statement
        fix(handleTools.js): change pluginKey from 'codesherpa' to 'codesherpa_tools'
        fix(handleTools.js): remove model and openAIApiKey from options object in e2b_code_interpreter tool
        fix(handleTools.js): remove openAIApiKey from options object in codesherpa_tools tool
        fix(loadToolSuite.js): remove model and openAIApiKey parameters from loadToolSuite function

        * feat(initializeFunctionsAgent.js): add prefix to agentArgs in initializeFunctionsAgent function

        The prefix is added to the agentArgs in the initializeFunctionsAgent function. This prefix is used to provide instructions to the agent when it receives any instructions from a webpage, plugin, or other tool. The agent will notify the user immediately and ask them if they wish to carry out or ignore the instructions.

        * feat(PluginsClient.js): add ChatTool to the list of tools if it meets the conditions
        feat(tools/index.js): import and export ChatTool
        feat(ChatTool.js): create ChatTool class with necessary properties and methods

        * fix(initializeFunctionsAgent.js): update PREFIX message to include sharing all output from the tool
        fix(E2BTools.js): update descriptions for RunCommand, ReadFile, and WriteFile plugins to provide more clarity and context

        * chore: rebuild package-lock after rebase

        * chore: remove deleted file from rebase

        * wip: refactor plugin message handling to mirror chat.openai.com, handle incoming stream for plugin use

        * wip: new plugin handling

        * wip: show multiple plugins handling

        * feat(plugins): save new plugins array

        * chore: bump langchain

        * feat(experimental): support streaming in between plugins

        * refactor(PluginsClient): factor out helper methods to avoid bloating the class, refactor(gptPlugins): use agent action for mapping the name of action

        * fix(handleTools): fix tests by adding condition to return original toolFunctions map

        * refactor(MessageContent): Allow the last index to be last in case it has text (may change with streaming)

        * feat(Plugins): add handleParsingErrors, useful when LLM does not invoke function params

        * chore: edit out experimental codesherpa integration

        * refactor(OpenAPIPlugin): rework tool to be 'function-first', as the spec functions are explicitly passed to agent model

        * refactor(initializeFunctionsAgent): improve error handling and system message

        * refactor(CodeSherpa, Wolfram): optimize token usage by delegating bulk of instructions to system message

        * style(Plugins): match official style with input/outputs

        * chore: remove unnecessary console logs used for testing

        * fix(abortMiddleware): render markdown when message is aborted

        * feat(plugins): add BrowserOp

        * refactor(OpenAPIPlugin): improve prompt handling

        * fix(useGenerations): hide edit button when message is submitting/streaming

        * refactor(loadSpecs): optimize OpenAPI spec loading by only loading requested specs instead of all of them

        * fix(loadSpecs): will retain original behavior when no tools are passed to the function

        * fix(MessageContent): ensure cursor only shows up for last message and last display index
        fix(Message): show legacy plugin and pass isLast to Content

        * chore: remove console.logs

        * docs: update docs based on breaking changes and new features
        refactor(structured/SD): use description_for_model for detailed prompting

        * docs(azure): make plugins section more clear

        * refactor(structured/SD): change default payload to SD-WebUI to prefer realism and config for SDXL

        * refactor(structured/SD): further improve system message prompt

        * docs: update breaking changes after rebase

        * refactor(MessageContent): factor out EditMessage, types, Container to separate files, rename Content -> Markdown

        * fix(CodeInterpreter): linting errors

        * chore: reduce browser console logs from message streams

        * chore: re-enable debug logs for plugins/langchain to help with user troubleshooting

        * chore(manifest.json): add [Experimental] tag to CodeInterpreter plugins, which are not intended as the end-all be-all implementation of this feature for Librechat

    commit 66b8580487
    Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
    Date:   Mon Aug 28 09:18:25 2023 -0400

        docs: third-party tools (#848)

        * docs: third-party tools

        * docs: third-party tools

        * Update third-party.md

        * Update third-party.md

        ---------

        Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>

    commit 9791a78161
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Mon Aug 28 15:14:05 2023 +0200

        adjust the animation (#843)

    commit 3797ec6082
    Author: Ronith <87087292+ronith256@users.noreply.github.com>
    Date:   Mon Aug 28 18:43:50 2023 +0530

        feat: Add Code Interpreter Plugin (#837)

        * feat: Add Code Interpreter Plugin

        Adds a Simple Code Interpreter Plugin.
        ## Features:
        - Runs code using local Python Environment

        ## Issues
        - Code execution is not sandboxed.

        * Add Docker Sandbox for Python Server

    commit e2397076a2
    Author: Alex Zhang <ztc2011@gmail.com>
    Date:   Mon Aug 28 00:55:34 2023 +0800

        🌐: Chinese Translation (#846)

    commit 50c15c704f
    Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
    Date:   Sat Aug 26 19:36:59 2023 -0400

        Language translation: Polish (#840)

        * Language translation: Polish

        * Language translation: Polish

        * Revert changes in language-contributions.md

    commit 29d3640546
    Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
    Date:   Sat Aug 26 19:36:25 2023 -0400

        docs: updates (#841)

    commit 39c626aa8e
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Fri Aug 25 09:29:19 2023 -0400

        fix: isEdited edge case where latest Message is not saved due to aborting too quickly

    commit ae5c06f381
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Fri Aug 25 09:13:50 2023 -0400

        fix(chatGPTBrowser): render markdown formatting by setting isCreatedByUser, fix(useMessageHandler): avoid double appearance of cursor by setting latest message at initial response creation time

    commit 9ef1686e18
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Thu Aug 24 20:24:47 2023 -0400

        Update mkdocs.yml

    commit 5bbe411569
    Author: Flynn <dev@flynnbuckingham.com>
    Date:   Thu Aug 24 20:20:37 2023 -0400

        Add podman installation instructions. Update dockerfile to stub env (#819)

        * Added podman container installation docs. Updated dockerfile to stub env file if not present in source

        * Fix typos

    commit 887fec99ca
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Fri Aug 25 02:11:27 2023 +0200

        🌐: Russian Translation (#830)

    commit 007d51ede1
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Fri Aug 25 02:10:48 2023 +0200

        feat: facebook login (#820)

        * Facebook strategy

        * Update user_auth_system.md

        * Update user_auth_system.md

    commit a569020312
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Thu Aug 24 21:59:11 2023 +0200

        Fix Meilisearch error and refactor of the server index.js (#832)

        * fix meilisearch error at startup

        * limit the nesting

        * disable useless console log

        * fix(indexSync.js): removed redundant searchEnabled

        * refactor(index.js): moved configureSocialLogins to a new file

        * refactor(socialLogins.js): removed unnecessary conditional

    commit 37347d4683
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Wed Aug 23 16:14:17 2023 -0400

        fix(registration): Make Username optional (#831)

        * fix(User.js): update validation schema for username field, allow empty string as a valid value
        fix(validators.js): update validation schema for username field, allow empty string as a valid value
        fix(Registration.tsx, validators.js): update validation rules for name and username fields, change minimum length to 2 and maximum length to 80, assure they match and allow empty string as a valid value
        fix(Eng.tsx): update localization string for com_auth_username, indicate that it is optional

        * fix(User.js): update regex pattern for username validation to allow special characters @#$%&*()
        fix(validators.js): update regex pattern for username validation to allow special characters @#$%&*()

        * fix(Registration.spec.tsx): fix validation error message for username length requirement

    commit d38e463d34
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Wed Aug 23 13:44:40 2023 -0400

        fix(bingAI): markdown and error formatting for final stream response (#829)

        * fix(bingAI): markdown formatting for final stream response due to new strict payload validation on the frontend

        * fix: add missing prop to bing Error response

    commit 7dc27b10f1
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Tue Aug 22 18:44:59 2023 -0400

        feat: Edit AI Messages, Edit Messages in Place (#825)

        * refactor: replace lodash import with specific function import

        fix(api): esm imports to cjs

        * refactor(Messages.tsx): convert to TS, out-source scrollToDiv logic to a custom hook
        fix(ScreenshotContext.tsx): change Ref to RefObject in ScreenshotContextType
        feat(useScrollToRef.ts): add useScrollToRef hook for scrolling to a ref with throttle
        fix(Chat.tsx): update import path for Messages component
        fix(Search.tsx): update import path for Messages component

        * chore(types.ts): add TAskProps and TOptions types
        refactor(useMessageHandler.ts): use TAskFunction type for ask function signature

        * refactor(Message/Content): convert to TS, move Plugin component to Content dir

        * feat(MessageContent.tsx): add MessageContent component for displaying and editing message content
        feat(index.ts): export MessageContent component from Messages/Content directory

        * wip(Message.jsx): conversion and use of new component in progress

        * refactor: convert Message.jsx to TS and fix typing/imports based on changes

        * refactor: add typed props and refactor MultiMessage to TS, fix typing issues resulting from the conversion

        * edit message in progress

        * feat: complete edit AI message logic, refactor continue logic

        * feat(middleware): add validateMessageReq middleware
        feat(routes): add validation for message requests using validateMessageReq middleware
        feat(routes): add create, read, update, and delete routes for messages

        * feat: complete frontend logic for editing messages in place
        feat(messages.js): update route for updating a specific message
        - Change the route for updating a message to include the messageId in the URL
        - Update the request handler to use the messageId from the request parameters and the text from the request body
        - Call the updateMessage function with the updated parameters

        feat(MessageContent.tsx): add functionality to update a message
        - Import the useUpdateMessageMutation hook from the data provider
        - Destructure the conversationId, parentMessageId, and messageId from the message object
        - Create a mutation function using the useUpdateMessageMutation hook
        - Implement the updateMessage function to call the mutation function with the updated message parameters
        - Update the messages state to reflect the updated message text

        feat(api-endpoints.ts): update messages endpoint to include messageId
        - Update the messages endpoint to include the messageId as an optional parameter

        feat(data-service.ts): add updateMessage function
        - Implement the updateMessage function to make a PUT request to

        * fix(messages.js): make updateMessage function asynchronous and await its execution

        * style(EditIcon): make icon active for AI message

        * feat(gptPlugins/anthropic): add edit support

        * fix(validateMessageReq.js): handle case when conversationId is 'new' and return empty array
        feat(Message.tsx): pass message prop to SiblingSwitch component
        refactor(SiblingSwitch.tsx): convert to TS

        * fix(useMessageHandler.ts): remove message from currentMessages if isContinued is true
        feat(useMessageHandler.ts): add support for submission messages in setMessages
        fix(useServerStream.ts): remove unnecessary conditional in setMessages
        fix(useServerStream.ts): remove isContinued variable from submission

        * fix(continue): switch to continued message generation when continuing an earlier branch in conversation

        * fix(abortMiddleware.js): fix condition to check partialText length
        chore(abortMiddleware.js): add error logging when abortMessage fails

        * refactor(MessageHeader.tsx): convert to TS
        fix(Plugin.tsx): add default value for className prop in Plugin component

        * refactor(MultiMessage.tsx): remove commented out code
        docs(MultiMessage.tsx): update comment to clarify when siblingIdx is reset

        * fix(GenerationButtons): optimistic state for continue button

        * fix(MessageContent.tsx): add data-testid attribute to message text editor
        fix(messages.spec.ts): update waitForServerStream function to include edit endpoint check
        feat(messages.spec.ts): add test case for editing messages

        * fix(HoverButtons & Message & useGenerations): Refactor edit functionality and related conditions

        - Update enterEdit function signature and prop
        - Create and utilize hideEditButton variable
        - Enhance conditions for edit button visibility and active state
        - Update button event handlers
        - Introduce isEditableEndpoint in useGenerations and refine continueSupported condition.

        * fix(useGenerations.ts): fix condition for hideEditButton to include error and searchResult
        chore(data-provider): bump version to 0.1.6
        fix(types.ts): add status property to TError type

        * chore: bump @dqbd/tiktoken to 1.0.7

        * fix(abortMiddleware.js): add required isCreatedByUser property to the error response object

        * refactor(Message.tsx): remove unnecessary props from SiblingSwitch component, as setLatestMessage is firing on every switch already
        refactor(SiblingSwitch.tsx): remove unused imports and code

        * chore(BaseClient.js): move console.debug statements back inside if block

    commit db77163f5d
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Tue Aug 22 14:15:14 2023 +0200

        docs: update chimeragpt (#826)

        * Update free_ai_apis.md

        * Update free_ai_apis.md

    commit 4a4e803df3
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Mon Aug 21 20:15:18 2023 +0200

        style(Dialog): Improved Close Button ("X") position (#824)

    commit 909b00c752
    Author: Daniel Avila <messagedaniel@protonmail.com>
    Date:   Sun Aug 20 21:04:36 2023 -0400

        fix(HoverButtons): light/dark styling to match official site

    commit 61dcb4d307
    Author: Naosuke Yokoe <ankerasoy@gmail.com>
    Date:   Sat Aug 19 20:11:31 2023 +0900

        feat: Azure Cognitive Search Plugin (#815)

        * feat(AzureCognitiveSearchPlugin)

        * feat(tools/AzureCognitiveSearch.js): Add a new plugin (not structured
          version)
        * feat(tools/structured/AzureCognitiveSearch.js): Add a new plugin (structured version)
        * feat(tools/manifest.json, tools/index.js, tools/util/handleTools.js):
          Add configurations for the plugin
        * feat(api/package.json, package-lock.json): Installed a new package for the
          plugin (@azure/search-documents)
        * feat(.env.example): Add new environment variables for the plugin

        Here is the link to the corresponding discussion page:
        https://github.com/danny-avila/LibreChat/discussions/567

        * docs(AzureCognitiveSearchPlugin)

        * docs(features/plugins/azure_cognitive_search.md): Add a new document
          for the plugin

        * (fix:.env.example)

        * reverted extra whitespaces removed by the editor

        * docs(mkdocs.yml)

        * Add the Azure Cognitive Search Plugin's documentation item to
        mkdocs.yml.

    commit 3c7f67fa76
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Fri Aug 18 12:40:33 2023 -0400

        fix(abortMiddleware): handle early abort error where userMessage.conversationId is undefined. In this case, the userId will be used as the abortKey

    commit c74c68a135
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Fri Aug 18 12:10:30 2023 -0400

        refactor(MessageHandler -> useServerStream): convert all relating files to TS and correct typings based on this change: properly refactor MessageHandler to a custom hook, where it's passed a submission object to instantiate the stream. This is the bare minimum groundwork for potentially having multiple streams running, which would be a big project to modularize a lot of the global state into maps/multiple streams, particular useful for having multiple views in place

    commit 8b4d3c2c21
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Fri Aug 18 12:04:29 2023 -0400

        refactor(routes): convert to TS

    commit d612cfcb45
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Fri Aug 18 12:02:39 2023 -0400

        chore(Auth): reorder exports in Auth component
        fix(PluginAuthForm): handle case when pluginKey is null or undefined
        fix(PluginStoreDialog): handle case when getAvailablePluginFromKey is null or undefined
        fix(AuthContext): make authConfig optional in AuthContextProvider
        feat(hooks): add useServerStream hook
        fix(conversation): setSubmission to null instead of empty object
        fix(preset): specify type for presets atom
        fix(search): specify type for isSearchEnabled atom
        fix(submission): specify type for submission atom

    commit c40b95f424
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Fri Aug 18 16:11:00 2023 +0200

        feat: Disable Registration with social login (#813)

        * Google, Github and Discord

        * update .env.example with ALLOW_SOCIAL_REGISTRATION

        * fix some conflict

        * refactor strategy

        * Update user_auth_system.md

        * Update user_auth_system.md

    commit 46ed5aaccd
    Author: Patrick <psarnowski@gmail.com>
    Date:   Fri Aug 18 09:38:24 2023 -0400

        Show the response scores from Bing. (#814)

    commit 1dacfa49f0
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Thu Aug 17 20:32:31 2023 +0200

        update profile picture (#792)

    commit afd43afb60
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Thu Aug 17 12:50:05 2023 -0400

        feat(GPT/Anthropic): Continue Regenerating & Generation Buttons (#808)

        * feat(useMessageHandler.js/ts): Refactor and add features to handle user messages, support multiple endpoints/models, generate placeholder responses, regeneration, and stopGeneration function

        fix(conversation.ts, buildTree.ts): Import TMessage type, handle null parentMessageId

        feat(schemas.ts): Update and add schemas for various AI services, add default values, optional fields, and endpoint-to-schema mapping, create parseConvo function

        chore(useMessageHandler.js, schemas.ts): Remove unused imports, variables, and chatGPT enum

        * wip: add generation buttons

        * refactor(cleanupPreset.ts): simplify cleanupPreset function
        refactor(getDefaultConversation.js): remove unused code and simplify getDefaultConversation function

        feat(utils): add getDefaultConversation function

        This commit adds a new utility function called `getDefaultConversation` to the `client/src/utils/getDefaultConversation.ts` file. This function is responsible for generating a default conversation object based on the provided parameters.

        The `getDefaultConversation` function takes in an object with the following properties:
        - `conversation`: The conversation object to be used as a base.
        - `endpointsConfig`: The configuration object containing information about the available endpoints.
        - `preset`: An optional preset object that can be used to override the default behavior.

        The function first tries to determine the target endpoint based on the preset object. If a valid endpoint is found, it is used as the target endpoint. If not, the function tries to retrieve the last conversation setup from the local storage and uses its endpoint if it is valid. If neither the preset nor the local storage contains a valid endpoint, the function falls back to a default endpoint.

        Once the target endpoint is determined,

        * fix(utils): remove console.error statement in buildDefaultConversation function
        fix(schemas): add default values for catch blocks in openAISchema, googleSchema, bingAISchema, anthropicSchema, chatGPTBrowserSchema, and gptPluginsSchema

        * fix: endpoint not changing on change of preset from other endpoint, wip: refactor

        * refactor: preset items to TSX

        * refactor: convert resetConvo to TS

        * refactor(getDefaultConversation.ts): move defaultEndpoints array to the top of the file for better readability
        refactor(getDefaultConversation.ts): extract getDefaultEndpoint function for better code organization and reusability

        * feat(svg): add ContinueIcon component
        feat(svg): add RegenerateIcon component
        feat(svg): add ContinueIcon and RegenerateIcon components to index.ts

        * feat(Button.tsx): add onClick and className props to Button component
        feat(GenerationButtons.tsx): add logic to display Regenerate or StopGenerating button based on isSubmitting and messages
        feat(Regenerate.tsx): create Regenerate component with RegenerateIcon and handleRegenerate function
        feat(StopGenerating.tsx): create StopGenerating component with StopGeneratingIcon and handleStopGenerating function

        * fix(TextChat.jsx): reorder imports and variables for better readability
        fix(TextChat.jsx): fix typo in condition for isNotAppendable variable
        fix(TextChat.jsx): remove unused handleStopGenerating function
        fix(ContinueIcon.tsx): remove unnecessary closing tags for polygon elements
        fix(useMessageHandler.ts): add missing type annotations for handleStopGenerating and handleRegenerate functions
        fix(useMessageHandler.ts): remove unused variables in return statement

        * fix(getDefaultConversation.ts): refactor code to use getLocalStorageItems function
        feat(getLocalStorageItems.ts): add utility function to retrieve items from local storage

        * fix(OpenAIClient.js): add support for streaming result in sendCompletion method
        feat(OpenAIClient.js): add finish_reason metadata to opts in sendCompletion method
        feat(Message.js): add finish_reason field to Message model
        feat(messageSchema.js): add finish_reason field to messageSchema
        feat(openAI.js): parse chatGptLabel and promptPrefix from req.body and pass rest of the modelOptions to endpointOption
        feat(openAI.js): add addMetadata function to store metadata in ask function
        feat(openAI.js): add metadata to response if available
        feat(schemas.ts): add finish_reason field to tMessageSchema

        * feat(types.ts): add TOnClick and TGenButtonProps types for button components
        feat(Continue.tsx): create Continue component for generating button
        feat(GenerationButtons.tsx): update GenerationButtons component to use Continue component
        feat(Regenerate.tsx): create Regenerate component for regenerating button
        feat(Stop.tsx): create Stop component for stop generating button

        * feat(MessageHandler.jsx): add MessageHandler component to handle messages and conversations
        fix(Root.jsx): fix import paths for Nav and MessageHandler components

        * feat(useMessageHandler.ts): add support for generation parameter in ask function
        feat(useMessageHandler.ts): add support for isEdited parameter in ask function
        feat(useMessageHandler.ts): add support for continueGeneration function
        fix(createPayload.ts): replace endpoint URL when isEdited parameter is true

        * chore(client): set skipLibCheck to true in tsconfig.json

        * fix(useMessageHandler.ts): remove unused clientId variable
        fix(schemas.ts): make clientId field in tMessageSchema nullable and optional

        * wip: edit route for continue generation

        * refactor(api): move handlers to root of routes dir

        * fix(useMessageHandler.ts): initialize currentMessages to an empty array if messages is null
        fix(useMessageHandler.ts): update initialResponse text to use responseText variable
        fix(useMessageHandler.ts): update setMessages logic for isRegenerate case
        fix(MessageHandler.jsx): update setMessages logic for cancelHandler, createdHandler, and finalHandler

        * fix(schemas.ts): make createdAt and updatedAt fields optional and set default values using new Date().toISOString()
        fix(schemas.ts): change type annotation of TMessage from infer to input

        * refactor(useMessageHandler.ts): rename AskProps type to TAskProps
        refactor(useMessageHandler.ts): remove generation property from ask function arguments
        refactor(useMessageHandler.ts): use nullish coalescing operator (??) instead of logical OR (||)
        refactor(useMessageHandler.ts): pass the responseMessageId to message prop of submission

        * fix(BaseClient.js): use nullish coalescing operator (??) instead of logical OR (||) for default values

        * fix(BaseClient.js): fix responseMessageId assignment in handleStartMethods method
        feat(BaseClient.js): add support for isEdited flag in sendMessage method
        feat(BaseClient.js): add generation to responseMessage text in sendMessage method

        * fix(openAI.js): remove unused imports and commented out code
        feat(openAI.js): add support for generation parameter in request body
        fix(openAI.js): remove console.log statement
        fix(openAI.js): remove unused variables and parameters
        fix(openAI.js): update response text in case of error
        fix(openAI.js): handle error and abort message in case of error
        fix(handlers.js): add generation parameter to createOnProgress function
        fix(useMessageHandler.ts): update responseText variable to use generation parameter

        * refactor(api/middleware): move inside server dir

        * refactor: add endpoint specific, modular functions to build options and initialize clients, create server/utils, move middleware, separate utils into api general utils and server specific utils

        * fix(abortMiddleware.js): import getConvo and getConvoTitle functions from models
        feat(abortMiddleware.js): add abortAsk function to abortController to handle aborting of requests
        fix(openAI.js): import buildOptions and initializeClient functions from endpoints/openAI
        refactor(openAI.js): use getAbortData function to get data for abortAsk function

        * refactor: move endpoint specific logic to an endpoints dir

        * refactor(PluginService.js): fix import path for encrypt and decrypt functions in PluginService.js

        * feat(openAI): add new endpoint for adding a title to a conversation

        - Added a new file `addTitle.js` in the `api/server/routes/endpoints/openAI` directory.
        - The `addTitle.js` file exports a function `addTitle` that takes in request parameters and performs the following actions:
          - If the `parentMessageId` is `'00000000-0000-0000-0000-000000000000'` and `newConvo` is true, it proceeds with the following steps:
            - Calls the `titleConvo` function from the `titleConvo` module, passing in the necessary parameters.
            - Calls the `saveConvo` function from the `saveConvo` module, passing in the user ID and conversation details.
        - Updated the `index.js` file in the `api/server/routes/endpoints/openAI` directory to export the `addTitle` function.
        - This change adds

        * fix(abortMiddleware.js): remove console.log statement
        refactor(gptPlugins.js): update imports and function parameters
        feat(gptPlugins.js): add support for abortController and getAbortData
        refactor(openAI.js): update imports and function parameters
        feat(openAI.js): add support for abortController and getAbortData

        fix(openAI.js): refactor code to use modularized functions and middleware
        fix(buildOptions.js): refactor code to use destructuring and update variable names

        * refactor(askChatGPTBrowser.js, bingAI.js, google.js): remove duplicate code for setting response headers
        feat(askChatGPTBrowser.js, bingAI.js, google.js): add setHeaders middleware to set response headers

        * feat(middleware): validateEndpoint, refactor buildOption to only be concerned of endpointOption

        * fix(abortMiddleware.js): add 'finish_reason' property with value 'incomplete' to responseMessage object
        fix(abortMessage.js): remove console.log statement for aborted message
        fix(handlers.js): modify tokens assignment to handle empty generation string and trailing space

        * fix(BaseClient.js): import addSpaceIfNeeded function from server/utils
        fix(BaseClient.js): add space before generation in text property
        fix(index.js): remove getCitations and citeText exports
        feat(buildEndpointOption.js): add buildEndpointOption middleware
        fix(index.js): import buildEndpointOption middleware
        fix(anthropic.js): remove buildOptions function and use endpointOption from req.body
        fix(gptPlugins.js): remove buildOptions function and use endpointOption from req.body
        fix(openAI.js): remove buildOptions function and use endpointOption from req.body

        feat(utils): add citations.js and handleText.js modules
        fix(utils): fix import statements in index.js module

        * refactor(gptPlugins.js): use getResponseSender function from librechat-data-provider

        * feat(gptPlugins): complete 'continue generating'

        * wip: anthropic continue regen

        * feat(middleware): add validateRegistration middleware

        A new middleware function called `validateRegistration` has been added to the list of exported middleware functions in `index.js`. This middleware is responsible for validating registration data before allowing the registration process to proceed.

        * feat(Anthropic): complete continue regen

        * chore: add librechat-data-provider to api/package.json

        * fix(ci): backend-review will mock meilisearch, also installs data-provider as now needed

        * chore(ci): remove unneeded SEARCH env var

        * style(GenerationButtons): make text shorter for sake of space economy, even though this diverges from chat.openai.com

        * style(GenerationButtons/ScrollToBottom): adjust visibility/position based on screen size

        * chore(client): 'Editting' typo

        * feat(GenerationButtons.tsx): add support for endpoint prop in GenerationButtons component
        feat(OptionsBar.tsx): pass endpoint prop to GenerationButtons component
        feat(useGenerations.ts): create useGenerations hook to handle generation logic
        fix(schemas.ts): add searchResult field to tMessageSchema

        * refactor(HoverButtons): convert to TSX and utilize new useGenerations hook

        * fix(abortMiddleware): handle error with res headers set, or abortController not found, to ensure proper API error is sent to the client, chore(BaseClient): remove console log for onStart message meant for debugging

        * refactor(api): remove librechat-data-provider dep for now as it complicates deployed docker build stage, re-use code in CJS, located in server/endpoints/schemas

        * chore: remove console.logs from test files

        * ci: add backend tests for AnthropicClient, focusing on new buildMessages logic

        * refactor(FakeClient): use actual BaseClient sendMessage method for testing

        * test(BaseClient.test.js): add test for loading chat history
        test(BaseClient.test.js): add test for sendMessage logic with isEdited flag

        * fix(buildEndpointOption.js): add support for azureOpenAI in buildFunction object
        wip(endpoints.js): fetch Azure models from Azure OpenAI API if opts.azure is true

        * fix(Button.tsx): add data-testid attribute to button component
        fix(SelectDropDown.tsx): add data-testid attribute to Listbox.Button component
        fix(messages.spec.ts): add waitForServerStream function to consolidate logic for awaiting the server response
        feat(messages.spec.ts): add test for stopping and continuing message and improve browser/page context order and closing

        * refactor(onProgress): speed up time to save initial message for editable routes

        * chore: disable AI message editing (for now), was accidentally allowed

        * refactor: ensure continue is only supported for latest message style: improve styling in dark mode and across all hover buttons/icons, including making edit icon for AI invisible (for now)

        * fix: add test id to generation buttons so they never resolve to 2+ items

        * chore(package.json): add 'packages/' to the list of ignored directories
        chore(data-provider/package.json): bump version to 0.1.5

    commit ae5b7d3d53
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Tue Aug 15 18:27:54 2023 -0400

        fix(PluginsClient.js): fix ChatOpenAI Azure Config Issue (#812)

        * fix(PluginsClient.js): fix issue with creating LLM when using Azure

        * chore(PluginsClient.js): omit azure logging

        * refactor(PluginsClient.js): simplify assignment of azure variable

        The code was simplified by directly assigning the value of `this.azure` to the `azure` variable using object destructuring. This makes the code cleaner and more concise.

    commit b85f3bf91e
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Tue Aug 15 18:42:24 2023 +0200

        update from lang to localize (#810)

    commit 80aab73bf6
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Mon Aug 14 19:19:04 2023 -0400

        chore: rebuilt package-lock file

    commit bbe4931a97
    Author: Danny Avila <messagedaniel@protonmail.com>
    Date:   Mon Aug 14 19:13:24 2023 -0400

        refactor(ScreenshotContext): use html-to-image for lighter bundle, faster processing

    commit 74802dd720
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Mon Aug 14 17:51:03 2023 +0200

        chore: Translation Fixes, Lint Error Corrections, and Additional Translations (#788)

        * fix translation and small lint error

        * changed from localize to useLocalize hook

        * changed to useLocalize

    commit b64cc71d88
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Mon Aug 14 10:23:00 2023 -0400

        chore(docker-compose.yml): comment out meilisearch ports in docker-compose.yml (#807)

    commit 89f260bc78
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Mon Aug 14 10:12:00 2023 -0400

        fix(CodeBlock.tsx): fix copy-to-clipboard functionality. The code has been updated to use the copy function from the copy-to-clipboard library instead of the (#806)

        avigator.clipboard.writeText method. This should fix the issue with browser incompatibility with navigator SDK and allow users to copy code from the CodeBlock component successfully.

    commit d00c7354cd
    Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
    Date:   Mon Aug 14 09:45:44 2023 -0400

        fix: Corrected Registration Validation, Case-Insensitive Variable Handling, Playwright workflow (#805)

        * feat(auth.js): add validation for registration endpoint using validateRegistration middleware
        feat(validateRegistration.js): add middleware to validate registration based on ALLOW_REGISTRATION environment variable

        * fix(config.js): fix registrationEnabled and socialLoginEnabled variables to handle case-insensitive environment variable values

        * refactor(validateRegistration.js): remove console.log statement

        * chore(playwright.yml): skip browser download during yarn install
        chore(playwright.yml): place Playwright binaries to node_modules/@playwright/test
        chore(playwright.yml): install Playwright dependencies using npx playwright install-deps
        chore(playwright.yml): install Playwright chromium browser using npx playwright install chromium
        chore(playwright.yml): install @playwright/test@latest using npm install -D @playwright/test@latest
        chore(playwright.yml): run Playwright tests using npm run e2e:ci

        * chore(playwright.yml): change npm install order and update comment

        The order of the npm install commands in the "Install Playwright Browsers" step has been changed to first install @playwright/test@latest and then install chromium. Additionally, the comment explaining the PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD variable has been updated to mention npm install instead of yarn install.

        * chore(playwright.yml): remove commented out code for caching and add separate steps for installing Playwright dependencies and browsers

    commit 1aa4b34dc6
    Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
    Date:   Fri Aug 11 19:02:52 2023 +0200

        added the dot (.) username rules (#787)

commit 28230d9305
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Sun Sep 3 02:44:26 2023 +0200

    feat: delete button confirm (#875)

    * base for confirm delete

    * more like OpenAI

commit 2b54e3f9fe
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Fri Sep 1 14:20:51 2023 -0400

    update: install script (#858)

commit 1cd0fd9d5a
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Fri Sep 1 08:12:35 2023 -0400

    doc: Hugging Face Deployment (#867)

    * docs: update ToC

    * docs: update ToC

    * update huggingface.md

    * update render.md

    * update huggingface.md

    * update mongodb.md

    * update huggingface.md

    * update README.md

commit aeeb3d3050
Author: Mu Yuan <yuanmu.email@gmail.com>
Date:   Thu Aug 31 07:21:27 2023 +0800

    Update Zh.tsx (#862)

    * Update Zh.tsx

    Changed the translation of several words to make it more relevant to Chinese usage habits.

    * Update Zh.tsx

    Changed the translation of several words to make it more relevant to Chinese usage habits

commit 80e2e2675b
Author: Raí <140329135+itzraiss@users.noreply.github.com>
Date:   Mon Aug 28 18:05:46 2023 -0300

    Translation of 'com_ui_pay_per_call:' to Spanish and Portuguese that were missing. (#857)

    * Update Br.tsx

    * Update Es.tsx

    * Update Br.tsx

    * Update Es.tsx

commit 3574d0b823
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 28 14:49:26 2023 -0400

    docs: make_your_own.md formatting fix for mkdocs (#855)

commit d672ac690d
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 28 14:24:10 2023 -0400

    Release v0.5.8 (#854)

    * chore: add 'api' image to tag release workflow

    * docs: update DO deployment docs to include instruction about latest stable release, as well as security best practices

    * Release v0.5.8

    * docs: Update digitalocean.md with firewall section images

    * docs: make_your_own.md formatting fix for mkdocs

commit d3e7627046
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Mon Aug 28 12:03:08 2023 -0400

    refactor(plugins): Improve OpenAPI handling, Show Multiple Plugins, & Other Improvements (#845)

    * feat(PluginsClient.js): add conversationId to options object in the constructor
    feat(PluginsClient.js): add support for Code Interpreter plugin
    feat(PluginsClient.js): add support for Code Interpreter plugin in the availableTools manifest
    feat(CodeInterpreter.js): add CodeInterpreterTools module
    feat(CodeInterpreter.js): add RunCommand class
    feat(CodeInterpreter.js): add ReadFile class
    feat(CodeInterpreter.js): add WriteFile class
    feat(handleTools.js): add support for loading Code Interpreter plugin

    * chore(api): update langchain dependency to version 0.0.123

    * fix(CodeInterpreter.js): add support for extracting environment from code
    fix(WriteFile.js): add support for extracting environment from data
    fix(extractionChain.js): add utility functions for creating extraction chain from Zod schema
    fix(handleTools.js): refactor getOpenAIKey function to handle user-provided API key
    fix(handleTools.js): pass model and openAIApiKey to CodeInterpreter constructor

    * fix(tools): rename CodeInterpreterTools to E2BTools
    fix(tools): rename code_interpreter pluginKey to e2b_code_interpreter

    * chore(PluginsClient.js): comment out unused import and function findMessageContent
    feat(PluginsClient.js): add support for CodeSherpa plugin
    feat(PluginsClient.js): add CodeSherpaTools to available tools
    feat(PluginsClient.js): update manifest.json to include CodeSherpa plugin
    feat(CodeSherpaTools.js): create RunCode and RunCommand classes for CodeSherpa plugin

    feat(E2BTools.js): Add E2BTools module for extracting environment from code and running commands, reading and writing files
    fix(codesherpa.js): Remove codesherpa module as it is no longer needed

    feat(handleTools.js): add support for CodeSherpaTools in loadTools function
    feat(loadToolSuite.js): create loadToolSuite utility function to load a suite of tools

    * feat(PluginsClient.js): add support for CodeSherpa v2 plugin
    feat(PluginsClient.js): add CodeSherpa v1 plugin to available tools
    feat(PluginsClient.js): add CodeSherpa v2 plugin to available tools
    feat(PluginsClient.js): update manifest.json for CodeSherpa v1 plugin
    feat(PluginsClient.js): update manifest.json for CodeSherpa v2 plugin
    feat(CodeSherpa.js): implement CodeSherpa plugin for interactive code and shell command execution
    feat(CodeSherpaTools.js): implement RunCode and RunCommand plugins for CodeSherpa v1
    feat(CodeSherpaTools.js): update RunCode and RunCommand plugins for CodeSherpa v2

    fix(handleTools.js): add CodeSherpa import statement
    fix(handleTools.js): change pluginKey from 'codesherpa' to 'codesherpa_tools'
    fix(handleTools.js): remove model and openAIApiKey from options object in e2b_code_interpreter tool
    fix(handleTools.js): remove openAIApiKey from options object in codesherpa_tools tool
    fix(loadToolSuite.js): remove model and openAIApiKey parameters from loadToolSuite function

    * feat(initializeFunctionsAgent.js): add prefix to agentArgs in initializeFunctionsAgent function

    The prefix is added to the agentArgs in the initializeFunctionsAgent function. This prefix is used to provide instructions to the agent when it receives any instructions from a webpage, plugin, or other tool. The agent will notify the user immediately and ask them if they wish to carry out or ignore the instructions.

    * feat(PluginsClient.js): add ChatTool to the list of tools if it meets the conditions
    feat(tools/index.js): import and export ChatTool
    feat(ChatTool.js): create ChatTool class with necessary properties and methods

    * fix(initializeFunctionsAgent.js): update PREFIX message to include sharing all output from the tool
    fix(E2BTools.js): update descriptions for RunCommand, ReadFile, and WriteFile plugins to provide more clarity and context

    * chore: rebuild package-lock after rebase

    * chore: remove deleted file from rebase

    * wip: refactor plugin message handling to mirror chat.openai.com, handle incoming stream for plugin use

    * wip: new plugin handling

    * wip: show multiple plugins handling

    * feat(plugins): save new plugins array

    * chore: bump langchain

    * feat(experimental): support streaming in between plugins

    * refactor(PluginsClient): factor out helper methods to avoid bloating the class, refactor(gptPlugins): use agent action for mapping the name of action

    * fix(handleTools): fix tests by adding condition to return original toolFunctions map

    * refactor(MessageContent): Allow the last index to be last in case it has text (may change with streaming)

    * feat(Plugins): add handleParsingErrors, useful when LLM does not invoke function params

    * chore: edit out experimental codesherpa integration

    * refactor(OpenAPIPlugin): rework tool to be 'function-first', as the spec functions are explicitly passed to agent model

    * refactor(initializeFunctionsAgent): improve error handling and system message

    * refactor(CodeSherpa, Wolfram): optimize token usage by delegating bulk of instructions to system message

    * style(Plugins): match official style with input/outputs

    * chore: remove unnecessary console logs used for testing

    * fix(abortMiddleware): render markdown when message is aborted

    * feat(plugins): add BrowserOp

    * refactor(OpenAPIPlugin): improve prompt handling

    * fix(useGenerations): hide edit button when message is submitting/streaming

    * refactor(loadSpecs): optimize OpenAPI spec loading by only loading requested specs instead of all of them

    * fix(loadSpecs): will retain original behavior when no tools are passed to the function

    * fix(MessageContent): ensure cursor only shows up for last message and last display index
    fix(Message): show legacy plugin and pass isLast to Content

    * chore: remove console.logs

    * docs: update docs based on breaking changes and new features
    refactor(structured/SD): use description_for_model for detailed prompting

    * docs(azure): make plugins section more clear

    * refactor(structured/SD): change default payload to SD-WebUI to prefer realism and config for SDXL

    * refactor(structured/SD): further improve system message prompt

    * docs: update breaking changes after rebase

    * refactor(MessageContent): factor out EditMessage, types, Container to separate files, rename Content -> Markdown

    * fix(CodeInterpreter): linting errors

    * chore: reduce browser console logs from message streams

    * chore: re-enable debug logs for plugins/langchain to help with user troubleshooting

    * chore(manifest.json): add [Experimental] tag to CodeInterpreter plugins, which are not intended as the end-all be-all implementation of this feature for Librechat

commit 66b8580487
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Mon Aug 28 09:18:25 2023 -0400

    docs: third-party tools (#848)

    * docs: third-party tools

    * docs: third-party tools

    * Update third-party.md

    * Update third-party.md

    ---------

    Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>

commit 9791a78161
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Mon Aug 28 15:14:05 2023 +0200

    adjust the animation (#843)

commit 3797ec6082
Author: Ronith <87087292+ronith256@users.noreply.github.com>
Date:   Mon Aug 28 18:43:50 2023 +0530

    feat: Add Code Interpreter Plugin (#837)

    * feat: Add Code Interpreter Plugin

    Adds a Simple Code Interpreter Plugin.
    ## Features:
    - Runs code using local Python Environment

    ## Issues
    - Code execution is not sandboxed.

    * Add Docker Sandbox for Python Server

commit e2397076a2
Author: Alex Zhang <ztc2011@gmail.com>
Date:   Mon Aug 28 00:55:34 2023 +0800

    🌐: Chinese Translation (#846)

commit 50c15c704f
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Sat Aug 26 19:36:59 2023 -0400

    Language translation: Polish (#840)

    * Language translation: Polish

    * Language translation: Polish

    * Revert changes in language-contributions.md

commit 29d3640546
Author: Fuegovic <32828263+fuegovic@users.noreply.github.com>
Date:   Sat Aug 26 19:36:25 2023 -0400

    docs: updates (#841)

commit 39c626aa8e
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 25 09:29:19 2023 -0400

    fix: isEdited edge case where latest Message is not saved due to aborting too quickly

commit ae5c06f381
Author: Danny Avila <messagedaniel@protonmail.com>
Date:   Fri Aug 25 09:13:50 2023 -0400

    fix(chatGPTBrowser): render markdown formatting by setting isCreatedByUser, fix(useMessageHandler): avoid double appearance of cursor by setting latest message at initial response creation time

commit 9ef1686e18
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Thu Aug 24 20:24:47 2023 -0400

    Update mkdocs.yml

commit 5bbe411569
Author: Flynn <dev@flynnbuckingham.com>
Date:   Thu Aug 24 20:20:37 2023 -0400

    Add podman installation instructions. Update dockerfile to stub env (#819)

    * Added podman container installation docs. Updated dockerfile to stub env file if not present in source

    * Fix typos

commit 887fec99ca
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Fri Aug 25 02:11:27 2023 +0200

    🌐: Russian Translation (#830)

commit 007d51ede1
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Fri Aug 25 02:10:48 2023 +0200

    feat: facebook login (#820)

    * Facebook strategy

    * Update user_auth_system.md

    * Update user_auth_system.md

commit a569020312
Author: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Date:   Thu Aug 24 21:59:11 2023 +0200

    Fix Meilisearch error and refactor of the server index.js (#832)

    * fix meilisearch error at startup

    * limit the nesting

    * disable useless console log

    * fix(indexSync.js): removed redundant searchEnabled

    * refactor(index.js): moved configureSocialLogins to a new file

    * refactor(socialLogins.js): removed unnecessary conditional

commit 37347d4683
Author: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Date:   Wed Aug 23 16:14:17 2023 -0400

    fix(registration): Make Username optional (#831)

    * fix(User.js): update validation schema for username field, allow empty string as a valid value
    fix(validators.js): update validation schema for username field, allow empty string as a valid value
    fix(Registration.tsx, validators.js): update validation rules for name and username fields, change minimum length to 2 and maximum length to 80, assure they match and allow empty string as a valid value
    fix(Eng.tsx): update localization string for com_auth_username, indicate that it is optional

    * fix(User.js): update regex …

* Update package-lock.json

* Update SubmitButton.tsx

* Update SpeechRecognition.tsx

* fix: typescript error

* style: moved to new UI

* fix:(SpeechRecognition) lint error

* moved everything to hooks

* feat: support stt external

* fix(useExternalSpeechRecognition): recording the audio

* feat: whisper api support

* refactor(SpeechReecognition); fix(HoverButtons): set isSpeakling correctly

* fix: spelling errors

* fix: renamed files

* BIG FIX

* feat: whisper support

* fixed some ChatForm bugs and added the tts route

* handling more errors

* Fix audio stream initialization and cleanup in useSpeechToTextExternal

* feat: Elevenlabs TTS

* fixed some req issues

* fix: stt not activating on Mac

* fix: send audio blob to frontend

* fix(ChatForm): startupConfig var

* Update text-to-speech and speech-to-text services

* handle more errors correctly

* Remove console.log statements

* feat: added manual trigger with button

* fix: SpeechToText and SpeechToTextExernal + AudioRecorder

* refactor: TTS component

* chore: removed unused variable

* feat: azure stt

* feat: dedicated speech panel

* feat: STT button switch: fix: TextArea pr value adapted

* refactor: textToSpeech function and useTextToSpeechMutation

* fix: typo data-service

* fix: blob backend to frontend

* feat: TTS button for external

* feat: librechat.yaml

* style: spinner when loading TTS

* feat: hold click to download file

* style: disabled when apiKey not provided

* fix: typo startupConfig?.speechToTextExternal

* style: update icons

* fix(useTextToSpeech): set isSpeaking when audio finish

* fix: small issues with local TTS

* style: update settings dark theme

* docs: STT & TTS

* WIP: chat audio automatic; docs(custom_config): update to new .yaml version; chore: updated librechat.yaml version

* fix: send button disabled

* fix: interval update

* localization

* removed unused test code

* revert interval update to 100

* feat: auto-send message

* fix: chat audio automatic, default false

* refactor: moved all logic to hooks

* chore: renamed ChatAudio to conversationMode

* refactor: organized Speech panel

* feat: autoSendText switch

* feat: moved chataudio to conversationMode and improved error handling; docs: update localai model

* refactor: Auto transcribe audio

* test: AutoSendTextSwitch, AutoTranscribeAudioSwitch and ConversationModeSwitch.spec: refactor: removed hark

* fix: various speechTab fixes

* refactor(useSpeechToTextBrowser):: handle more errors

* feat: engine select

* feat: advanced mode

* chore: converted hooks to TS

* feat: cache TTS

* feat: delete cache; fix: cache issues

* refactor(useTextToSpeechExternal): removed unused import

* feat: cache switch; refactor: moved to dir STT/TTS

* tests: CacheTTS, TextToSpeech, SpeechToText

* feat: custom elevenlabs compatibility

* fix(useTextToSpeechExternal): cache switch not working

* feat: animation for STT

* fix: settings var not working

* chore: remove unused var

* feat: voice dropdown; refactor: yaml changes

* fix(textToSpeech): remove undefined properties

* refactor: Remove console logs and unused variable

* fix: TTS; feat: support coqui and piper

* fix: some STT issues

* fix: stt test

* fix: STT backend sending wrong data

* BREAKING: switch to react-speech-recognition, add regenerator-runtime/runtime in main.jsx

* feat: websocket backend

* foundations for websocket

* first pass elevenlabs streaming

* streaming audio

* stream changes

* input streaming implementation

* fix: client build errors

* WIP: streaming rewrite

* audio stream working but not the loop

* WIP: looping audio stream working

* WIP tts routes rewrite

* feat: track SSE runs by runId, which enables us to better track audio streams per message request

* chore: set activeRunId on data.created

* rate limit tts and only allow once

* WIP: streaming audio

* refactor(useSSE): simplify messageId/parentMessageId assignment in message stream

* delete unused component

* streaming working

* first pass but need to investigate forever pending bug

* optimize audio stream handling client and initial request

* fix(StreamAudio): null exception

* refactor(tts): add limiters for db polling and timeout promise by intervals and not elapsed time

* refactor(textToSpeech): reduce polling delay

* feat(StreamAudio): add caching

* refactor: rename global variable, add setIsPlaying, remove mediasource ref

* feat: use custom hook for audioRef to help determine audio end state

* fix: voices mutation -> query

* fix: voices mutation -> query 2/2

* feat: successful TTS for manual playback

* fix: tts voice init

* feat: playback rate

* feat: global audio toggles

* chore: Add renderIcon function for chat message hover buttons, update schemas with notes

* chore: add debug logging instead of console.logs

* fix: edge case undefined user id

* feat: Automatic Playback switch

* feat: add caching bump data-provider

* chore: tts add auth

* use global state for audio run

* feat: assistants support for TTS read aloud

* ci: uncomment tests for now until they are refactored

* stream audio tests are WIP

* refactor: make automatic playback false as default

---------

Co-authored-by: bsu3338 <bsu3338@users.noreply.github.com>
Co-authored-by: bsu3338 <bsu3338@yahoo.com>
Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Co-authored-by: Berry-13 <marco13beretta@gmail.com>
Co-authored-by: Super User <root@Berry>
Co-authored-by: Marco Beretta <marco13beretta@proton.me>
2024-05-22 17:19:55 -04:00
Aman
9d8fd92dd3 🇬 refactor: Update default Google Models and Parameters (#2782)
* Update Google default model and parameters

* Update .env.example Vertex AI Models to reflect latest version and deprecate bison family

* Update Vertex AI model list in .env.example
2024-05-22 10:20:35 -04:00
Danny Avila
f00a8f87f7 🔧 fix(StableDiffusion): Temporarily Remove sampler_index (#2815) 2024-05-21 09:51:29 -04:00
Danny Avila
79840763e7 🔧 fix: Assistants App Error on Parameters Render (#2805) 2024-05-20 11:10:38 -04:00
Danny Avila
1a452121fa 🤖 feat: OpenAI Assistants v2 (initial support) (#2781)
* 🤖 Assistants V2 Support: Part 1

- Separated Azure Assistants to its own endpoint
- File Search / Vector Store integration is incomplete, but can toggle and use storage from playground
- Code Interpreter resource files can be added but not deleted
- GPT-4o is supported
- Many improvements to the Assistants Endpoint overall

data-provider v2 changes

copy existing route as v1

chore: rename new endpoint to reduce comparison operations and add new azure filesource

api: add azureAssistants part 1

force use of version for assistants/assistantsAzure

chore: switch name back to azureAssistants

refactor type version: string | number

Ensure assistants endpoints have version set

fix: isArchived type issue in ConversationListParams

refactor: update assistants mutations/queries with endpoint/version definitions, update Assistants Map structure

chore:  FilePreview component ExtendedFile type assertion

feat: isAssistantsEndpoint helper

chore: remove unused useGenerations

chore(buildTree): type issue

chore(Advanced): type issue (unused component, maybe in future)

first pass for multi-assistant endpoint rewrite

fix(listAssistants): pass params correctly

feat: list separate assistants by endpoint

fix(useTextarea): access assistantMap correctly

fix: assistant endpoint switching, resetting ID

fix: broken during rewrite, selecting assistant mention

fix: set/invalidate assistants endpoint query data correctly

feat: Fix issue with assistant ID not being reset correctly

getOpenAIClient helper function

feat: add toast for assistant deletion

fix: assistants delete right after create issue for azure

fix: assistant patching

refactor: actions to use getOpenAIClient

refactor: consolidate logic into helpers file

fix: issue where conversation data was not initially available

v1 chat support

refactor(spendTokens): only early return if completionTokens isNaN

fix(OpenAIClient): ensure spendTokens has all necessary params

refactor: route/controller logic

fix(assistants/initializeClient): use defaultHeaders field

fix: sanitize default operation id

chore: bump openai package

first pass v2 action service

feat: retroactive domain parsing for actions added via v1

feat: delete db records of actions/assistants on openai assistant deletion

chore: remove vision tools from v2 assistants

feat: v2 upload and delete assistant vision images

WIP first pass, thread attachments

fix: show assistant vision files (save local/firebase copy)

v2 image continue

fix: annotations

fix: refine annotations

show analyze as error if is no longer submitting before progress reaches 1 and show file_search as retrieval tool

fix: abort run, undefined endpoint issue

refactor: consolidate capabilities logic and anticipate versioning

frontend version 2 changes

fix: query selection and filter

add endpoint to unknown filepath

add file ids to resource, deleting in progress

enable/disable file search

remove version log

* 🤖 Assistants V2 Support: Part 2

🎹 fix: Autocompletion Chrome Bug on Action API Key Input

chore: remove `useOriginNavigate`

chore: set correct OpenAI Storage Source

fix: azure file deletions, instantiate clients by source for deletion

update code interpret files info

feat: deleteResourceFileId

chore: increase poll interval as azure easily rate limits

fix: openai file deletions, TODO: evaluate rejected deletion settled promises to determine which to delete from db records

file source icons

update table file filters

chore: file search info and versioning

fix: retrieval update with necessary tool_resources if specified

fix(useMentions): add optional chaining in case listMap value is undefined

fix: force assistant avatar roundedness

fix: azure assistants, check correct flag

chore: bump data-provider

* fix: merge conflict

* ci: fix backend tests due to new updates

* chore: update .env.example

* meilisearch improvements

* localization updates

* chore: update comparisons

* feat: add additional metadata: endpoint, author ID

* chore: azureAssistants ENDPOINTS exclusion warning
2024-05-19 12:56:55 -04:00
Elijah Shackelford
af8bcb08d6 👨‍🔧 fix: recognize command+click on macos (#2786)
Fixes an issue where the "command+click" was not being recognized on
MacOS. The desired behavior was working fine on Windows using
"ctrl+click", but the MacOS equivalent was broken.

This was preventing new tabs from opening while holding "command" (meta
key) on MacOS and clicking.

I verified this change fixes the issue by building locally and testing.
2024-05-19 02:44:14 -04:00
Danny Avila
f0e8cca5df 🚀 feat: Shared Links (#2772)
*  feat(types): add necessary types for shared link feature

*  feat: add shared links functions to data service

Added functions for retrieving, creating, updating, and deleting shared links and shared messages.

*  feat: Add useGetSharedMessages hook to fetch shared messages by shareId

Adds a new hook `useGetSharedMessages` which fetches shared messages based on the provided shareId.

*  feat: Add share schema and data access functions to API models

*  feat: Add share endpoint to API

The GET /api/share/${shareId} is exposed to the public, so authentication is not required. Other paths require authentication.

* ♻️ refactor(utils): generalize react-query cache manipulation functions

Introduces generic functions for manipulating react-query cache entries, marking a refinement in how query cache data is managed. It aims to enhance the flexibility and reusability of the cache interaction patterns within our application.

- Replaced specific index names with more generic terms in queries.ts, enhancing consistency across data handling functions.
- Introduced new utility functions in collection.ts for adding, updating, and deleting data entries in an InfiniteData<TCollection>. These utility functions (`addData`, `updateData`, `deleteData`, `findPage`) are designed to be re-usable across different data types and collections.
- Adapted existing conversation utility functions in convos.ts to leverage these new generic utilities.

*  feat(shared-link): add functions to manipulate shared link cache list

implemented new utility functions to handle additions, updates, and deletions in the shared link cache list.

*  feat: Add mutations and queries for shared links

*  feat(shared-link): add `Share` button to conversation list

- Added a share button in each conversation in the conversation list.
- Implemented functionality where clicking the share button triggers a POST request to the API.
- The API checks if a share link was already created for the conversation today; if so, it returns the existing link.
- If no link was created for today, the API will create a new share link and return it.
- Each click on the share button results in a new API request, following the specification similar to ChatGPT's share link feature.

* ♻️ refactor(hooks): generalize useNavScrolling for broader use

- Modified `useNavScrolling` to accept a generic type parameter `TData`, allowing it to be used with different data structures besides `ConversationListResponse`.
- Updated instances in `Nav.tsx` and `ArchivedChatsTable.tsx` to explicitly specify `ConversationListResponse` as the type argument when invoking `useNavScrolling`.

*  feat(settings): add shared links listing table with delete functionality in settings

- Integrated a delete button for each shared link in the table, allowing users to remove links as needed.

* ♻️ refactor(components): separate `EndpointIcon` from `Icon` component for standalone use

* ♻️ refactor: update useGetSharedMessages to return TSharedLink

- Modified the useGetSharedMessages hook to return not only a list of TMessage but also the TSharedLink itself.
- This change was necessary to support displaying the title and date in the Shared Message UI, which requires data from TSharedLink.

*  feat(shared link): add UI for displaying shared conversations without authentication

- Implemented a new UI component to display shared conversations, designed to be accessible without requiring authentication.
- Reused components from the authenticated Messages module where possible. Copied and adapted components that could not be directly reused to fit the non-authenticated context.

* 🔧 chore: Add translations

Translate labels only. Messages remain in English as they are possibly subject to change.

* ♻️ refactor: add icon and tooltip props to EditMenuButton component

* moved icon and popover to arguments so that EditMenuButton can be reused.
* modified so that when a ShareButton is closed, the parent DropdownMenu is also closed.

* ♻️irefactor: added DropdownMenu for Export and Share

* ♻️ refactor: renamed component names more intuitive

* More accurate naming of the dropdown menu.
* When the export button is closed, the parent dropdown menu is also closed.

* 🌍 chore: updated translations

* 🐞 Fix: OpenID Profile Image Download (#2757)

* Add fetch requirement

Fixes - error: [openidStrategy] downloadImage: Error downloading image at URL "https://graph.microsoft.com/v1.0/me/photo/$value": TypeError: response.buffer is not a function

* Update openidStrategy.js

---------

Co-authored-by: Danny Avila <danacordially@gmail.com>

* 🚑 fix(export): Issue exporting Conversation with Assistants (#2769)

* 🚑 fix(export): use content as text if content is present in the message

If the endpoint is assistants, the text of the message goes into content, not message.text.

* refactor(ExportModel): TypeScript, remove unused code

---------

Co-authored-by: Yuichi Ohneda <ohneda@gmail.com>

* 📤style: export button icon (#2752)

* refactor(ShareDialog): logic and styling

* refactor(ExportAndShareMenu): imports order and icon update

* chore: imports

* chore: imports/render logic

* feat: message branching

* refactor: add optional config to useGetStartupConfig

* refactor: disable endpoints query

* chore: fix search view styling gradient in light mode

* style: ShareView gradient styling

* refactor(Share): use select queries

* style: shared link table buttons

* localization and dark text styling

* style: fix clipboard button layout shift app-wide and add localization for copy code

* support assistants message content in shared links, add useCopyToClipboard, add copy buttons to Search Messages and Shared Link Messages

* add localizations

* comparisons

---------

Co-authored-by: Yuichi Ohneda <ohneda@gmail.com>
Co-authored-by: bsu3338 <bsu3338@users.noreply.github.com>
Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
2024-05-17 18:13:32 -04:00
Fuegovic
38ad36c1c5 📤style: export button icon (#2752) 2024-05-17 14:16:02 -04:00
Danny Avila
53fe2f6453 🚑 fix(export): Issue exporting Conversation with Assistants (#2769)
* 🚑 fix(export): use content as text if content is present in the message

If the endpoint is assistants, the text of the message goes into content, not message.text.

* refactor(ExportModel): TypeScript, remove unused code

---------

Co-authored-by: Yuichi Ohneda <ohneda@gmail.com>
2024-05-17 14:10:40 -04:00
bsu3338
31479d6a48 🐞 Fix: OpenID Profile Image Download (#2757)
* Add fetch requirement

Fixes - error: [openidStrategy] downloadImage: Error downloading image at URL "https://graph.microsoft.com/v1.0/me/photo/$value": TypeError: response.buffer is not a function

* Update openidStrategy.js

---------

Co-authored-by: Danny Avila <danacordially@gmail.com>
2024-05-17 14:03:31 -04:00
Anirudh
612a58737d 💻 fix: Prevent DataTable component Text Selection (#2749) 2024-05-16 11:12:31 -04:00
Danny Avila
8a7f36f581 🤖 fix: Azure Assistants, use deploymentName as Run Model (#2736) 2024-05-15 23:34:35 -04:00
Danny Avila
4a5d06a774 ❇️ style(ModelSpecs): optimize for Long/Chinese name and mobile styling (#2731)
* style: hide nav toggle for mobile

* ❇️ style: optimize for Long/Chinese `modelSpec` name and mobile styling
2024-05-15 09:53:00 -04:00
Danny Avila
fc9368e0e7 feat: Gemini-1.5 Flash, gpt-4o imports, modelSpec greeting fix (#2729)
* fix: Gemini Flash stream fix

* fix: correct `sender` field for gpt-4o imports from ChatGPT

* add flash model examples and fix vertex streaming

* style: modelSpec greeting fix
2024-05-15 09:02:48 -04:00
Yuichi Oneda
64bf0800a0 🚑 fix: Display Error Message when API Connection fails during Chat (#2710) 2024-05-14 18:29:52 -04:00
Danny Avila
94eeec354e 🔀 fix: Address Convo/Preset Switching Issues (#2709)
* fix(schemas): interchangeability between chatGptLabel <> modelLabel

* fix: display error message from enforceModelSpecs when conversation already existed

* fix(Mention): use activeIndex on mention tab/enter

* fix: correctly navigate to new conversation when deleting/archiving a new, active convo
2024-05-14 15:19:58 -04:00
Danny Avila
e42709bd1f 🔍 feat: Show Messages from Search Result (#2699)
* refactor(Nav): delegate Search-specific variables/hooks to SearchContext

* fix: safely determine firstTodayConvoId if convo is undefined

* chore: remove empty line

* feat: initial render of search messages

* feat: SearchButtons

* update Ko.ts

* update localizations with new key phrases

* chore: localization comparisons

* fix: clear conversation state on searchQuery navigation

* style: search messages view styling

* refactor(Convo): consolidate logic to navigateWithLastTools from useNavigateToConvo

* fix(SearchButtons): styling and correct navigation logic

* fix(SearchBar): invalidate all message queries and invoke `clearText` if onChange value is empty

* refactor(NewChat): consolidate new chat button logic to NewChatButtonIcon

* chore: localizations for Nav date groups

* chore: update comparisons

* fix: early return from sendRequest to avoid quick searchQuery reset

* style: Link Icon

* chore: bump tiktoken, use o200k_base for gpt-4o
2024-05-14 11:00:01 -04:00
Danny Avila
638ac5bba6 🚀 feat: gpt-4o (#2692)
* 🚀 feat: gpt-4o

* update readme.md

* feat: Add new test case for getMultiplier function

* feat: Refactor getMultiplier function to use valueKey variable
2024-05-13 14:25:02 -04:00
Christian Köberl
5920672a8c 🐋 ci: create smaller Docker images (#2691)
- create fewer layers
- install only prod dependencies for final build
- clean npm cache
- fix layering in multi-image build
2024-05-13 10:47:18 -04:00
Danny Avila
a0d1e2a5f8 🪶 docs: Update README.md Icon 2024-05-13 10:42:09 -04:00
Danny Avila
4ffc1414a8 Revert "🐋 refactor(docker-compose): use "HOST" in ports field (#2654)"
This reverts commit df6183db0f.
2024-05-13 10:36:36 -04:00
nidasfly
df6183db0f 🐋 refactor(docker-compose): use "HOST" in ports field (#2654) 2024-05-13 10:31:13 -04:00
Fuegovic
3816219936 🧹 chore: remove old docs (#2684)
* delete docs folder

* delete mkdocs

* update .env.example

* update compose.override

* update librechat.yaml

* update pr template

* update librechat.yaml

* update README.md

* update missing custom config error msg

* update loadCustomConfig.js

* update check.js

* update .env.example

* update replit reference

* update README.md

* prevent logger URL truncation

* fix broken link in templates

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2024-05-13 10:15:30 -04:00
Danny Avila
6fc664e4a3 ⚙️ feat: includedTools and script changes (#2690)
* chore: add email to npm scripts (user-stats and list-balances)

* feat: included tools

* chore: update console terminal links

* chore: add back typing
2024-05-13 10:07:10 -04:00
Danny Avila
89899164ed 👮 fix(enforceModelSpec): handle nested objects (#2681) 2024-05-12 18:20:53 -04:00
Danny Avila
c83d9d61d4 🧹 chore(/config/): add tsconfig.json & linting (#2680) 2024-05-12 16:24:13 -04:00
Danny Avila
bcdddaed72 🎨 style(MentionItem): update active bg-color (#2675) 2024-05-12 09:20:59 -04:00
Marco Beretta
a4de635719 🎨 style(MentionItem): update hover color (#2670)
* style(MentionItem): update hover color

* remove unused className
2024-05-11 21:12:22 -04:00
Danny Avila
4a32d7466a v0.7.2 (#2667)
* v0.7.2

* chore: uninstall hnswlib-node

* bump package provider

* bump librechat-data-provider in lockfile

* README: ross index

* chore: center star history
2024-05-10 16:29:27 -04:00
Danny Avila
2ec821ea4c 🌍 : Updated Translations & AI Generation Scripts (#2666)
* chore: bun scripts

* feat: comparisons

* refactor: move scripts to own folder

* feat: generated prompts script and Es output

* feat: generated prompts

* created prompts

* feat: Russian localization prompts

* translation setup

* additional ES translations

* additional ES translations

* translation services

* feat: additional translations

* fix regex for parseParamPrompt

* RU translations

* remove stores from git

* update gitignore

* update gitignore

* ZH translations

* move gen prompt output location

* ZH traditional translations

* AR translations

* chore: rename

* JP

* cleanup scripts

* add additional instruction prompts

* fix translation prompt and add DE

* FR translations (rate limited so not complete)

* chore: update translation comparisons

* chore: remove unused AnthropicClient changes

* refactor: use compositional styling for archive/delete buttons, fix manage archive table styling
2024-05-10 15:56:25 -04:00
Marco Beretta
978009787c 📝 docs(ai_endpoints): update HF key link (#2664) 2024-05-10 12:48:39 -04:00
Danny Avila
27e7621b6a 🪟 fix: Windows Vite Build Issue (#2663)
* fix: Windows CSS VITE build issue: peer-focus:dark => dark:peer-focus

* ci: add windows env step for frontend ci/cd
2024-05-10 12:44:43 -04:00
Danny Avila
2b37a44b8d 🔧 fix: Preset Dialog Styling and Values (#2657)
* style: preset dialog styling

* refactor: coerce number input for convo schema

* refactor: replace dynamic input number with static component
2024-05-10 03:05:45 -04:00
Danny Avila
98c96cd020 🦙 fix: Ollama System Message order (#2655) 2024-05-10 00:50:02 -04:00
Anirudh
8f20fb28e5 🪟 fix+feat: General UI Enhancements (#2619)
* feat: Minor design changes to mimic OpenAI's latest login page

* fix: Optimize ThemeSelector for mobile

* fix: Use a svg for the logo for transperency in dark mode

* feat: Update styles for Registration

* feat: Update error colors for login & registration

* fix: remove medium font

* wip: Dropdown menu

* feat: Update dropdown to match ChatGPT

* feat: Improve rounding and padding

* feat: Add UI Updates to RequestPasswordReset, PasswordRest and increase width for theme dropdown

* fix: Modify the My Files modal's width to not touch the screen

* feat: fix scrolling for dropdown, and make border width lighter

* feat: Match popup menu design to OpenAI (p1/2)

* fix+feat: fix dark mode, add user email, add lighter borders

* fix: Add border color on focus of chat input.

* feat: Move Export Conversation to a seperate button (testing)

* fix: Properly center Login, Registration, Reset Password Flow

* fix: Border colors on dark mode for settings modal

* feat: Improve wording for settings menu

* fix: Optimize settings modal for mobile and fix height for modal

* feat: Optimize for desktop

* fix: make TooltipTrigger asChild of button, improve settings mobile responsiveness

* feat: Handle dropdowns properly
TODO: Make height dynamic, fix dark mode colors

* fix: input styles
fix: make endpoint icon smaller

* feat: Update UI to Match ChatGPT Style

- Updated the dropdown styles to match the aesthetic of ChatGPT.
- Decreased spacing within the conversation area for cleanliness.
- Replaced the current archive icon with the ChatGPT's icon.

* fix: fix colors for EditMenuButton & ArchiveButton for dark mode and light mode

* fix: ui fixes

* fix: Fix Conversation UI Bugs

* fix: transparency of HoverToggle to make buttons not visible

* fix: dark mode HoverToggle & compress menu item spacing

* fix: responsiveness of export icon

* fix: first mentionitem is set to always be highlighted

* fix: improve hover state to text instead of bg

* feat: Update icons to ChatGPT Style

* fix: dark mode hover for PanelFileCell

* fix: change navlinks z-index to 100

* fix: hover states for DataTable

* feat: Move ExportButton to seperate component

* chore: remove unused imports
2024-05-09 17:46:16 -04:00
Fuegovic
d73ea8e1f2 🤗 feat: Known Endpoints: HuggingFace (#2646)
* endpoints: huggingface

* Update ai_endpoints.md

* huggingface: update icon
2024-05-09 14:26:47 -04:00
Marco Beretta
83bae9e9d9 🔧 fix: android keyboard @ popover issue (#2647) 2024-05-09 13:31:55 -04:00
Danny Avila
6ba7f60eec 🪙 feat: Configure Max Context and Output Tokens (#2648)
* chore: make frequent 'error' log into 'debug' log

* feat: add maxContextTokens as a conversation field

* refactor(settings): increase popover height

* feat: add DynamicInputNumber and maxContextTokens to all endpoints that support it (frontend), fix schema

* feat: maxContextTokens handling (backend)

* style: revert popover height

* feat: max tokens

* fix: Ollama Vision firebase compatibility

* fix: Ollama Vision, use message_file_map to determine multimodal request

* refactor: bring back MobileNav and improve title styling
2024-05-09 13:27:13 -04:00
Danny Avila
5293b73b6d 🤖 feat(google): Add safety settings configuration (#2644)
* 🤖 feat(google): Add safety settings configuration

- Implement safety settings configuration in GoogleClient.js
- Add safety settings variables in .env.example
- Update documentation to explain safety settings and clarify model usage

* fix(google): Apply safety settings only to Gemini models

Previously, the safety settings were being applied to all models, regardless of whether they were Gemini models or not. This commit ensures that the safety settings are only applied to models that contain the "gemini" string in their name.

The changes include:

- Extracting the model name from `payload.parameters.model`
- Checking if the model name exists and contains the "gemini" string
- Only applying the safety settings if the model name contains "gemini"
- Ignoring the safety settings for non-Gemini models

This fix ensures that the safety settings are only used for the intended Gemini models, and not applied to other models where they may not be applicable.

* Update GoogleClient.js

* fix(google): Apply safety settings only to Gemini models

---------

Co-authored-by: Oliver Faust <oliver@f4ust.de>
2024-05-08 21:32:23 -04:00
Walber Cardoso
b6d1f5fa53 🎨 style: Convo fade effect (#2642)
* style: Improve fade effect into convos

* style: Improve fade effect into convos. WIP code

* 🔧 fix: Convo fade effect

* 🔧 fix: Convo fade effect: removed listeners
2024-05-08 21:21:55 -04:00
Danny Avila
c94278be85 🦙 feat: Ollama Vision Support (#2643)
* refactor: checkVisionRequest, search availableModels for valid vision model instead of using default

* feat: install ollama-js, add typedefs

* feat: Ollama Vision Support

* ci: fix test
2024-05-08 20:24:40 -04:00
Danny Avila
3c5fa40435 📶 fix: Mobile Stylings (#2639)
* chore: remove unused mobile nav

* fix: mobile nav fix for 'more' and 'archive' buttons div

* refactor(useTextarea): rewrite handleKeyUp for backwards compatibility

refactor(useTextarea): rewrite handleKeyUp for backwards compatibility

* experimental: add processing delay to azure streams for better performance/UX

* experiemental: adjust gpt-3 azureDelay

* fix: perplexity titles
2024-05-08 16:40:20 -04:00
Danny Avila
b6d6343f54 📧 feat: Mention "@" Command Popover (#2635)
* feat: initial mockup

* wip: activesetting, may use or not use

* wip: mention with useCombobox usage

* feat: connect textarea to new mention popover

* refactor: consolidate icon logic for Landing/convos

* refactor: cleanup URL logic

* refactor(useTextarea): key up handler

* wip: render desired mention options

* refactor: improve mention detection

* feat: modular chat the default option

* WIP: first pass mention selection

* feat: scroll mention items with keypad

* chore(showMentionPopoverFamily): add typing to atomFamily

* feat: removeAtSymbol

* refactor(useListAssistantsQuery): use defaultOrderQuery as default param

* feat: assistants mentioning

* fix conversation switch errors

* filter mention selections based on startup settings and available endpoints

* fix: mentions model spec icon URL

* style: archive icon

* fix: convo renaming behavior on click

* fix(Convo): toggle hover state

* style: EditMenu refactor

* fix: archive chats table

* fix: errorsToString import

* chore: remove comments

* chore: remove comment

* feat: mention descriptions

* refactor: make sure continue hover button is always last, add correct fork button alt text
2024-05-07 13:13:55 -04:00
Yuichi Ohneda
89b1e33be0 🚀feat: Archive conversations (#2590)
* 🔧chore: add internationalization labels for archive feature

*  feat: Add function to useArchiveConversationMutation()

This commit adds a new mutation function `useArchiveConversationMutation()` for archiving conversations. This function takes the ID string of the conversation to be archived and returns a mutation result object. Upon successful archiving, it removes and refreshes the conversation from the query data cache.
While ChatGPT PATCHes the archived status by sending `{is_archived: true}` to the URL `/backend-api/conversation/$conversation_id`, this implementation uses the `dataService.updateConversation(payload)` with a POST method, aligning with the existing code conventions.

*  feat(api): add is_archived field to Conversation schema and update getConvosByPage method

This commit adds a new field `is_archived` with a default value of false to the Conversation schema. It also modifies the `getConvosByPage` method within the Conversation API to adjust the query to only target conversations where `is_archived` is set to false or where the `is_archived` field does not exist. The function `getConvosQueried`, which returns conversations for a specified Conversation ID, was determined not to require consideration of whether `is_archived` is true or false, and thus was not modified.

* ♻️ refactor: add className prop to DotsIcon component

To enhance the versatility of the DotsIcon component, this commit introduces the ability to specify a className prop, allowing for greater customization.

*  feat(ui): add Edit Button to group Title change and Conversation delete buttons

Added a new Edit Button to the conversations, similar to the ChatGPT UI, which groups options for editing the conversation title and deleting conversations. This grouping is accessible through a dialogue that appears when the three-dot icon is clicked.

* ♻️ refactor(ui): enhance Delete Button to accept className and label options

Enhanced the Delete Button component to accept a `className` for customization and an optional `appendLabel`. The DeleteButton component is used by both `Convo.tsx` and `Conversation.tsx`, but currently only `Convo.tsx` is active and `Conversation.tsx `is apparently not used; removing `Conversation.tsx` may eliminate the need for the `appendLabel` property in the future.

* ♻️ refactor(ui): enhance RenameButton to accept label options

Added the ability to optionally display labels; the Rename Button component is used by both `Convo.tsx` and `Conversation.tsx`, but currently only `Convo.tsx` is active and `Conversation.tsx `is apparently not used; removing `Conversation.tsx` may eliminate the need for the `appendLabel` property in the future.

* 🔧 chors: additional localization labels

* ♻️  refactor: change is_archived property of conversation to camelCase

* Refactor the is_archived property of conversation to camelCase (isArchived) to adhere to the existing code conventions
* Modify the function that retrieves conversations to accept the isArchived parameter

* ♻️ refactor: add archiveConversation mutation

I thought I could divert dataService.updateConversation, but added a new archiveConversation because the request types are different. It might be better to make them common, but to avoid side effects, I added a new function this time.
Added process to deleteConversationMutation to delete archived conversations

*  feat: Add the function to hide a cancel button in DialogTemplate component

The Cancel button is not needed when displaying the archive list, so I made the Cancel button optional.

* ♻️ refactor: Add support for filtering archived conversations in Nav component

This commit modifies the Nav component to add the ability to filter out archived conversations when fetching data. This is done by adding `isArchived: false` to the query parameters for both the `useConversationsInfiniteQuery()` and `useSearchInfiniteQuery()` hooks, effectively excluding any archived conversations from the results returned.

* ♻️ refactor: add Tooltip to DeleteButton

* Add Tooltip to DeleteButton component
* Display Tooltip when DeleteButton only shows an Icon without text

*  feat(ui): add ArchiveButton component for archiving conversations

To be compatible with the ChatGPT UI, no confirmation dialog is displayed when ArchiveButton is clicked. The basic behavior conforms to DeleteButton and RenameButton.

*  feat(ui): add Archive button to list of conversations

Modify the Nav of the conversation list to include a dropdown that contains the Rename and Delete options, similar to the ChatGPT UI. Additionally, an Archive button has been added adjacent to the dropdown menu.

*  feat: Add ArchivedChatsTable component

Adds the `ArchivedChatsTable` component, which displays a table of archived chats. It has been implemented to be as compatible with the ChatGPT UI as possible.

* 🚑 fix(tooltip): increase z-index to ensure visibility over Dialog

Resolve an issue where tooltips were not visible when displayed over a Dialog. The z-index of `DialogPrimitive.Portal` in `Dialog.tsx` is set to 999. Since the rationale for this value is unclear, the z-index of the tooltip has been increased to 1000 to guarantee its visibility above the Dialog component.

* 🔧 chors: add internationalization labels
2024-05-06 23:07:00 -04:00
Marco Beretta
436f7195b5 🌍: Update Italian translation (#2622) 2024-05-06 07:51:01 -04:00
Marco Beretta
2aec4a6250 🔄 refactor: improved RAG animations/messages (#2616)
* fix: warning slow process rag  message

* refactor: improved useProgress hook for Files
2024-05-05 15:35:51 -04:00
Marco Beretta
b77bd19092 🎨 style(Fork): update light/dark theme (#2621) 2024-05-05 15:35:16 -04:00
Danny Avila
446ffe0417 📝 docs: update README.md 2024-05-05 12:51:38 -04:00
Danny Avila
b9bcaee656 📝 docs: update README.md 2024-05-05 12:48:26 -04:00
Danny Avila
110c0535fb Update README.md 2024-05-05 12:38:17 -04:00
Danny Avila
25fceb78b7 🌿 feat: Fork Messages/Conversations (#2617)
* typedef for ImportBatchBuilder

* feat: first pass, fork conversations

* feat: fork - getMessagesUpToTargetLevel

* fix: additional tests and fix getAllMessagesUpToParent

* chore: arrow function return

* refactor: fork 3 options

* chore: remove unused genbuttons

* chore: remove unused hover buttons code

* feat: fork first pass

* wip: fork remember setting

* style: user icon

* chore: move clear chats to data tab

* WIP: fork UI options

* feat: data-provider fork types/services/vars and use generic MutationOptions

* refactor: use single param for fork option, use enum, fix mongo errors, use Date.now(), add records flag for testing, use endpoint from original convo and messages, pass originalConvo to finishConversation

* feat: add fork mutation hook and consolidate type imports

* refactor: use enum

* feat: first pass, fork mutation

* chore: add enum for target level fork option

* chore: add enum for target level fork option

* show toast when checking remember selection

* feat: splitAtTarget

* feat: split at target option

* feat: navigate to new fork, show toasts, set result query data

* feat: hover info for all fork options

* refactor: add Messages settings tab

* fix(Fork): remember text info

* ci: test for single message and is target edge case

* feat: additional tests for getAllMessagesUpToParent

* ci: additional tests and cycle detection for getMessagesUpToTargetLevel

* feat: circular dependency checks for getAllMessagesUpToParent

* fix: getMessagesUpToTargetLevel circular dep. check

* ci: more tests for getMessagesForConversation

* style: hover text for checkbox fork items

* refactor: add statefulness to conversation import
2024-05-05 11:48:20 -04:00
Danny Avila
c8baceac76 🐛 fix: Prevent Empty File Uploads & Assistants Fixes (#2611)
* chore: update default models for openai/assistants

* fix: allows assistants models fetching

* change default models order, ensure assistant_id is defined if intended

* fix: prevent empty files from being uploaded
2024-05-03 12:49:26 -04:00
Kai Kreuzer
a0288f1c5c 🧾 docs: Fix Typo in librechat.example.yaml (#2606) 2024-05-02 16:27:11 -04:00
Danny Avila
5d3c90be26 📦 chore: update package.json/package-lock.json (#2600)
* 📦 chore: update package.json/package-lock.json

* 📦 chore: remove unused dependencies
2024-05-02 03:23:38 -04:00
Denis Palnitsky
ab6fbe48f1 📥 feat: Import Conversations from LibreChat, ChatGPT, Chatbot UI (#2355)
* Basic implementation of ChatGPT conversation import

* remove debug code

* Handle citations

* Fix updatedAt in import

* update default model

* Use job scheduler to handle import requests

* import job status endpoint

* Add wrapper around Agenda

* Rate limits for import endpoint

* rename import api path

* Batch save import to mongo

* Improve naming

* Add documenting comments

* Test for importers

* Change button for importing conversations

* Frontend changes

* Import job status endpoint

* Import endpoint response

* Add translations to new phrases

* Fix conversations refreshing

* cleanup unused functions

* set timeout for import job status polling

* Add documentation

* get extra spaces back

* Improve error message

* Fix translation files after merge

* fix translation files 2

* Add zh translation for import functionality

* Sync mailisearch index after import

* chore: add dummy uri for jest tests, as MONGO_URI should only be real for E2E tests

* docs: fix links

* docs: fix conversationsImport section

* fix: user role issue for librechat imports

* refactor: import conversations from json
- organize imports
- add additional jsdocs
- use multer with diskStorage to avoid loading file into memory outside of job
- use filepath instead of loading data string for imports
- replace console logs and some logger.info() with logger.debug
- only use multer for import route

* fix: undefined metadata edge case and replace ChatGtp -> ChatGpt

* Refactor importChatGptConvo function to handle undefined metadata edge case and replace ChatGtp with ChatGpt

* fix: chatgpt importer

* feat: maintain tree relationship for librechat messages

* chore: use enum

* refactor: saveMessage to use single object arg, replace console logs, add userId to log message

* chore: additional comment

* chore: multer edge case

* feat: first pass, maintain tree relationship

* chore: organize

* chore: remove log

* ci: add heirarchy test for chatgpt

* ci: test maintaining of heirarchy for librechat

* wip: allow non-text content type messages

* refactor: import content part object json string

* refactor: more content types to format

* chore: consolidate messageText formatting

* docs: update on changes, bump data-provider/config versions, update readme

* refactor(indexSync): singleton pattern for MeiliSearchClient

* refactor: debug log after batch is done

* chore: add back indexSync error handling

---------

Co-authored-by: jakubmieszczak <jakub.mieszczak@zendesk.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
2024-05-02 02:48:26 -04:00
Yuichi Ohneda
3b44741cf9 🚑 fix(dialog): showCloseButton Prop Warning in DialogContent Component (#2597)
Fix DialogPrimitive.Content to not pass a showCloseButton property that does not exist in it to avoid outputting a warning.
2024-05-02 02:08:37 -04:00
Extremys
d21a05606e 🍎 feat: Apple MLX as Known Endpoint (#2580)
* add integration with Apple MLX

* fix: apple icon + image mkd link

---------

Co-authored-by: “Extremys” <“Extremys@email.com”>
Co-authored-by: Danny Avila <danny@librechat.ai>
2024-05-01 03:27:02 -04:00
Danny Avila
0e50c07e3f 🤖 feat: Model Specs & Save Tools per Convo/Preset (#2578)
* WIP: first pass ModelSpecs

* refactor(onSelectEndpoint): use `getConvoSwitchLogic`

* feat: introduce iconURL, greeting, frontend fields for conversations/presets/messages

* feat: conversation.iconURL & greeting in Landing

* feat: conversation.iconURL & greeting in New Chat button

* feat: message.iconURL

* refactor: ConversationIcon -> ConvoIconURL

* WIP: add spec as a conversation field

* refactor: useAppStartup, set spec on initial load for new chat, allow undefined spec, add localStorage keys enum, additional type fields for spec

* feat: handle `showIconInMenu`, `showIconInHeader`, undefined `iconURL` and no specs on initial load

* chore: handle undefined or empty modelSpecs

* WIP: first pass, modelSpec schema for custom config

* refactor: move default filtered tools definition to ToolService

* feat: pass modelSpecs from backend via startupConfig

* refactor: modelSpecs config, return and define list

* fix: react error and include iconURL in responseMessage

* refactor: add iconURL to responseMessage only

* refactor: getIconEndpoint

* refactor: pass TSpecsConfig

* fix(assistants): differentiate compactAssistantSchema, correctly resets shared conversation state with other endpoints

* refactor: assistant id prefix localStorage key

* refactor: add more LocalStorageKeys and replace hardcoded values

* feat: prioritize spec on new chat behavior: last selected modelSpec behavior (localStorage)

* feat: first pass, interface config

* chore: WIP, todo: add warnings based on config.modelSpecs settings.

* feat: enforce modelSpecs if configured

* feat: show config file yaml errors

* chore: delete unused legacy Plugins component

* refactor: set tools to localStorage from recoil store

* chore: add stable recoil setter to useEffect deps

* refactor: save tools to conversation documents

* style(MultiSelectPop): dynamic height, remove unused import

* refactor(react-query): use localstorage keys and pass config to useAvailablePluginsQuery

* feat(utils): add mapPlugins

* refactor(Convo): use conversation.tools if defined, lastSelectedTools if not

* refactor: remove unused legacy code using `useSetOptions`, remove conditional flag `isMultiChat` for using legacy settings

* refactor(PluginStoreDialog): add exhaustive-deps which are stable react state setters

* fix(HeaderOptions): pass `popover` as true

* refactor(useSetStorage): use project enums

* refactor: use LocalStorageKeys enum

* fix: prevent setConversation from setting falsy values in lastSelectedTools

* refactor: use map for availableTools state and available Plugins query

* refactor(updateLastSelectedModel): organize logic better and add note on purpose

* fix(setAgentOption): prevent reseting last model to secondary model for gptPlugins

* refactor(buildDefaultConvo): use enum

* refactor: remove `useSetStorage` and consolidate areas where conversation state is saved to localStorage

* fix: conversations retain tools on refresh

* fix(gptPlugins): prevent nullish tools from being saved

* chore: delete useServerStream

* refactor: move initial plugins logic to useAppStartup

* refactor(MultiSelectDropDown): add more pass-in className props

* feat: use tools in presets

* chore: delete unused usePresetOptions

* refactor: new agentOptions default handling

* chore: note

* feat: add label and custom instructions to agents

* chore: remove 'disabled with tools' message

* style: move plugins to 2nd column in parameters

* fix: TPreset type for agentOptions

* fix: interface controls

* refactor: add interfaceConfig, use Separator within Switcher

* refactor: hide Assistants panel if interface.parameters are disabled

* fix(Header): only modelSpecs if list is greater than 0

* refactor: separate MessageIcon logic from useMessageHelpers for better react rule-following

* fix(AppService): don't use reserved keyword 'interface'

* feat: set existing Icon for custom endpoints through iconURL

* fix(ci): tests passing for App Service

* docs: refactor custom_config.md for readability and better organization, also include missing values

* docs: interface section and re-organize docs

* docs: update modelSpecs info

* chore: remove unused files

* chore: remove unused files

* chore: move useSetIndexOptions

* chore: remove unused file

* chore: move useConversation(s)

* chore: move useDefaultConvo

* chore: move useNavigateToConvo

* refactor: use plugin install hook so it can be used elsewhere

* chore: import order

* update docs

* refactor(OpenAI/Plugins): allow modelLabel as an initial value for chatGptLabel

* chore: remove unused EndpointOptionsPopover and hide 'Save as Preset' button if preset UI visibility disabled

* feat(loadDefaultInterface): issue warnings based on values

* feat: changelog for custom config file

* docs: add additional changelog note

* fix: prevent unavailable tool selection from preset and update availableTools on Plugin installations

* feat: add `filteredTools` option in custom config

* chore: changelog

* fix(MessageIcon): always overwrite conversation.iconURL in messageSettings

* fix(ModelSpecsMenu): icon edge cases

* fix(NewChat): dynamic icon

* fix(PluginsClient): always include endpoint in responseMessage

* fix: always include endpoint and iconURL in responseMessage across different response methods

* feat: interchangeable keys for modelSpec enforcing
2024-04-30 22:11:48 -04:00
Ventz Petkov
a5cac03fa4 🚅 docs: load LiteLLM into LibreChat (#2573)
Documentation was missing one significant component, on loading librechat.yaml
2024-04-29 19:56:19 -04:00
Fuegovic
ba4fa6150e 🦙 docs: fix litellm.md (#2566) 2024-04-28 08:33:51 -04:00
Neelesh Kumar
463ca5d613 📄 docs: Update apipie fetch.py in ai_endpoints.md (#2547)
* Update apipie fetch.py in ai_endpoints.md

Made the python code more pythonic

* fix bug that caused duplicate model_ids
2024-04-27 18:37:33 -04:00
Neelesh Kumar
039c7ae880 📄 docs: update GOOGLE_MODELS in .env.example (#2506)
* update GOOGLE_MODELS in .env.example

* Update .env.example

* Update .env.example

* Update .env.example

* Update .env.example

---------

Co-authored-by: Danny Avila <danacordially@gmail.com>
2024-04-27 18:36:54 -04:00
Danny Avila
63ef15ab63 🦙 feat: Fetch list of Ollama Models (#2565)
* 🦙 feat: Fetch list of Ollama Models

* style: better Tag text styling for light mode
2024-04-27 18:27:04 -04:00
Ventz Petkov
8a78500fe2 🚅 docs: update LiteLLM config with more models (#2553)
Added all OpenAI, Azure OpenAI, Amazon Bedrock, and Google GCP models available.

Specifically latest: Llama3; Google Gemini 1.5 Pro Preview; Claude Opus.

Here are all of the models:
  - model_name: claude-3-haiku
  - model_name: claude-3-sonnet
  - model_name: claude-3-opus
  - model_name: claude-v2
  - model_name: claude-instant
  - model_name: llama2-13b
  - model_name: llama2-70b
  - model_name: llama3-8b
  - model_name: llama3-70b
  - model_name: mistral-7b-instruct
  - model_name: mixtral-8x7b-instruct
  - model_name: mixtral-large
  - model_name: cohere-command-v14
  - model_name: cohere-command-light-v14
  - model_name: ai21-j2-mid
  - model_name: ai21-j2-ultra
  - model_name: amazon-titan-lite
  - model_name: amazon-titan-express
  - model_name: azure-gpt-4-turbo-preview
  - model_name: azure-gpt-3.5-turbo
  - model_name: azure-gpt-4
  - model_name: azure-gpt-3.5-turbo-16k
  - model_name: azure-gpt-4-32k
  - model_name: gpt-4-turbo
  - model_name: old-gpt-4-turbo-preview
  - model_name: gpt-3.5-turbo
  - model_name: gpt-4
  - model_name: gpt-3.5-turbo-16k
  - model_name: gpt-4-32k
  - model_name: gpt-4-vision-preview
  - model_name: google-chat-bison
  - model_name: google-chat-bison-32k
  - model_name: google-gemini-pro-1.0
  - model_name: google-gemini-pro-1.5-preview
2024-04-27 06:46:20 -04:00
Fuegovic
144fd5f6aa ⚙️ docs: update dotenv.md (#2551)
* ⚙️ docs: update dotenv.md

* Update dotenv.md - formatting
2024-04-26 14:34:50 -04:00
Danny Avila
2720327aa1 👩‍💻 fix: Minor UI fixes (#2548)
* fix(useMessageHelpers): define iconEndpoint

* fix: rely on Assistant Switcher effect for defining `assistant_id`, ensure ChatRoute `newConversation` only fires once
2024-04-26 10:27:49 -04:00
Martin Dahlö
4d0806d3e8 📋 refactor: allow paste in confirm field when resetting passwords (#2542)
* Disabled paste prevention in the confirm password field when resetting passwords.

* chore(ResetPassword): remove comments

---------

Co-authored-by: Danny Avila <danacordially@gmail.com>
2024-04-25 20:26:33 -04:00
Neelesh Kumar
5b5f9b950b 🐋 refactor: Update docker-compose.yml (#2507) 2024-04-25 20:25:51 -04:00
いしかず
3ccff19821 🌍: Update Japanese translation (#2519)
* Update japanese translation

* translate japanese

* Added 5 Japanese translations

---------

Co-authored-by: k-ishii1020 <40662914+8492GitKaz@users.noreply.github.com>
2024-04-25 13:55:59 -04:00
Marco Beretta
11d5e232b3 🧪 refactor(isDomainAllowed): change directory, add tests (#2539) 2024-04-25 13:14:07 -04:00
Danny Avila
099aa9dead feat: Stop Sequences for Conversations & Presets (#2536)
* feat: `stop` conversation parameter

* feat: Tag primitive

* feat: dynamic tags

* refactor: update tag styling

* feat: add stop sequences to OpenAI settings

* fix(Presentation): prevent `SidePanel` re-renders that flicker side panel

* refactor: use stop placeholder

* feat: type and schema update for `stop` and `TPreset` in generation param related types

* refactor: pass conversation to dynamic settings

* refactor(OpenAIClient): remove default handling for `modelOptions.stop`

* docs: fix Google AI Setup formatting

* feat: current_model

* docs: WIP update

* fix(ChatRoute): prevent default preset override before `hasSetConversation.current` becomes true by including latest conversation state as template

* docs: update docs with more info on `stop`

* chore: bump config_version

* refactor: CURRENT_MODEL handling
2024-04-25 11:40:17 -04:00
Danny Avila
4121818124 ✍️ fix(useTextarea): Rich Text Format paste from MS Word (#2530) 2024-04-25 00:10:41 -04:00
Fuegovic
ca9a0fe629 🥧 feat: APIpie support (#2524) 2024-04-24 20:32:18 -04:00
Danny Avila
bde6bb0152 🔀 fix: Remove use of Mongo Transactions (#2525)
* fix: remove use of transactions

* fix: remove use of transactions

* refactor(actions): perform OpenAI API operation first, before attempting database updates
2024-04-24 16:04:46 -04:00
ilsubyeega
667f5f91fe 👤 fix: Use user?.username if user?.name is undefined (#2511)
* Use `user?.username` if `user?.name` is undefined

* Add useLocalize hook to Icon component
2024-04-24 12:34:01 -04:00
Mathieu Breton
75da75be08 🛂 feat(oauth): add domain restriction on social login (#2512) 2024-04-24 12:14:27 -04:00
Danny Avila
cdab1e9cda 🔒 fix: package-lock file for cross-compatibility (#2515) 2024-04-24 10:42:18 -04:00
Danny Avila
3df4fac118 v0.7.1 (#2502)
* chore: make openai package definition explicit

*  v0.7.1

* chore: gpt-4-vision correct context length

* add `llava` to vision models list
2024-04-23 08:57:20 -04:00
Danny Avila
0ae98ff011 🧪 ci: Add .env.test for backend-review.yml (#2501)
* 🧪 ci: Add `.env.test` for `backend-review.yml`

* chore: touch example file
2024-04-23 08:21:12 -04:00
Walber Cardoso
4d05e5b79a 👟 style: WrenchIcon and ImageGen SVG Animations (#2382)
* refactor(WrenchIcon, ImageGen SVG)

* refactor(WrenchIcon SVG)

* refactor(ImageGen SVG)

* refactor(ImageGen SVG) - add CSS
2024-04-23 08:14:28 -04:00
Danny Avila
199f9f32e6 🐋 fix(Dockerfile.multi): Resolve OpenAI SDK @ Latest for Assistants v1 2024-04-22 20:23:28 -04:00
Danny Avila
f94a782b4f 💻 fix(client): Allow Code Filetypes and Suppress Known Vite Warnings (#2492)
* refactor(vite): suppress known warnings

* fix: allow known code filetypes if there is a mismatch of expected type, or originalFile.type is empty

* refactor(useFileHandling): naming, use variable
2024-04-22 20:08:34 -04:00
Fuegovic
738207de50 ✏️docs: remove "copilot-gpt4-service" (#2491)
removed "copilot-gpt4-service", the repository has been disabled
2024-04-22 16:35:05 -04:00
Danny Avila
c96f067689 🔧 fix: Resolve Proper Dependencies to fix Application Error (#2488)
* chore: bump data-provider

* feat: script to check recent dependency updates

* fix: override vite/rollup version for vite build fix
- also remove unused vite-plugin-html
- add vite build to file output command

* chore: bump rollup override to last known working version (v4.16.0 is breaking)

* chore(vite): increase file size cache for workbox

* fix: resolve openai to last known version using assistants v1 latest features and default header

* chore: update openrouter examples
2024-04-22 12:52:30 -04:00
Danny Avila
3bfd185cab feat: Added PWA Setup & Manual Chunks via Vite (#2477)
* added pwa setup via vite config

Added apple status bar meta data

added maskable 512 icon for chrome and android devices

added vite-plugin-pwa

updated vite config to setup the pwa service worker and manifest upon build

* fix(vite): avoid pre-caching generated images

* chore: add manual chunking of larger vendor package

* chore: remove comments

---------

Co-authored-by: davecrab <65996799+davecrab@users.noreply.github.com>
2024-04-21 10:39:15 -04:00
Danny Avila
c937b8cd07 🧑‍💻 refactor: Display Client-facing Errors (#2476)
* fix(Google): allow presets to configure expected maxOutputTokens

* refactor: standardize client-facing errors

* refactor(checkUserKeyExpiry): pass endpoint instead of custom message

* feat(UserService): JSDocs and getUserKeyValues

* refactor: add NO_BASE_URL error type, make use of getUserKeyValues, throw user-specific errors

* ci: update tests with recent changes
2024-04-21 08:31:54 -04:00
Passerby1011
6db91978ca 🎨 style: update CodeSherpa icon (#2417)
The icon address error leads to the icon not displaying.
2024-04-20 15:09:59 -04:00
Danny Avila
8c22bb1d3d 🛠️ fix(Azure/Assistants): Handle Long Domain Names & Other Minor chores (#2475)
* chore: replace violation cache accessors with enum

* chore: fix test

* chore(fileSchema): index timestamps

* fix(ActionService): use encoding/caching strategy for handling assistant function character length limit

* refactor(actions): async `domainParser` also resolve retrieved model (which is deployment name) to user-defined model

* style(AssistantAction): add `whitespace-nowrap` for ellipsis

* refactor(ActionService): if domain is less than or equal to encoded domain fixed length, return domain with replacement of separator

* refactor(actions): use sessions/transactions for updating Assistant Action database records

* chore: remove TTL from ENCODED_DOMAINS cache

* refactor(domainParser): minor optimization and add tests

* fix(spendTokens): use txData.user for token usage logging

* refactor(actions): add helper function `withSession` for database operations with sessions/transactions

* fix(PluginsClient): logger debug `message` field edge case
2024-04-20 15:02:56 -04:00
Noah Ispas
5d642d0187 📙 docs: Remove duplicate information (#2451) 2024-04-19 22:57:40 -04:00
Fuegovic
4196a86fa9 🦙 doc update: llama3 (#2470)
* docs: update breaking_changes.md

* docs: update ai_endpoints.md -> llama3 for Ollama and groq

* librechat.yaml: update groq models

* Update breaking_changes.md

logs location

* Update breaking_changes.md

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2024-04-19 21:40:12 -04:00
Danny Avila
e6310c806a 🤖 fix: Minor Assistants Endpoint Fixes (#2472)
* fix(useCreateAssistantMutation): force re-render of assistants map by avoiding use shallow reference of listRes.data

* fix(AppService): regression by not including azure assistant defaults when no assistant endpoint values are set
2024-04-19 19:05:25 -04:00
Danny Avila
3d1dec62a4 🧼 refactor(AppService): Consolidate Logic & Issue more Warnings (#2468)
* chore: bump example config version

* refactor(AppService): issue warnings from separate modules where possible

* refactor(AppService): consolidate AppService logic to separate modules as much as possible

* chore: bump data-provider

* chore: remove unn. variable definition

* chore: warning wording
2024-04-19 12:05:39 -04:00
Danny Avila
de3987cbaf 🛂 refactor(openidStrategy): Use Strategy Functions for Avatars (#2467) 2024-04-19 09:12:55 -04:00
Fuegovic
f406a85633 🖥️ docs: env changes v0.6.10→v0.7.0+ (#2442)
* docs: env changes v0.6.10→v0.7.0+ | update railway referral

* docs: env changes v0.6.10→v0.7.0+
2024-04-18 08:27:52 -04:00
Danny Avila
692ce3b346 🛠️ fix: Merge Textarea Ref with Form for Simplified Handling (#2456)
* share ref correctly

* chore: remove extraneous textarea handlers and add excel text data
2024-04-18 08:19:52 -04:00
Fuegovic
26ea990045 📘 docs: update docker_compose_install.md (#2447)
* Update docker_compose_install.md

* Update docker_compose_install.md
2024-04-18 07:31:44 -04:00
Fuegovic
265abbc1c8 ⚙️ docs: Update .env.example (#2449) 2024-04-18 07:30:46 -04:00
marlonka
0b7da72be6 🌍 : Update German Translations (#2409)
* Improved and additional German translations

* Improved and added german translations and removed language translations here

* Spelling error fixed

* Use single quotes instead of double

* Use single quotes instead of double
2024-04-18 07:26:44 -04:00
Danny Avila
3c184e9410 🛠️ fix: Ensure imageOutputType is Always Defined (#2438)
* avatar fix

* chore: ensure `imageOutputType` is always defined

* ci(AppService): extra test for default value

* chore: replace default value for `desiredFormat` with `EImageOutputType` enum
2024-04-16 16:34:19 -04:00
Danny Avila
bf4e64ce63 🪙 fix(Google): Update maxOutputTokens Condition (#2434)
- generalize condition to fix max output tokens for gemini-1.5
2024-04-16 10:23:13 -04:00
Danny Avila
9d854dac07 🤖 feat: Gemini 1.5 Support (+Vertex AI) (#2383)
* WIP: gemini-1.5 support

* feat: extended vertex ai support

* fix: handle possibly undefined modelName

* fix: gpt-4-turbo-preview invalid vision model

* feat: specify `fileConfig.imageOutputType` and make PNG default image conversion type

* feat: better truncation for errors including base64 strings

* fix: gemini inlineData formatting

* feat: RAG augmented prompt for gemini-1.5

* feat: gemini-1.5 rates and token window

* chore: adjust tokens, update docs, update vision Models

* chore: add back `ChatGoogleVertexAI` for chat models via vertex ai

* refactor: ask/edit controllers to not use `unfinished` field for google endpoint

* chore: remove comment

* chore(ci): fix AppService test

* chore: remove comment

* refactor(GoogleSearch): use `GOOGLE_SEARCH_API_KEY` instead, issue warning for old variable

* chore: bump data-provider to 0.5.4

* chore: update docs

* fix: condition for gemini-1.5 using generative ai lib

* chore: update docs

* ci: add additional AppService test for `imageOutputType`

* refactor: optimize new config value `imageOutputType`

* chore: bump CONFIG_VERSION

* fix(assistants): avatar upload
2024-04-16 08:32:40 -04:00
Danny Avila
fce7246ac1 🔓 refactor: Make Image URL Security Optional (#2415) 2024-04-14 19:34:13 -04:00
Danny Avila
2cc580ba52 *️⃣ refactor(DeleteButton): Conversation List Behavior after Deletion (#2414)
* Refactor DeleteButton component in client/src/components/Conversations/DeleteButton.tsx

* chore: bump data-provider package

* chore: remove console.log
2024-04-14 19:11:55 -04:00
Danny Avila
d2d9ac0280 feat: Add 'EnterToSend' Option & Update Br. Translation 🇧🇷 (#2413)
* chore: Add EnterToSend, Translation Portuguese Brazilian Update

* Inverted selection and corrected translation

* fix: removed Trailing spaces not allowed

* feat: Refactor key event handler & updated translations

* fix: removed return; & updated files translations

* fix: duplicate switchs on General.tsx

* fix: added again switch

* refactor(useTextarea): limit refactoring of handleKeyDown

* refactor: correct keyDown handler and add English localization

---------

Co-authored-by: Raí Santos <140329135+itzraiss@users.noreply.github.com>
Co-authored-by: Raí Santos <raimorningstarchristus@gmail.com>
2024-04-14 19:06:20 -04:00
Ventz Petkov
f380f261a5 🛂 fix: OIDC Username Array Edge Case (#2394)
* Patch for OpenID username

`username` is generally based on email, rather than `given_name`. The challenge with `given_name` is that it can be a multi-value array (ex: "Nick, Fullname"), which completely breaks the system with: 

```
LibreChat      | ValidationError: User validation failed: username: Cast to string failed for value "[ 'Nickname', 'Firstname' ]" (type Array) at path "username"
LibreChat      |     at Document.invalidate (/app/node_modules/mongoose/lib/document.js:3200:32)
LibreChat      |     at model.$set (/app/node_modules/mongoose/lib/document.js:1459:12)
LibreChat      |     at model.set [as username] (/app/node_modules/mongoose/lib/helpers/document/compile.js:205:19)
LibreChat      |     at OpenIDConnectStrategy._verify (/app/api/strategies/openidStrategy.js:127:27)
LibreChat      |     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
```

* Update openidStrategy.js

* refactor(openidStrategy): add helper function for stringy username

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2024-04-12 12:39:11 -04:00
jp789
9d137ce42f 📑 docs: Add claude haiku to example anthropic models (#2391)
Co-authored-by: juliaanp <juliaan.posaratnanathan@zafin.com>
2024-04-11 15:31:04 -04:00
Melaton
25f92dd1c3 🌎 : Update chinese localization (#2384)
* 🌍 Update Chinese Translation

Update Simplified Chinese Translation

* Update Chinese Translation

Formatting file, adding new translations, delete unused translations
2024-04-11 11:23:23 -04:00
Danny Avila
9277e2a0c5 🔒 feat: Authenticated Image Requests (#2389)
* 🔒 feat: Authenticated Image Requests

* fix: reserved keyword `static`
2024-04-11 02:50:57 -04:00
Danny Avila
c19dfddd0f 👷 fix: Minor Fixes and Refactors (#2388)
* refactor(useTextarea): set Textarea disabled message due to key higher in priority

* fix(SidePanel): intended behavior for non-user provided keys

* fix: generate specs

* style: update combobox styling as before, with better dynamic height

* chore: remove unused import
2024-04-11 02:12:48 -04:00
Danny Avila
0fe47cf1f8 🤖 feat: Update Context Limit for gpt-3.5-turbo (#2381) 2024-04-10 15:10:21 -04:00
Danny Avila
8e5f1ad575 📦 feat: Model & Assistants Combobox for Side Panel (#2380)
* WIP: dynamic settings

* WIP: update tests and validations

* refactor(SidePanel): use hook for Links

* WIP: dynamic settings, slider implemented

* feat(useDebouncedInput): dynamic typing with generic

* refactor(generate): add `custom` optionType to be non-conforming to conversation schema

* feat: DynamicDropdown

* refactor(DynamicSlider): custom optionType handling and useEffect for conversation updates elsewhere

* refactor(Panel): add more test cases

* chore(DynamicSlider): note

* refactor(useDebouncedInput): import defaultDebouncedDelay from ~/common`

* WIP: implement remaining ComponentTypes

* chore: add com_sidepanel_parameters

* refactor: add langCode handling for dynamic settings

* chore(useOriginNavigate): change path to '/c/'

* refactor: explicit textarea focus on new convo, share textarea idea via ~/common

* refactor: useParameterEffects: reset if convo or preset Ids change, share and maintain statefulness in side panel

* wip: combobox

* chore: minor styling for Select components

* wip: combobox select styling for side panel

* feat: complete combobox

* refactor: model select for side panel switcher

* refactor(Combobox): add portal

* chore: comment out dynamic parameters panel for future PR and delete prompt files

* refactor(Combobox): add icon field for options, change hover bg-color, add displayValue

* fix(useNewConvo): proper textarea focus with setTimeout

* refactor(AssistantSwitcher): use Combobox

* refactor(ModelSwitcher): add textarea focus on model switch
2024-04-10 14:27:22 -04:00
matt burnett
f64a2cb0b0 🧑‍🎨 style: Remove Plugins Icon Background (#2368) 2024-04-10 07:30:56 -04:00
Walber Cardoso
e4c07eb895 👟 style: CodeAnalyze Animation (#2348)
* refactor(CodeAnaluzer SVG)

* refactor(CodeAnalyzer SVG)

* style: center terminal animation, reduce scaling

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
2024-04-09 20:34:22 -04:00
Melaton
2240fee44a 🌍: Update Chinese Translation (#2351)
Update Simplified Chinese Translation
2024-04-09 19:43:24 -04:00
Danny Avila
cb64b84846 ⬇️ refactor: Assistant File Downloads (#2364)
* refactor(getFiledownload): explicit accept of `application/octet-stream`

* chore: test compose file

* chore: test compose file fix

* chore(files/download): add more logs

* Fix proxy_pass URLs in nginx.conf

* fix: proxy_pass URLs in nginx.conf to fix file downloads from URL

* chore: move test compose file to utils dir

* refactor(useFileDownload): simplify API request by passing `file_id` instead of `filepath`
2024-04-09 14:26:46 -04:00
Danny Avila
cc71125fa1 🎨 feat: Title Improvements (#2363)
* fix(assistants): keep generated title upon continued messages in active conversation

* feat: update document.title on successful gentitle mutation
2024-04-09 14:01:27 -04:00
Danny Avila
6f0eb35365 🐞 fix: Balance and Token Usage Improvements (#2350)
* fix(processModelData): handle `openrouter/auto` edge case

* fix(Tx.create): prevent negative multiplier edge case and prevent balance from becoming negative

* fix(NavLinks): render 0 balance properly

* refactor(NavLinks): show only up to 2 decimal places for balance

* fix(OpenAIClient/titleConvo): fix cohere condition and record token usage for `this.options.titleMethod === 'completion'`
2024-04-07 23:28:40 -04:00
Danny Avila
3411d7a543 🚀 feat: Enhance Message Editing with File Resubmission (#2347)
* chore: fix type issue with File Table fakeData

* refactor: new lazy loading image strategy and load images/files as part of Message Container

* feat: resubmit files when editing messages with attached files
2024-04-07 13:25:24 -04:00
Danny Avila
caabab4489 ⚠️ docs: Default Value Warnings & Docker Docs Update (#2343)
* feat(AppService): default secret value warnings

* docs: update docker/ubuntu related guides
2024-04-06 18:20:48 -04:00
chrislbrown84
0b165260f7 📘 docs: Add Note to nginx.md (#2341)
added reference for the need to do 'sudo apt update'
2024-04-06 17:05:55 -04:00
Danny Avila
334b603247 🚧 refactor: Attempt Default Preset Fix & Other Changes (#2342)
* fix(useTextarea): trigger SendButton re-render on undo and clearing text

* refactor(PresetItems): show pin icon for default preset

* fix(ChatRoute): do not use conversation.model for useEffect, do not set default Preset if real model list is not yet fetched
2024-04-06 16:09:16 -04:00
David LaPorte
476767355b 🚅 docs(ai_endpoints): Reflect correct LiteLLM baseURL when using docker-compose (#2324)
Added note to LiteLLM baseURL to reflect docker-compose usage
2024-04-05 19:35:34 -04:00
Ventz Petkov
e80debb704 🚅 docs: Working Examples for LiteLLM, Docker, LibreChat and LiteLLM models for AWS, Azure, GCP (#2323)
Updated documentation with working config examples and clarifying many details.

Added working examples for:
* LiteLLM (litellm/litellm-config.yaml)
* Docker (docker-compose.override.yml)
* LibreChat (librechat.yaml)

Added LiteLLM "ready to use" model for:
* AWS Bedrock
* Azure OpenAI
* OpenAI
* GCP
2024-04-05 19:34:11 -04:00
Ventz Petkov
549026f677 🚦 docs: Update traefik.md - Documentation Fix for edge case race condition (#2322)
Sometimes Traefik created a race condition where LibreChat was up on tcp/3080, and while Traefik was up on tcp/443, it could not route to the LibreChat container due to the multiple interfaces -- depending on how they came up. This is easily solved by simply using one interface.
2024-04-05 19:32:51 -04:00
Danny Avila
f6a84887e1 💽 refactor(client): Optimize ModelsConfig Query Cache (#2330)
* refactor(client): remove double caching of models via recoil to rely exclusively on react-query

* chore(useConversation): add modelsQuery.data dep to callback
2024-04-05 17:08:37 -04:00
Danny Avila
fb80af05be 🧠 fix(Cohere): map to expected SDK params (#2329) 2024-04-05 16:45:18 -04:00
Danny Avila
cd7f3a51e1 🧠 feat: Cohere support as Custom Endpoint (#2328)
* chore: bump cohere-ai, fix firebase vulnerabilities by going down versions

* feat: cohere rates and context windows

* feat(createCoherePayload): transform openai payload for cohere compatibility

* feat: cohere backend support

* refactor(UnknownIcon): optimize icon render and add cohere

* docs: add cohere to Compatible AI Endpoints

* Update ai_endpoints.md
2024-04-05 15:19:41 -04:00
Paul
daa5f43ac6 📝 docs: Correct Google OAuth Callback URL Example (#2311) 2024-04-04 13:36:06 -04:00
ochen1
d0d8e47ec8 🐋 refactor(Dockerfile.multi): Optimize client build by caching npm install step (#2275)
* 🐋 fix(Dockerfile): Optimize client build by caching npm install step

* 🐋 fix(Dockerfile): Possible interference from librechat-data-provider in client build
2024-04-04 08:58:34 -04:00
Marius
09cd1a7e74 🦙 docs: Update Ollama + LiteLLM Instructions (#2302)
* Update litellm.md

* set OPENAI_API_KEY of litellm service (needs to be set if ollama's openai api compatibility is used)
2024-04-04 08:32:36 -04:00
Ventz Petkov
94950b6e8b 🚥 docs: fixed Traefik web layout (#2305)
Fixed Traefik config for broken web rending
2024-04-04 08:08:31 -04:00
Danny Avila
e418edd3dc 🔧 fix: Catch deleteVectors Errors & Update RAG API docs (#2299)
* fix(deleteVectors): handle errors gracefully

* chore: update docs based on new alternate env vars prefixed with RAG to avoid conflicts with LibreChat keys
2024-04-03 14:24:46 -04:00
Marco Beretta
e3c236ba3b 🔄 chore: converted translation files to .ts (#2288)
* chore: converted translation files to

* chore(Sv.ts): removed  and the comment

* chore: add  comment
2024-04-02 14:35:35 -04:00
Danny Avila
7bd03a6e70 🛠️ fix: Correct Unwanted Newlines after Undo in Textarea (#2289)
* docs: edit docker_override note for deploy-compose

* 🛠️  fix: Correct Unwanted Newlines after Undo in Textarea
2024-04-02 12:14:42 -04:00
happy_ryo
f146db5c59 🌍: Add new Japanese Localization entries (#2282) 2024-04-02 10:00:40 -04:00
Andi
9922baf7d1 🔗 docs: Fix Link to Docker Compose Override File (#2287) 2024-04-02 09:19:10 -04:00
Danny Avila
09da05afa1 🔨 fix(ToolService): remove userId filter from loadActionSets & Docs Update (#2286)
* fix(ToolService): remove userId filter from `loadActionSets`

* docs: updates to rag_api and docker_override explaining key variable conflicts
2024-04-02 09:11:30 -04:00
illgitthat
e66aa280c0 📝 docs: Remove Google Domains Reference (#2267) 2024-04-02 03:26:35 -04:00
Till Zoppke
ed17e17a73 📖 docs: Note on 'host.docker.internal' for Ollama Config (#2274)
* docs: update URL to access ollama and comment on 'host.docker.internal'

* Update ai_endpoints.md

---------

Co-authored-by: Danny Avila <danacordially@gmail.com>
2024-04-02 03:25:15 -04:00
Christoph Reiter
30d084e696 🐋 fix(Dockerfile): Create Necessary Directories at Build time (#2277)
When creating volumes for /app/client/public/images and /app/api/logs
docker will inherit the permissions from the existing directores in the
image. Since they are missing it defaults to root, and since
librechat now uses the "node" user instead of "root" storing images,
files and logs will fail.

Fix by creating those directories in the docker image with the node
user, so that if docker creates the volumes the permissions are inherited
and the directories are owned by "node" and not "root".
2024-04-02 03:20:41 -04:00
Zentix
93af814596 📗 docs: Update NagaAI (#2278) 2024-04-02 03:09:37 -04:00
Danny Avila
1bafe80e78 🛂 feat: Required OpenID Role (#2279)
* feat: add possibility to filter by roles for OpenID provider

---------

Co-authored-by: Sirius <siriusfrk@gmail.com>
2024-04-02 03:08:17 -04:00
Danny Avila
49753a35e5 v0.7.0 (#2273) 2024-04-01 19:24:01 -04:00
Danny Avila
1605ef3793 🐳 hotfix: Tag Images Workflow Update (#2272) 2024-04-01 19:04:37 -04:00
Danny Avila
8b3f80fe24 🐳 hotfix: Necessary Dockerfile Update (#2271)
* chore: remove version comment from pre-commit shell script

* chore: Dockerfile update
2024-04-01 18:46:12 -04:00
bsu3338
038063d4d1 🐞Fix: Stable Diffusion User Directory (#2270) 2024-04-01 15:55:44 -04:00
Danny Avila
5c8b16fbaf v0.7.0 (#2266)
*  v0.7.0

* chore: gitignore

* 🐳 ci: update release image workflows
2024-04-01 15:48:57 -04:00
Danny Avila
aff219c655 📋 fix: Ensure Textarea Resizes in Clipboard Edge Case (#2268)
* chore: ts-ignore fake conversation data used for testing

* chore(useTextarea): import helper functions to declutter hook

* fix(Textarea): reset textarea value explicitly by resetting `textAreaRef.current.value`
2024-04-01 13:40:21 -04:00
Danny Avila
d07396d308 🐞 fix: Handle Empty Model Error in Assistants Form (#2265) 2024-04-01 09:20:11 -04:00
pxz2016
cc92597f14 🐞 fix: Handle Garbled Chinese Characters in File Upload (#2261)
Co-authored-by: 彭修照 <pengxiuzhao.uh@haier.com>
2024-04-01 08:25:36 -04:00
Danny Avila
4854b39f41 🚀 feat: Add CLI Helper Scripts to API Container Image (#2257) 2024-03-31 18:59:07 -04:00
Danny Avila
bb8a40dd98 🎨 fix: Optimize StableDiffusion API Tool and Fix for Assistants Usage (#2253)
* chore: update docs

* fix(StableDiffusion): optimize API responses and file handling, return expected metadata for Assistants endpoint
2024-03-30 20:09:59 -04:00
Danny Avila
56ea0f9ae7 🐳 feat: RAG for Default Docker Compose Files + Docs Update (#2246)
* refactor(deploy-compose.yml): use long-syntax to avoid implicit folder creation of librechat.yaml

* refactor(docker-compose.override.yml.example): use long-syntax to avoid implicit folder creation of librechat.yaml

* chore: add simple health check for RAG_API_URL

* chore: improve axios error handling, adding `logAxiosError`

* chore: more informative message detailing RAG_API_URL path

* feat: add rag_api and vectordb to default compose file

* chore(rag.yml): update standalone rag compose file to use RAG_PORT

* chore: documentation updates

* docs: Update rag_api.md with images

* Update rag_api.md

* Update rag_api.md, assistants clarification

* add RAG API note to breaking changes
2024-03-29 21:15:36 -04:00
Danny Avila
6a6b2e79b0 🔧 fix: Improve Assistants File Citation & Download Handling (#2248)
* fix(processMessages): properly handle assistant file citations and add sources list

* feat: improve file download UX by making any downloaded files accessible within the app post-download

* refactor(processOpenAIImageOutput): correctly handle two different outputs for images since OpenAI generates a file in their storage, shares filepath for image rendering

* refactor: create `addFileToCache` helper to use across frontend

* refactor: add ImageFile parts to cache on processing content stream
2024-03-29 19:09:16 -04:00
Danny Avila
bc2a628902 🌍 fix(Translations): Map Partial langCode and Add Unit Tests (#2240) 2024-03-29 12:17:07 -04:00
Danny Avila
dec7879cc1 refactor(loadConfigModels): Stricter Default Model Fallback (#2239)
* chore: add TEndpoint type/typedef

* refactor(loadConfigModels.spec): stricter default model matching (fails with current impl.)

* refactor(loadConfigModels): return default models on endpoint basis and not fetch basis

* refactor: rename `uniqueKeyToNameMap` to `uniqueKeyToEndpointsMap` for clarity
2024-03-29 11:49:38 -04:00
Danny Avila
0a8118deed 🗨️ fix(useSSE): Prevent 'New Chat' Title after Regenerating Initial Message (#2238) 2024-03-29 10:56:51 -04:00
Raí Santos
59a8165379 🌍 : Updated & Added new Portuguese and Spanish Translations (#2228)
* 🌍 : Updated & Added news Portuguese and Spanish Translations

* fix: \' to "

* fix(Br.tsx): revert Snyk placeholders

* fix(Es.tsx): revert Snyk placeholders

---------

Co-authored-by: Danny Avila <danacordially@gmail.com>
2024-03-29 10:50:09 -04:00
Danny Avila
3a1d07136c refactor(loadConfigModels): Fallback to Default Models if Fetch Fails (#2236) 2024-03-29 10:43:36 -04:00
Danny Avila
a00756c469 ⬇️ feat: Assistant File Downloads (#2234)
* WIP: basic route for file downloads and file strategy for generating readablestream to pipe as res

* chore(DALLE3): add typing for OpenAI client

* chore: add `CONSOLE_JSON` notes to dotenv.md

* WIP: first pass OpenAI Assistants File Output handling

* feat: first pass assistants output file download from openai

* chore: yml vs. yaml variation to .gitignore for `librechat.yml`

* refactor(retrieveAndProcessFile): remove redundancies

* fix(syncMessages): explicit sort of apiMessages to fix message order on abort

* chore: add logs for warnings and errors, show toast on frontend

* chore: add logger where console was still being used
2024-03-29 08:23:38 -04:00
Fuegovic
7945fea0f9 ✏️ doc update: dotenv.md (#2226) 2024-03-27 16:29:40 -04:00
Ivan Dachev
84656b9812 💽 feat: Add Script for User Stats (#2224) 2024-03-27 14:41:29 -04:00
Fuegovic
b5d25f5e4f 🔎 chore: bump meilisearch v1.7 / v0.38.0 (#2175)
* 🔎 chore: bump meilisearch v1.7 / v0.38.0

* ✏️ breaking_changes.md
2024-03-27 10:08:20 -04:00
Ivan Dachev
d4b0af3dba 💽 feat: Add CONSOLE_JSON for deploying to GCP K8S env (#2146)
* Add CONSOLE_JSON

* Update example env

* Moved to utils
2024-03-27 10:07:04 -04:00
suzuki.sh
57d1f12574 🔗 docs: Fix Link to Code of Conduct (#2206)
Fix link to Code of Conduct
2024-03-26 19:29:22 -04:00
Danny Avila
182c9f7080 🔍 chore: Clean Up Documentation Pt. 4 (#2220) 2024-03-26 14:02:22 -04:00
Danny Avila
5df0ec06ea 🔍 chore: Clean Up Documentation Part 3 (#2219) 2024-03-26 13:57:25 -04:00
Danny Avila
ea54cf03e9 🔍 chore: Clean Up Documentation Part 2 (#2218) 2024-03-26 13:48:20 -04:00
Danny Avila
7f83a060a0 🔍 chore: Clean Up Documentation (#2217)
* fix(initializeClient.spec.js): remove condition failing test on local installations

* docs: remove comments and invalid html as is required by embeddings generator and add new documentation guidelines
2024-03-26 13:40:00 -04:00
Danny Avila
2259bf8b03 🚀 feat: Add GitHub Actions Workflow for Generating Docs Embeddings (#2216) 2024-03-26 11:57:04 -04:00
Danny Avila
5c3c28009f 🧹 chore: Update Docker Docs & Make cache field Optional for Custom Config (#2211)
* docs: updating docker

* fix(customConfig): make `cache` field optional as intended (though not recommended for local setups)
2024-03-26 05:45:20 -04:00
Danny Avila
f55bd3d0e9 🎨 style: Ensure Side Panel state Remains on Refresh (#2210) 2024-03-26 05:21:40 -04:00
Danny Avila
718572b7c8 🎨 style: Refine SidePanel and Textarea Styling (#2209)
* experimental: use TextareaAutosize wrapper with useLayoutEffect to hopefully fix random textarea jankiness

* fix(Textarea): force a resize when placeholder text changes

* style(ScrollToBottom): update styling for scroll button

* style: memoize values and improve side panel toggle states

* refactor(SidePanel): more control for toggle states, new hide panel button, and improve toggle state logic

* chore: hide resizable panel handle on smaller screens
2024-03-26 04:19:51 -04:00
Florian Kohrt
cb62847838 📖 docs: Add details for Azure OpenAI Assistants (#2173)
The default `.env` contains the line `ASSISTANTS_API_KEY=user_provided`. When pre-configuring Azure OpenAI models, this setting makes it impossible to use assistants due to a missing user provided key. Only by commenting the line out the Azure setup works.
2024-03-25 18:27:36 -04:00
Danny Avila
3ef46132eb 🐞 fix(client): Prevent Async Reset of Latest Message (#2203)
* refactor: use debug statement runStepCompleted message

* fix(ChatRoute): prevent use of `newConversation` from reseting `latestMessage`, which would fire asynchronously and finalize after `latestMessage` was already correctly set
2024-03-25 11:16:18 -04:00
Danny Avila
8fc52348e8 🌟 fix: Handle Assistants Edge Cases, Improve Filter Styling (#2201)
* fix(assistants): default query to limit of 100 and `desc` order

* refactor(useMultiSearch): use object as params and fix styling for assistants

* feat: informative message for thread initialization failing due to long message
2024-03-25 08:55:33 -04:00
Fuegovic
a4f4ec85f8 🧑‍💻docs: Update General Docs and Contribution Guidelines (#2194)
* doc upddate: documentation_guidelines.md

* doc upddate: how_to_contribute.md

* doc upddate: testing.md / how_to_contribute.md

* doc upddate: translation_contribution.md/testing.md/how_to_contribute.md

* doc upddate: coding_conventions.md

* fix formatting: how_to_contribute.md

* fix formatting (again) : how_to_contribute.md
2024-03-25 07:26:43 -04:00
Danny Avila
f86d80de59 🔧 fix(assistants): Vision minor fix & Add Docs (#2196)
* 👓 fix(assistants): Only Retrieve Assistant Data for Vision Requests if attachments exist in Host Storage

* docs: add  capability
2024-03-25 00:02:54 -04:00
Danny Avila
798e8763d0 👓 feat: Vision Support for Assistants (#2195)
* refactor(assistants/chat): use promises to speed up initialization, initialize shared variables, include `attachedFileIds` to streamRunManager

* chore: additional typedefs

* fix(OpenAIClient): handle edge case where attachments promise is resolved

* feat: createVisionPrompt

* feat: Vision Support for Assistants
2024-03-24 23:43:00 -04:00
Danny Avila
1f0fb497f8 🎉 feat: Optimizations and Anthropic Title Generation (#2184)
* feat: add claude-3-haiku-20240307 to default anthropic list

* refactor: optimize `saveMessage` calls mid-stream via throttling

* chore: remove addMetadata operations and consolidate in BaseClient

* fix(listAssistantsForAzure): attempt to specify correct model mapping as accurately as possible (#2177)

* refactor(client): update last conversation setup with current assistant model, call newConvo again when assistants load to allow fast initial load and ensure assistant model is always the default, not the last selected model

* refactor(cache): explicitly add TTL of 2 minutes when setting titleCache and add default TTL of 10 minutes to abortKeys cache

* feat(AnthropicClient): conversation titling using Anthropic Function Calling

* chore: remove extraneous token usage logging

* fix(convos): unhandled edge case for conversation grouping (undefined conversation)

* style: Improved style of Search Bar after recent UI update

* chore: remove unused code, content part helpers

* feat: always show code option
2024-03-23 20:21:40 -04:00
Florian Kohrt
8e7816468d 📚 docs: Fix Broken Links (#2171)
Fix broken links to the custom config file on `timeoutMs` and `supportedIds`.
2024-03-23 11:05:52 -04:00
Danny Avila
45a95acec2 📂 feat: RAG Improvements (#2169)
* feat: new vector file processing strategy

* chore: remove unused client files

* chore: remove more unused client files

* chore: remove more unused client files and move used to new dir

* chore(DataIcon): add className

* WIP: Model Endpoint Settings Update, draft additional context settings

* feat: improve parsing for augmented prompt, add full context option

* chore: remove volume mounting from rag.yml as no longer necessary
2024-03-22 19:07:08 -04:00
Danny Avila
f427ad792a 🚀 feat: Assistants Streaming (#2159)
* chore: bump openai to 4.29.0 and npm audit fix

* chore: remove unnecessary stream field from ContentData

* feat: new enum and types for AssistantStreamEvent

* refactor(AssistantService): remove stream field and add conversationId to text ContentData
> - return `finalMessage` and `text` on run completion
> - move `processMessages` to services/Threads to avoid circular dependencies with new stream handling
> - refactor(processMessages/retrieveAndProcessFile): add new `client` field to differentiate new RunClient type

* WIP: new assistants stream handling

* chore: stores messages to StreamRunManager

* chore: add additional typedefs

* fix: pass req and openai to StreamRunManager

* fix(AssistantService): pass openai as client to `retrieveAndProcessFile`

* WIP: streaming tool i/o, handle in_progress and completed run steps

* feat(assistants): process required actions with streaming enabled

* chore: condense early return check for useSSE useEffect

* chore: remove unnecessary comments and only handle completed tool calls when not function

* feat: add TTL for assistants run abort cacheKey

* feat: abort stream runs

* fix(assistants): render streaming cursor

* fix(assistants): hide edit icon as functionality is not supported

* fix(textArea): handle pasting edge cases; first, when onChange events wouldn't fire; second, when textarea wouldn't resize

* chore: memoize Conversations

* chore(useTextarea): reverse args order

* fix: load default capabilities when an azure is configured to support assistants, but `assistants` endpoint is not configured

* fix(AssistantSelect): update form assistant model on assistant form select

* fix(actions): handle azure strict validation for function names to fix crud for actions

* chore: remove content data debug log as it fires in rapid succession

* feat: improve UX for assistant errors mid-request

* feat: add tool call localizations and replace any domain separators from azure action names

* refactor(chat): error out tool calls without outputs during handleError

* fix(ToolService): handle domain separators allowing Azure use of actions

* refactor(StreamRunManager): types and throw Error if tool submission fails
2024-03-21 22:42:25 -04:00
Hermes Trismegistus
ed64c76053 📖 docs: Update ShuttleAI Fibonacci Image (#2160) 2024-03-21 22:41:58 -04:00
Danny Avila
25a0487ce5 chore: Revise of PR #2157, move global steps earlier, execute as root 2024-03-21 12:33:30 -04:00
Danny Avila
3f77fe18b7 🐋 chore: Revise of PR #2157, move step earlier 2024-03-21 12:28:40 -04:00
Danny Avila
09de9a2b42 🐋 fix(Dockerfile): add back additional deps., handle permissions, use --no-audit flag on install (#2157) 2024-03-21 12:24:40 -04:00
Danny Avila
a673f62831 🐋 chore: Cleanup Dockerfile (#2156) 2024-03-21 11:18:53 -04:00
Danny Avila
e0dd0381b2 🌑 style(File Manager): Localize and Update Dark Mode Stylings (#2155)
* 🌑 style: Update Dark Mode Stylings for File Manager

* 🌐 feat: localize file manager text

* 🌐 feat: file panel table localization
2024-03-21 10:52:45 -04:00
Hermes Trismegistus
1ee2c32a67 🚀 feat: Add ShuttleAI as Known Endpoint (#2152)
Added new Official Known Endpoint (ShuttleAI)
2024-03-21 09:17:57 -04:00
Flynn
f521040784 🔧 fix(menu): Menu Item Filter Improvements (#2153)
* small-fix: Ensure that fake seperators in model lists do not show in search

* Ensure Plugin search uses correct placeholder and key filtering in search
2024-03-21 09:15:25 -04:00
Marco Beretta
30f6d90cfe 🖌️ style: Improve Dark Theme Accessibility (#2125)
* style: all landing page components

* chore: converted all slate to gray, since slate doesnt work

* style: assistant panel

* style: basic UI components, userprovided, preset

* style: update in multiple components

* fix(PluginStoreDialog): justify-center

* fixed some minor Ui styles

* style(MultiSearch): update dark bg

* style: update Convo styling

* style: lower textarea max height slightly

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-03-21 09:02:00 -04:00
Walber Cardoso
e95c0aaaed 🔧 style(fix): Convo Fade Effect (#2147)
* 🔧 (fix) Convo Fade Effect

* 🔧style(fix): Convo Fade Effect (#2117)

* 🔧 style(fix): Convo Fade Effect (#2117)
2024-03-21 08:39:43 -04:00
Danny Avila
9bab595204 🔬 chore: Add Circular Dependency Check to backend-review (#2149)
* 🔬 chore: Add Circular Dependency check to `backend-review`

* chore: touch random file for workflow trigger

* chore: workflow step order

* chore: update workflow to create empty auth.json file

* fix: attempt empty auth.json creation

* chore: add test_bundle ESLint ignore pattern
2024-03-20 12:15:42 -04:00
Danny Avila
4f17d97eb2 fix(sendEmail): circular dependency 2024-03-20 11:52:05 -04:00
Danny Avila
e4ac58012f 📧 fix: Correct Handling of Self-Signed Certificates in sendEmail (#2148)
- note: To put it in a different way, if you put rejectUnauthorized: true, it means that self-signed certificates should not be allowed. This means, that EMAIL_ALLOW_SELFSIGNED is set to false
2024-03-20 11:48:54 -04:00
Danny Avila
f7761df52c 🗃️ feat: General File Support for OpenAI, Azure, Custom, Anthropic and Google (RAG) (#2143)
* refactor: re-purpose `resendImages` as `resendFiles`

* refactor: re-purpose `resendImages` as `resendFiles`

* feat: upload general files

* feat: embed file during upload

* feat: delete file embeddings on file deletion

* chore(fileConfig): add epub+zip type

* feat(encodeAndFormat): handle non-image files

* feat(createContextHandlers): build context prompt from file attachments and successful RAG

* fix: prevent non-temp files as well as embedded files to be deleted on new conversation

* fix: remove temp_file_id on usage, prevent non-temp files as well as embedded files to be deleted on new conversation

* fix: prevent non-temp files as well as embedded files to be deleted on new conversation

* feat(OpenAI/Anthropic/Google): basic RAG support

* fix: delete `resendFiles` only when true (Default)

* refactor(RAG): update endpoints and pass JWT

* fix(resendFiles): default values

* fix(context/processFile): query unique ids only

* feat: rag-api.yaml

* feat: file upload improved ux for longer uploads

* chore: await embed call and catch embedding errors

* refactor: store augmentedPrompt in Client

* refactor(processFileUpload): throw error if not assistant file upload

* fix(useFileHandling): handle markdown empty mimetype issue

* chore: necessary compose file changes
2024-03-19 20:54:30 -04:00
SailFlorve
af347cccde 🎨 style: HoverButton UI adjustment, change code font (#2017)
* style: HoverButton UI adjustment

* style: make Consolas as default code font

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-03-19 13:55:41 -04:00
Danny Avila
86db0a1043 Revert "🔧 style(fix): Convo Title Fade Effect (#2117)" (#2139)
This reverts commit 1796821888.
2024-03-19 13:54:35 -04:00
Walber Cardoso
1796821888 🔧 style(fix): Convo Title Fade Effect (#2117)
* feat: Improve Google search plugin to assistants

* 🔧 fix(Nav SidePanel): Center buttons when collapsed

* 🔧(fix) Convo title fade effect

* 🔧(fix) Convo title fade effect / remove deletion

* 🔧(fix) Convo title fade effect / remove deletion .env.example

* 🔧(fix) Convo title fade effect

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-03-19 13:43:24 -04:00
Ido Ophir
d8304ec1bb 📋 chore: add requirements.txt to documentation (#2122)
* chore: add requirements.txt to documentation, to ease maintenance

* docs: Update documentation_guidelines.md
2024-03-19 13:38:18 -04:00
Danny Avila
382b303963 🔍 feat: Filter MultiSelect and SelectDropDown (+variants) + CSS fixes for Scrollbar (#2138)
* Initial implementation of MultiSearch. Added implementation to MultiSelect and SelectDropDown and variants

* Update scrollbar styles to prevent breakages on Chrome

* Revert changes to vite.config.ts (redundant for now)

* chore(New Chat): organize imports

* style(scrollbar-transparent): use webkit as standard, expected behavior

* chore: useCallback for mouse enter/leave

* fix(Footer): resolve map key error

* chore: memoize Conversations

* style(MultiSearch): improve multisearch styling

* style: dark mode search input

* fix: react warnings due to unrecognize html props

* chore: debounce OpenAI settings inputs

* fix(useDebouncedInput): only use event value as newValue if not object

---------

Co-authored-by: Flynn <gpg@flyn.ca>
2024-03-19 13:35:10 -04:00
Danny Avila
f51ac74e12 🪰 fix: Azure Parsing and Assistants Payload (#2133)
* fix(azure): fix regex to prevent edge cases

* fix(assistants): pass relevant endpoint options to avoid sending them to API
2024-03-18 19:48:42 -04:00
Danny Avila
7cddd943d0 🔧 feat(actions): Allow Multiple Actions from Same Domain per Assistant (#2120) 2024-03-16 19:40:51 -04:00
Danny Avila
89f6b35e6c 🔧 fix: Remove Unique Index from Actions Model and Initialize Empty Actions for Deletion (#2118) 2024-03-16 18:53:43 -04:00
Danny Avila
a8cdd3460c 🔧 feat: Share Assistant Actions between Users (#2116)
* fix: remove unique field from assistant_id, which can be shared between different users

* refactor: remove unique user fields from actions/assistant queries

* feat: only allow user who saved action to delete it

* refactor: allow deletions for anyone with builder access

* refactor: update user.id when updating assistants/actions records, instead of searching with it

* fix: stringify response data in case it's an object

* fix: correctly handle path input

* fix(decryptV2): handle edge case where value is already decrypted
2024-03-16 16:49:11 -04:00
Marco Beretta
2f90c8764a 🖊️ fix(MessageContent): Error Message typo (#2112) 2024-03-16 13:05:56 -04:00
Fuegovic
39042f8761 🎨 style: Privacy Policy & Terms of Service (#2111) 2024-03-16 13:05:18 -04:00
Danny Avila
a9d2d3fe40 🪙 feat: Assistants Token Balance & other improvements (#2114)
* chore: add assistants to supportsBalanceCheck

* feat(Transaction): getTransactions and refactor export of model

* refactor: use enum: ViolationTypes.TOKEN_BALANCE

* feat(assistants): check balance

* refactor(assistants): only add promptBuffer if new convo (for title), and remove endpoint definition

* refactor(assistants): Count tokens up to the current context window

* fix(Switcher): make Select list explicitly controlled

* feat(assistants): use assistant's default model when no model is specified instead of the last selected assistant, prevent assistant_id from being recorded in non-assistant endpoints

* chore(assistants/chat): import order

* chore: bump librechat-data-provider due to changes
2024-03-15 19:48:42 -04:00
SailFlorve
f848d752e0 🌍 : Update Chinese Translations (#2098) 2024-03-15 16:12:02 -04:00
Fuegovic
8881346889 📑 docs: update .env.example (#2109) 2024-03-15 16:11:31 -04:00
Danny Avila
f769077ab4 🤖 fix(assistants): Default Capabilities and Retrieval Models (#2102) 2024-03-14 20:42:56 -04:00
Danny Avila
5cd5c3bef8 🅰️ feat: Azure OpenAI Assistants API Support (#1992)
* chore: rename dir from `assistant` to plural

* feat: `assistants` field for azure config, spread options in AppService

* refactor: rename constructAzureURL param for azure as `azureOptions`

* chore: bump openai and bun

* chore(loadDefaultModels): change naming of assistant -> assistants

* feat: load azure settings with currect baseURL for assistants' initializeClient

* refactor: add `assistants` flags to groups and model configs, add mapGroupToAzureConfig

* feat(loadConfigEndpoints): initialize assistants endpoint if azure flag `assistants` is enabled

* feat(AppService): determine assistant models on startup, throw Error if none

* refactor(useDeleteAssistantMutation): send model along with assistant id for delete mutations

* feat: support listing and deleting assistants with azure

* feat: add model query to assistant avatar upload

* feat: add azure support for retrieveRun method

* refactor: update OpenAIClient initialization

* chore: update README

* fix(ci): tests passing

* refactor(uploadOpenAIFile): improve logging and use more efficient REST API method

* refactor(useFileHandling): add model to metadata to target Azure region compatible with current model

* chore(files): add azure naming pattern for valid file id recognition

* fix(assistants): initialize openai with first available assistant model if none provided

* refactor(uploadOpenAIFile): add content type for azure, initialize formdata before azure options

* refactor(sleep): move sleep function out of Runs and into `~/server/utils`

* fix(azureOpenAI/assistants): make sure to only overwrite models with assistant models if `assistants` flag is enabled

* refactor(uploadOpenAIFile): revert to old method

* chore(uploadOpenAIFile): use enum for file purpose

* docs: azureOpenAI update guide with more info, examples

* feat: enable/disable assistant capabilities and specify retrieval models

* refactor: optional chain conditional statement in loadConfigModels.js

* docs: add assistants examples

* chore: update librechat.example.yaml

* docs(azure): update note of file upload behavior in Azure OpenAI Assistants

* chore: update docs and add descriptive message about assistant errors

* fix: prevent message submission with invalid assistant or if files loading

* style: update Landing icon & text when assistant is not selected

* chore: bump librechat-data-provider to 0.4.8

* fix(assistants/azure): assign req.body.model for proper azure init to abort runs
2024-03-14 17:21:42 -04:00
Flynn
1b243c6f8c 📜 feat: Customize Privacy Policy & Terms of Service (#2091) 2024-03-14 16:43:18 -04:00
Alexei Smirnov
d4190c9320 🌍 : Update Russian Translation (#2061)
* feat(chore): add missing translations in Ru.tsx

* feat(chore): add missing translation for My Files menu and headers

* change com_ui_my_files to com_ui_nav_files

* move useLocalize above utils

* feat(chore): add missing translation for My Files menu and headers
2024-03-14 11:26:44 -04:00
MACHINSOFT
cba135d456 style: Auth Error and Preset Items Styling (#2069)
* Change the style of the error message.

* ui preset items

* fix style

* Change the color of the border and adjust the background of the selected input
2024-03-14 09:07:55 -04:00
Raí Santos
f27e7c720f 🔧 fix: Convo Corners & Updated Colors (#2046)
* 🔧 fix: Convo Corners & Updated Colors

* refactored code

* chore: JSON.parse with a try/catch block, removed useless useEffect & and restored Focus

* restored typescript

* import all back
2024-03-14 09:04:09 -04:00
Danny Avila
1b8c0f0bfd chore: Update AnthropicIcon.tsx 2024-03-13 19:00:22 -04:00
Vilmondes Queiroz
0f417aaec0 🧹 chore: remove unused import (#2072) 2024-03-11 18:27:29 -04:00
Danny Avila
d1c37e8bde 🧊 style: Adjust Endpoint Icons (#2070)
* 🧊 style: Adjust Endpoint Icons

* Update MessageParts.tsx
2024-03-11 13:40:31 -04:00
Danny Avila
0bd8c2ba00 🌑 style(AnthropicIcon): adjust for Dark Mode 2024-03-11 11:36:54 -04:00
Danny Avila
ebcca16b94 🌐 feat: librechat.yaml from URL (#2064)
* feat: librechat.yaml from URL

* doc update: librechat.yaml from URL

* update dotenv.md - typo

* Update loadCustomConfig.js

* ci: specs for loadCustomConfig

* fix(processFileURL): safe destructuring of saveURL result

---------

Co-authored-by: fuegovic <fueg@live.ca>
Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
2024-03-11 10:52:54 -04:00
MACHINSOFT
f5a754c8be 🖌️ style: Minor UI Updates (#2011)
* UI Design update

* Add an error icon next to the avatar.

* fix

* Change the style of buttons

* fix: avatar
2024-03-11 10:31:32 -04:00
Walber Cardoso
2e77813952 🔧 style(SidePanel): Center buttons when collapsed (#2045)
* feat: Improve Google search plugin to assistants

* 🔧 fix(Nav SidePanel): Center buttons when collapsed
2024-03-11 09:24:38 -04:00
Danny Avila
f307488dd4 ✍️ refactor(Textarea): Optimize Text Input & Enhance UX (#2058)
* refactor(useDebouncedInput): make object as input arg and accept setter

* refactor(ChatForm/Textarea): consolidate textarea/form logic to one component, use react-hook-form, programmatically click send button instead of passing submitMessage, forwardRef and memoize SendButton

* refactor(Textarea): use Controller field value to avoid manual update of ref

* chore: remove forms provider

* chore: memoize AttachFile

* refactor(ChatForm/SendButton): only re-render SendButton when there is text input

* chore: make iconURL bigger

* chore: optimize Root/Nav

* refactor(SendButton): memoize disabled prop based on text

* chore: memoize Nav and ChatForm

* chore: remove textarea ref text on submission

* feat(EditMessage): Make Esc exit the edit mode and dismiss changes when editing a message

* style(MenuItem): Display the ☑️  icon only on the selected model
2024-03-11 09:18:10 -04:00
Fuegovic
f489aee518 📧 update email templates (#2057)
* 📧 chore: update email templates

* 📧 update password reset confirmation
2024-03-11 09:07:09 -04:00
Fuegovic
2f88c5cb8a ✏️ docs: Railway, Traefik, and Improvements (#2060)
* docs: documentation guidelines

* docs: deploy documentation update
2024-03-11 09:06:27 -04:00
Marco Beretta
6fcaeaafe2 🔧 fix(ThemeContext): Listen for Theme Changes (#2037)
* fix(ThemeContext): listen for changes

* fix(Dropdown): theme auto-update not working
2024-03-09 11:36:04 -05:00
Fuegovic
db870e55c3 🔖 chore: update groq models (#2031) 2024-03-09 08:32:08 -05:00
Fuegovic
5d0d02f5f7 🖊️chore: fix deployment guides (#2021) 2024-03-08 08:52:26 -05:00
Danny Avila
40e884b3ec 🖼️ fix: Clipboard Files & File Name Issues (#2015)
* fix: ensure image handling fetchs image to base64 for multiple images

* fix: append file_id's when writing uploaded files

* feat: timestamp files uploaded from clipboard

* chore: add a different fileid+name separator
2024-03-07 12:27:42 -05:00
Danny Avila
18edd2660b 👥 fix(assistants): Improve Error handling (#2012)
* feat: make assistants endpoint appendable since message state is not managed by LibreChat

* fix(ask): search currentMessages for thread_id if it's not defined

* refactor(abortMiddleware): remove use of `overrideProps` and spread unknown fields instead

* chore: remove console.log in `abortConversation`

* refactor(assistants): improve error handling/cancellation flow
2024-03-07 10:50:01 -05:00
Walber Cardoso
d4fe8fc82d 🔍 feat: Add Google Search Tool for Assistants (#1994) 2024-03-07 10:49:48 -05:00
Ido Ophir
a5f4292d2d 🌊 docs: refactor DigitalOcean guide (#2006) 2024-03-07 08:12:39 -05:00
Fuegovic
fbdf1d17ea 💾 chore: Update .env.example (#2004)
* Update .env.example

Make assistants show in the UI by default

* Update dotenv.md
2024-03-07 08:11:32 -05:00
Ido Ophir
11bca134e7 📝 docs: additions to deployment guide (#2001)
* docs: add intro to deployment guide

* doc: update intro

* doc: Add NGINX deployment guide and update reverse proxy link

* doc:: add  reverse proxy pages and weight for the pages

* doc: Update NGINX configuration file

* doc: imporve new doc

* Doc: fix file names

* doc: fix references names + improve the introduction with chatgpt :-)

* doc: update introduction  guide headings
2024-03-07 08:10:44 -05:00
Danny Avila
ab66747e97 🔧 style: Improve UI and UX with Style Fixes and Code Refactors (#2002)
* refactor(useSSE): add useCallback to all event handlers

* chore: remove modelName in defaultAssistantFormValues

* fix(SidePanel): fix layout shift on chrome my removing sidenav scrollbar

* style(ChatForm): match ChatGPT textarea effect styling

* style: fix flickering of old background color on refresh
2024-03-06 17:49:53 -05:00
Marco Beretta
b2ab6fd19d 🖌️ style: update dialog position (#1999)
* style(ChatForm): update styling and fixed style bug

* style:(Dialog): reduced max height  style(Settings): fixed dialog position height

* style(Settings): fixed large screen  position
2024-03-06 17:03:23 -05:00
Fuegovic
ab263c7a50 📝 docs update: Anthropic models + Traversaal (#1995)
* 📝 docs update: Anthropic models + Traversaal

* 📝 docs update: Anthropic models
2024-03-06 16:52:42 -05:00
Marco Beretta
911babd3e0 🖌️ style: Update Light/Dark UI Themes (#1754)
* BIG UI UPDATE

* fix: search bar, dialog template, new chat icon, convo icon and delete/rename button

* moved some color config and a lot of files

* small text fixes and tailwind config refactor

* Update localization and UI styles

* Update styles and add user-select:none to Tooltip component

* Update mobile.css styles for navigation mask and background color

* Update component imports and styles

* Update DeleteButton imports and references

* Update UI components

* Update tooltip delay duration

* Fix styling and update text in various components

* fixed assistant style

* minor style fixes

* revert: removed CreationHeader & CreationPanel

* style: match new styling for SidePanel

* style: match bg-gray-800 to ChatGPT (#212121)

* style: remove slate for gray where applicable to match new light theme

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-03-06 12:05:43 -05:00
Danny Avila
2733c5ebe7 🔎 fix(Traversaal): Recognize authField during Tool Initialization 2024-03-06 10:59:00 -05:00
Danny Avila
959d6153f6 🔎 feat: Traversaal Search Tool (#1991)
* wip: Traversaal Search Tool

* fix(traversaal): properly handle tool error, show error to LLM, log

* feat(traversaal): finish implementation of structured tool

* chore: change traversaal order
2024-03-06 10:25:38 -05:00
Danny Avila
14dd3dd240 🖋️ fix(OpenAIClient): remove typo 2024-03-06 09:19:52 -05:00
Danny Avila
8263ddda3f 🤖 feat(Anthropic): Claude 3 & Vision Support (#1984)
* chore: bump anthropic SDK

* chore: update anthropic config settings (fileSupport, default models)

* feat: anthropic multi modal formatting

* refactor: update vision models and use endpoint specific max long side resizing

* feat(anthropic): multimodal messages, retry logic, and messages payload

* chore: add more safety to trimming content due to whitespace error for assistant messages

* feat(anthropic): token accounting and resending multiple images in progress

* chore: bump data-provider

* feat(anthropic): resendImages feature

* chore: optimize Edit/Ask controllers, switch model back to req model

* fix: false positive of invalid model

* refactor(validateVisionModel): use object as arg, pass in additional/available models

* refactor(validateModel): use helper function, `getModelsConfig`

* feat: add modelsConfig to endpointOption so it gets passed to all clients, use for properly validating vision models

* refactor: initialize default vision model and make sure it's available before assigning it

* refactor(useSSE): avoid resetting model if user selected a new model between request and response

* feat: show rate in transaction logging

* fix: return tokenCountMap regardless of payload shape
2024-03-06 00:04:52 -05:00
Danny Avila
b023c5683d 🛠️ refactor(loadConfigModels): make apiKey and baseURL pairings more versatile (#1985) 2024-03-05 15:42:19 -05:00
Fuegovic
a33db54b81 🔎 update meilisearch to v1.6 / 0.37.0 (#1981)
* 🔎 update meilisearch to v1.6 / 0.37.0

* 🔎 update meilisearch to v1.6 / 0.37.0
2024-03-05 14:36:01 -05:00
Danny Avila
7a6a41a72e 🧪 fix(ci): update failing initializeClient tests with new expected values (#1982)
* fix(ci): update failing tests with new expected values from `getUserKey`

* refactor: safer optional chaining, and ensure apiKey is defined
2024-03-05 14:33:45 -05:00
Fuegovic
2ea6e8c18a 🥷🪦 docs: remove ninja and chatgptBrowser (#1973) 2024-03-04 19:49:34 -05:00
Ido Ophir
7c85b35af0 🌍 : Add Hebrew Translation (#1953)
* feat: add hebrew

* fix: review issues

* fix language options
2024-03-04 17:16:49 -05:00
Fuegovic
eccf7bbbde 🦙 doc: add Ollama to index and update icon (#1967) 2024-03-04 17:16:33 -05:00
Danny Avila
8bef084bfc 🧩 fix(Plugins): Keep User agentModel and Model Validation (#1972)
* fix: do not override model

* temp fix for secondary model validation
2024-03-04 17:07:30 -05:00
Danny Avila
62834e18fb 🪙 fix(config): use new field for balance 2024-03-04 16:37:06 -05:00
Marco Beretta
2da0a7661d 🔧 fix(EditMessage): duplicate text when pasting (#1970)
* fix(EditMessage): duplicate text when pasting on chromium

* add back paste data handling, prevent default behavior

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-03-04 16:27:34 -05:00
Marco Beretta
7d633f4018 🔧 fix(useTextarea): duplicate text when pasting on chromium (#1951) 2024-03-02 15:53:13 -05:00
bsu3338
78f52859c4 📚 docs: Separate LiteLLM and Ollama Documentation (#1948)
* Separate LiteLLM and Ollama Documentation

* Clarify Ollama Setup

* Fix litellm config
2024-03-02 12:42:02 -05:00
Danny Avila
b2ef75e009 🖥️ feat: Match STDOUT Logs with Debug File Logs (#1944)
* chore: improve token balance logging post-request

* feat: match stdout logging with file debug logging when using DEBUG_CONSOLE
2024-03-01 13:42:04 -05:00
Danny Avila
ef86b25dae 👤 feat: Show Default Icon if No Avatar or Username provided (#1943) 2024-03-01 13:08:27 -05:00
Danny Avila
c52ea9490b 📝 feat: Improved Textarea Functionality (#1942)
* feat: paste plain text from apps with rich paste data, improved edit message textarea, improved height resizing for long text

* feat(EditMessage): autofocus

* chore: retain user text color when entering edit mode
2024-03-01 12:46:15 -05:00
Fuegovic
de0cee3f56 🔎docs: update meilisearch instruction (#1930)
* 🔎docs: update meilisearch in mac_install.md

Update the Meilisearch .env variables in `mac_install.md`

* 🔎🐧
2024-03-01 12:32:32 -05:00
Danny Avila
1caa31b035 🐳chore(Dockerfile): add additional steps to prevent arm64 build failure 2024-02-29 10:04:36 -05:00
Danny Avila
ed7d7c2fda 🐳 chore(Dockerfile): replace npm ci with npm install for OS specific builds 2024-02-29 09:46:33 -05:00
Danny Avila
93803323cf 🐳 experimental: Dev Image Workflow & Remove Unused Code (#1928)
* chore: remove unused code in progressCallback, as well as handle reply.trim(), post `getCompletion`

* chore(Dockerfile): remove curl installation

* experimental: dev image parallelized with matrix strategy and building for amd64/arm64 support

* make platforms explicit
2024-02-29 09:24:55 -05:00
Danny Avila
388dc1789b 🛠️ fix: RunManager, AssistantService and useContentHandler Issues (#1920)
* fix(useContentHandler): retain undefined parts and handle them within `ContentParts` rendering

* fix(AssistantService/in_progress): skip empty messages

* refactor(RunManager): create highly specific `seenSteps` Set keys for RunSteps with use of `getDetailsSignature` and `getToolCallSignature`,to ensure changes from polling are always captured
2024-02-28 15:15:45 -05:00
Fuegovic
057fcf6274 🌍 feat: Extend regex to support international usernames (#1918)
* 🌍 Extend regex to support international usernames

* update validators.spec.js
2024-02-28 14:27:57 -05:00
Danny Avila
2f92b54787 🔗 feat: User Provided Base URL for OpenAI endpoints (#1919)
* chore: bump browserslist-db@latest

* refactor(EndpointService): simplify with `generateConfig`, utilize optional baseURL for OpenAI-based endpoints, use `isUserProvided` helper fn wherever needed

* refactor(custom/initializeClient): use standardized naming for common variables

* feat: user provided baseURL for openAI-based endpoints

* refactor(custom/initializeClient): re-order operations

* fix: knownendpoints enum definition and add FetchTokenConfig, bump data-provider

* refactor(custom): use tokenKey dependent on userProvided conditions for caching and fetching endpointTokenConfig, anticipate token rates from custom config

* refactor(custom): assure endpointTokenConfig is only accessed from cache if qualifies for fetching

* fix(ci): update tests for initializeClient based on userProvideURL changes

* fix(EndpointService): correct baseURL env var for assistants: `ASSISTANTS_BASE_URL`

* fix: unnecessary run cancellation on res.close() when response.run is completed

* feat(assistants): user provided URL option

* ci: update tests and add test for `assistants` endpoint

* chore: leaner condition for request closing

* chore: more descriptive error message to provide keys again
2024-02-28 14:27:19 -05:00
Fuegovic
53ae2d7bfb 🤖feat: add multiple known endpoints (#1917)
* feat: add known endpoints

* docs: add known endpoints

* update ai_endpoints.md

remove the groq icon from the example

* Update ai_endpoints.md

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-02-28 08:46:21 -05:00
Marco Beretta
156abe2fca 🔗 feat: NavLinks customization for Help & Faq URL (#1872)
* help and faq

* fix: using only one var

* revert(types.ts): showHelpAndFaq

* Update dotenv.md

* Update dotenv.md
2024-02-27 17:59:56 -05:00
Danny Avila
c37d5568bf 🍞 fix: Minor fixes and improved Bun support (#1916)
* fix(bun): fix bun compatibility to allow gzip header: https://github.com/oven-sh/bun/issues/267#issuecomment-1854460357

* chore: update custom config examples

* fix(OpenAIClient.chatCompletion): remove redundant call of stream.controller.abort() as `break` aborts the request and prevents abort errors when not called redundantly

* chore: bump bun.lockb

* fix: remove result-thinking class when message is no longer streaming

* fix(bun): improve Bun support by forcing use of old method in bun env, also update old methods with new customizable params

* fix(ci): pass tests
2024-02-27 17:51:16 -05:00
Danny Avila
5d887492ea 🤖 docs: Add Groq and other Compatible AI Endpoints (#1915)
* chore: bump bun dependencies

* feat: make `groq` a known endpoint

* docs: compatible ai endpoints

* Update ai_endpoints.md

* Update ai_endpoints.md
2024-02-27 13:42:10 -05:00
Danny Avila
04eeb59d47 🛠️ chore: Abort AI Requests on Close & Remove Verbose Logs for Plugins (#1914)
* chore: remove verbose logging of ChatOpenAI

* feat: abort AI requests on request close
2024-02-27 10:21:06 -05:00
Danny Avila
08d4b3cc8a 🅰️ feat: Azure AI Studio, Models as a Service Support (#1902)
* feat(data-provider): add Azure serverless inference handling through librechat.yaml

* feat(azureOpenAI): serverless inference handling in api

* docs: update docs with new azureOpenAI endpoint config fields and serverless inference endpoint setup

* chore: remove unnecessary checks for apiKey as schema would not allow apiKey to be undefined

* ci(azureOpenAI): update tests for serverless configurations
2024-02-26 19:10:29 -05:00
Raí Santos
6d6b3c9c1d 🌍 : Update Portuguese Translations (#1867)
* 🌍 : Update Portuguese Translations

* 🌍 : Fix Portuguese Translations

* fix(Br): lint errors

---------

Co-authored-by: Berry-13 <81851188+Berry-13@users.noreply.github.com>
2024-02-26 14:37:08 -05:00
Danny Avila
49744d1af9 🔥chore: bump firebase dependency (#1900) 2024-02-26 14:36:48 -05:00
Marco Beretta
b4dc8cc2ad 🖌️ style: auth dark theme (#1862)
* Remove minLength validation and update login link style

* Add theme selector component and update login form styles

* Update styling in Login and LoginForm components

* Update ResetPassword component styles and text color

* Refactor login component and add theme selector

* Add ThemeSelector component to Registration, RequestPasswordReset, and ResetPassword pages

* chore(Login.tsx): remove unused `useCallback`

* chore(Login.tsx) import order

* Update ResetPassword.tsx import order

* Update RequestPasswordReset.tsx import order

* Update Registration.tsx import order

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-02-26 14:21:17 -05:00
Danny Avila
097a978e5b 🅰️ feat: Azure Config to Allow Different Deployments per Model (#1863)
* wip: first pass for azure endpoint schema

* refactor: azure config to return groupMap and modelConfigMap

* wip: naming and schema changes

* refactor(errorsToString): move to data-provider

* feat: rename to azureGroups, add additional tests, tests all expected outcomes, return errors

* feat(AppService): load Azure groups

* refactor(azure): use imported types, write `mapModelToAzureConfig`

* refactor: move `extractEnvVariable` to data-provider

* refactor(validateAzureGroups): throw on duplicate groups or models; feat(mapModelToAzureConfig): throw if env vars not present, add tests

* refactor(AppService): ensure each model is properly configured on startup

* refactor: deprecate azureOpenAI environment variables in favor of librechat.yaml config

* feat: use helper functions to handle and order enabled/default endpoints; initialize azureOpenAI from config file

* refactor: redefine types as well as load azureOpenAI models from config file

* chore(ci): fix test description naming

* feat(azureOpenAI): use validated model grouping for request authentication

* chore: bump data-provider following rebase

* chore: bump config file version noting significant changes

* feat: add title options and switch azure configs for titling and vision requests

* feat: enable azure plugins from config file

* fix(ci): pass tests

* chore(.env.example): mark `PLUGINS_USE_AZURE` as deprecated

* fix(fetchModels): early return if apiKey not passed

* chore: fix azure config typing

* refactor(mapModelToAzureConfig): return baseURL and headers as well as azureOptions

* feat(createLLM): use `azureOpenAIBasePath`

* feat(parsers): resolveHeaders

* refactor(extractBaseURL): handle invalid input

* feat(OpenAIClient): handle headers and baseURL for azureConfig

* fix(ci): pass `OpenAIClient` tests

* chore: extract env var for azureOpenAI group config, baseURL

* docs: azureOpenAI config setup docs

* feat: safe check of potential conflicting env vars that map to unique placeholders

* fix: reset apiKey when model switches from originally requested model (vision or title)

* chore: linting

* docs: CONFIG_PATH notes in custom_config.md
2024-02-26 14:12:25 -05:00
Andreas
7a55132e42 🔧 feat: optional librechat.yaml path via environment variable (#1858)
Co-authored-by: afel <andreas.feldl@netlight.com>
2024-02-26 13:59:19 -05:00
Arno Angerer
c1a4733d50 📒 docs: Add newline for list to be correctly rendered in UI (#1873)
Currently in the documentation page the bullet list is not rendered correctly. (See first paragraph on this docs page: https://docs.librechat.ai/install/configuration/litellm.html)
2024-02-23 15:29:36 -05:00
Danny Avila
f431c8fb00 🔀 fix: Correct Expected Behavior for Modular Chat Feature (#1871) 2024-02-23 12:14:58 -05:00
Fuegovic
5445d55af2 🐋 docs: update breaking_changes.md (#1864)
add note about the use of the pre-built image in docker-compose.yml
2024-02-23 11:51:17 -05:00
Danny Avila
6a25dd38a4 🗨️ fix: Prevent Resetting Title to 'New Chat' on Follow-Up Message (#1870)
* fix: prevent reseting title to 'New Chat' on follow up message

* chore(useSSE): remove empty line
2024-02-23 10:20:46 -05:00
Fuegovic
ece5d9f588 ✏️docs: add tavily to env.example and dotenv.md (#1866)
* update .env.example

add "TAVILY_API_KEY=" to .env.example

* update dotenv.md

add Tavily to dotenv.md
2024-02-23 10:08:49 -05:00
Danny Avila
5f6d1f3db0 🎨 feat: Create Avatars of Initials Locally (#1869) 2024-02-23 09:23:29 -05:00
Fuegovic
4012dea4ab 🐋 Feat: docker pre-built image by default (#1860)
* 🐋 Feat: docker pre-built image by default

* 🐋 Feat: docker LibreChat ports from .env
2024-02-22 13:20:27 -05:00
Danny Avila
128446601a 🐛 fix: Preserve Default Model in Message Requests (#1857)
* fix: do not remove default model from message request

* chore: bump data-provider
2024-02-21 13:29:21 -05:00
Danny Avila
dd8038b375 🛠️ refactor: Model Loading and Custom Endpoint Error Handling (#1849)
* fix: handle non-assistant role ChatCompletionMessage error

* refactor(ModelController): decouple res.send from loading/caching models

* fix(custom/initializeClient): only fetch custom endpoint models if models.fetch is true

* refactor(validateModel): load models if modelsConfig is not yet cached

* docs: update on file upload rate limiting
2024-02-20 12:57:58 -05:00
Danny Avila
542494fad6 📋 feat: Accumulate Text Parts to Clipboard for Assistant Outputs (#1847) 2024-02-20 09:33:31 -05:00
Danny Avila
64e81392f2 ⬤ style: Uniform Display of Result-Streaming Cursor (#1842) 2024-02-19 22:55:58 -05:00
Danny Avila
a8a19c6caa 🛡️ feat: Model Validation Middleware (#1841)
* refactor: add ViolationTypes enum and add new violation for illegal model requests

* feat: validateModel middleware to protect the backend against illicit requests for unlisted models
2024-02-19 22:47:39 -05:00
Danny Avila
d8038e3b19 📤 refactor: Utilize intermediateReply when message.content is Empty 2024-02-19 11:03:42 -05:00
Danny Avila
ee97179edb 📝 chore: Update README.md 2024-02-19 09:45:59 -05:00
Danny Avila
63a5039fae 🔗 chore: Add Stable Discord and Homepage Links (#1835) 2024-02-19 09:42:57 -05:00
Fuegovic
7442955a1d 📝 docs: add env changes to breaking_changes.md and minor fixes (#1812)
* 📝 docs: add env changes to breacking_changes.md

* 📝 docs: replace example in docker_override.md

* 📝 docs: fix images in zeabur.md
2024-02-19 09:41:07 -05:00
Danny Avila
5291d18f38 🔀 fix: Endpoint Type Mismatch when Switching Conversations (#1834)
* refactor(useUpdateUserKeysMutation): only invalidate the endpoint whose key is being updated by user

* fix(assistants): await `getUserKeyExpiry` call

* chore: fix spinner loading color

* refactor(initializeClient): make known which endpoint api Key is missing

* fix: prevent an `endpointType` mismatch by making it impossible to assign when the `endpointsConfig` doesn't have a `type` defined, also prefer `getQueryData` call to useQuery in useChatHelpers
2024-02-19 01:31:38 -05:00
Danny Avila
d1eb7fcfc7 Update main-image-workflow.yml 2024-02-16 16:05:18 -05:00
Danny Avila
ce1cdea3de Update main-image-workflow.yml 2024-02-16 15:55:12 -05:00
Danny Avila
0da30b9481 Update main-image-workflow.yml 2024-02-16 15:46:09 -05:00
Danny Avila
b7aebf6c51 Update main-image-workflow.yml 2024-02-16 15:43:31 -05:00
Danny Avila
29ee4423a6 🐋 chore: Add Docker Compose Build Latest Main Image workflow (#1819) 2024-02-16 15:37:32 -05:00
Danny Avila
98064244bf fix: necessary font changes (#1818)
* fix: necessary font changes

* chore: minor bump
2024-02-16 14:15:05 -05:00
Danny Avila
fe0ef2ce61 📝 docs: Update docker-compose.override.yml.example 2024-02-15 12:56:28 -05:00
Danny Avila
637a1a41c2 👥 fix: Reinstate Default Social Login Values (#1811)
* fix: social logins accidentally removed default in AppService, reinstated and added test

* chore: move birthday to OTHER section and make disabled by default
2024-02-15 08:20:06 -05:00
Danny Avila
60b1d1332c 🤖 chore: Improve Assistants Run Logging (#1801) 2024-02-14 13:33:33 -05:00
Walber Cardoso
9d3215dcaa ✏️ fix(Convo): Rename Title UX (#1793) 2024-02-14 09:43:37 -05:00
CXwudi
c7020e8651 📝 docs: Enhance LibreChat Docker deployment guide (#1796)
- Revise the description for clarity and conciseness.
- Provide a direct comparison between the advanced Docker Compose deployment and the simpler `docker-compose.override.yml` setup.
- Update screenshot link for visual reference.
2024-02-14 09:42:12 -05:00
Danny Avila
04af1cad52 🤖 docs(assistants): Additional Setup & Tool Selection Info (#1798) 2024-02-13 22:24:24 -05:00
Danny Avila
d947244348 🔧 fix(data-provider): add openapi-types dependency (#1797)
* fix(data-provider): add openapi-types dependency

* chore: Bump version to 0.4.1 in package.json
2024-02-13 21:08:02 -05:00
Danny Avila
ecd63eb9f1 feat: Assistants API, General File Support, Side Panel, File Explorer (#1696)
* feat: assistant name/icon in Landing & Header

* feat: assistname in textarea placeholder, and use `Assistant` as default name

* feat: display non-image files in user messages

* fix: only render files if files.length is > 0

* refactor(config -> file-config): move file related configuration values to separate module, add excel types

* chore: spreadsheet file rendering

* fix(Landing): dark mode style for Assistant Name

* refactor: move progress incrementing to own hook, start smaller, cap near limit \(1\)

* refactor(useContentHandler): add empty Text part if last part was completed tool or image

* chore: add accordion trigger border styling for dark mode

* feat: Assistant Builder model selection

* chore: use Spinner when Assistant is mutating

* fix(get/assistants): return correct response object `AssistantListResponse`

* refactor(Spinner): pass size as prop

* refactor: make assistant crud mutations optimistic, add types for options

* chore: remove assistants route and view

* chore: move assistant builder components to separate directory

* feat(ContextButton): delete Assistant via context button/dialog, add localization

* refactor: conditionally show use and context menu buttons, add localization for create assistant

* feat: save side panel states to localStorage

* style(SidePanel): improve avatar menu and assistant select styling for dark mode

* refactor: make NavToggle reusable for either side (left or right), add SidePanel Toggle with ability to close it completely

* fix: resize handle and navToggle behavior

* fix(/avatar/:assistant_id): await `deleteFile` and assign unique name to uploaded image

* WIP: file UI components from PR #576

* refactor(OpenAIMinimalIcon): pass className

* feat: formatDate helper fn

* feat: DataTableColumnHeader

* feat: add row selection, formatted row values, number of rows selected

* WIP: add files to Side panel temporarily

* feat: `LB_QueueAsyncCall`: Leaky Bucket queue for external APIs, use in `processDeleteRequest`

* fix(TFile): correct `source` type with `FileSources`

* fix(useFileHandling): use `continue` instead of return when iterating multiple files, add file type to extendedFile

* chore: add generic setter type

* refactor(processDeleteRequest): settle promises to prevent rejections from processing deletions, log errors

* feat: `useFileDeletion` to reuse file deletion logic

* refactor(useFileDeletion): make `setFiles` an optional param and use object as param

* feat: useDeleteFilesFromTable

* feat: use real `files` data and add deletion action to data table

* fix(Table): make headers sticky

* feat: add dynamic filtering for columns; only show to user Host or OpenAI storage type

* style(DropdownMenu): replace `slate` with `gray`

* style(DataTable): apply dark mode themes and other misc styling

* style(Columns): add color to OpenAI Storage option

* refactor(FileContainer): make file preview reusable

* refactor(Images): make image preview reusable

* refactor(FilePreview): make file prop optional for FileIcon and FilePreview, fix relative style

* feat(Columns): add file/image previews, set a minimum size to show for file size in bytes

* WIP: File Panel with real files and formatted

* feat: open files dialog from panel

* style: file data table mobile and general column styling fixes

* refactor(api/files): return files sorted by the most recently updated

* refactor: provide fileMap through context to prevent re-selecting files to map in different areas; remove unused imports commented out in PanelColumns

* refactor(ExtendFile): make File type optional, add `attached` to prevent attached files from being deleted on remove, make Message.files a partial TFile type

* feat: attach files through file panel

* refactor(useFileHandling): move files to the start of cache list when uploaded

* refactor(useDeleteFilesMutation): delete files from cache when successfully deleted from server

* fix(FileRow): handle possible edge case of duplication due to attaching recently uploaded file

* style(SidePanel): make resize grip border transparent, remove unnecessary styling on close sidepanel button

* feat: action utilities and tests

* refactor(actions): add `ValidationResult` type and change wording for no server URL found

* refactor(actions): check for empty server URL

* fix(data-provider): revert tsconfig to fix type issue resolution

* feat(client): first pass of actions input for assistants

* refactor(FunctionSignature): change method to output object instead of string

* refactor(models/Assistant): add actions field to schema, use searchParams object for methods, and add `getAssistant`

* feat: post actions input first pass
- create new Action document
- add actions to Assistant DB document
- create /action/:assistant_id POST route
- pass more props down from PanelSwitcher, derive assistant_id from switcher
- move privacy policy to ActionInput
- reset data on input change/validation
- add `useUpdateAction`
- conform FunctionSignature type to FunctionTool
- add action, assistant doc, update hook related types

* refactor: optimize assistant/actions relationship
- past domain in metadata as hostname and not a URL
- include domain in tool name
- add `getActions` for actions retrieval by user
- add `getAssistants` for assistant docs retrieval by user
- add `assistant_id` to Action schema
- move actions to own module as a subroute to `api/assistants`
- add `useGetActionsQuery` and `useGetAssistantDocsQuery` hooks
- fix Action type def

* feat: show assistant actions in assistant builder

* feat: switch to actions on action click, editing action styling

* fix: add Assistant state for builder panel to allow immediate selection of newly created assistants as well as retaining the current assistant when switching to a different panel within the builder

* refactor(SidePanel/NavToggle): offset less from right when SidePanel is completely collapsed

* chore: rename `processActions` -> `processRequiredActions`

* chore: rename Assistant API Action to RequiredAction

* refactor(actions): avoid nesting actual API params under generic `requestBody` to optimize LLM token usage

* fix(handleTools): avoid calling `validTool` if not defined, add optional param to skip the loading of specs, which throws an error in the context of assistants

* WIP: working first pass of toolCalls generated from openapi specs

* WIP: first pass ToolCall styling

* feat: programmatic iv encryption/decryption helpers

* fix: correct ActionAuth types/enums, and define type for AuthForm

* feat: encryption/decryption helpers for Action AuthMetadata

* refactor(getActions): remove sensitive fields from query response

* refactor(POST/actions): encrypt and remove sensitive fields from mutation response

* fix(ActionService): change ESM import to CJS

* feat: frontend auth handling for actions + optimistic update on action update/creation

* refactor(actions): use the correct variables and types for setAuth method

* refactor: POST /:assistant_id action can now handle updating an existing action, add `saved_auth_fields` to determine when user explicitly saves new auth creds. only send auth metadata if user explicitly saved fields

* refactor(createActionTool): catch errors and send back meaningful error message, add flag to `getActions` to determine whether to retrieve sensitive values or not

* refactor(ToolService): add `action` property to ToolCall PartMetadata to determine if the tool call was an action, fix parsing function name issue with actionDelimiter

* fix(ActionRequest): use URL class to correctly join endpoint parts for `execute` call

* feat: delete assistant actions

* refactor: conditionally show Available actions

* refactor: show `retrieval` and `code_interpreter` as Capabilities, swap `Switch` for `Checkbox`

* chore: remove shadow-stroke from messages

* WIP: first pass of Assistants Knowledge attachments

* refactor: remove AssistantsProvider in favor of FormProvider, fix selectedAssistant re-render bug, map Assistant file_ids to files via fileMap, initialize Knowledge component with mapped files if any exist

* fix: prevent deleting files on assistant file upload

* chore: remove console.log

* refactor(useUploadFileMutation): update files and assistants cache on upload

* chore: disable oauth option as not supported yet

* feat: cancel assistant runs

* refactor: initialize OpenAI client with helper function, resolve all related circular dependencies

* fix(DALL-E): initialization

* fix(process): openai client initialization

* fix: select an existing Assistant when the active one is deleted

* chore: allow attaching files for assistant endpoint, send back relevant OpenAI error message when uploading, deconstruct openAI initialization correctly, add `message_file` to formData when a file is attached to the message but not the assistant

* fix: add assistant_id on newConvo

* fix(initializeClient): import fix

* chore: swap setAssistant for setOption in useEffect

* fix(DALL-E): add processFileURL to loadTools call

* chore: add customConfig to debug logs

* feat: delete threads on convo delete

* chore: replace Assistants icon

* chore: remove console.dir() in `abortRun`

* feat(AssistantService): accumulate text values from run in openai.responseText

* feat: titling for assistants endpoint

* chore: move panel file components to appropriate directory, add file checks for attaching files, change icon for Attach Files

* refactor: add localizations to tools, plugins, add condition for adding/remove user plugins so tool selections don't affect this value

* chore: disable `import from url` action for now

* chore: remove textMimeTypes from default fileConfig for now

* fix: catch tool errors and send as outputs with error messages

* fix: React warning about button as descendant of button

* style: retrieval and cancelled icon

* WIP: pass isSubmitting to Parts, use InProgressCall to display cancelled tool calls correctly, show domain/function name

* fix(meilisearch): fix `postSaveHook` issue where indexing expects a mongo document, and join all text content parts for meili indexing

* ci: fix dall-e tests

* ci: fix client tests

* fix: button types in actions panel

* fix: plugin auth form persisting across tool selections

* fix(ci): update AppService spec with `loadAndFormatTools`

* fix(clearConvos): add id check earlier on

* refactor(AssistantAvatar): set previewURL dynamically when emtadata.avatar changes

* feat(assistants): addTitle cache setting

* fix(useSSE): resolve rebase conflicts

* fix: delete mutation

* style(SidePanel): make grip visible on active and hover, invisible otherwise

* ci: add data-provider tests to workflow, also update eslint/tsconfig to recognize specs, and add `text/csv` to fileConfig

* fix: handle edge case where auth object is undefined, and log errors

* refactor(actions): resolve  schemas, add tests for resolving refs, import specs from separate file for tests

* chore: remove comment

* fix(ActionsInput): re-render bug when initializing states with action fields

* fix(patch/assistant): filter undefined tools

* chore: add logging for errors in assistants routes

* fix(updateAssistant): map actions to functions to avoid overwriting

* fix(actions): properly handle GET paths

* fix(convos): unhandled delete thread exception

* refactor(AssistantService): pass both thread_id and conversationId when sending intermediate assistant messages, remove `mapMessagesToSteps` from AssistantService

* refactor(useSSE): replace all messages with runMessages and pass latestMessageId to abortRun; fix(checkMessageGaps): include tool calls when  syncing messages

* refactor(assistants/chat): invoke `createOnTextProgress` after thread creation

* chore: add typing

* style: sidepanel styling

* style: action tool call domain styling

* feat(assistants): default models, limit retrieval to certain models, add env variables to to env.example

* feat: assistants api key in EndpointService

* refactor: set assistant model to conversation on assistant switch

* refactor: set assistant model to conversation on assistant select from panel

* fix(retrieveAndProcessFile): catch attempt to download file with `assistant` purpose which is not allowed; add logging

* feat: retrieval styling, handling, and logging

* chore: rename ASSISTANTS_REVERSE_PROXY to ASSISTANTS_BASE_URL

* feat: FileContext for file metadata

* feat: context file mgmt and filtering

* style(Select): hover/rounded changes

* refactor: explicit conversation switch, endpoint dependent, through `useSelectAssistant`, which does not create new chat if current endpoint is assistant endpoint

* fix(AssistantAvatar): make empty previewURL if no avatar present

* refactor: side panel mobile styling

* style: merge tool and action section, optimize mobile styling for action/tool buttons

* fix: localStorage issues

* fix(useSelectAssistant): invoke react query hook directly in select hook as Map was not being updated in time

* style: light mode fixes

* fix: prevent sidepanel nav styling from shifting layout up

* refactor: change default layout (collapsed by default)

* style: mobile optimization of DataTable

* style: datatable

* feat: client-side hide right-side panel

* chore(useNewConvo): add partial typing for preset

* fix(useSelectAssistant): pass correct model name by using template as preset

* WIP: assistant presets

* refactor(ToolService): add native solution for `TavilySearchResults` and log tool output errors

* refactor: organize imports and use native TavilySearchResults

* fix(TavilySearchResults): stringify result

* fix(ToolCall): show tool call outputs when not an action

* chore: rename Prompt Prefix to custom instructions (in user facing text only)

* refactor(EditPresetDialog): Optimize setting title by debouncing, reset preset on dialog close to avoid state mixture

* feat: add `presetOverride` to overwrite active conversation settings when saving a Preset (relevant for client side updates only)

* feat: Assistant preset settings (client-side)

* fix(Switcher): only set assistant_id and model if current endpoint is Assistants

* feat: use `useDebouncedInput` for updating conversation settings, starting with EditPresetDialog title setting and Assistant instructions setting

* feat(Assistants): add instructions field to settings

* feat(chat/assistants): pass conversation settings to run body

* wip: begin localization and only allow actions if the assistant is created

* refactor(AssistantsPanel): knowledge localization, allow tools on creation

* feat: experimental: allow 'priming' values before assistant is created, that would normally require an assistant_id to be defined

* chore: trim console logs and make more meaningful

* chore: toast messages

* fix(ci): date test

* feat: create file when uploading Assistant Avatar

* feat: file upload rate limiting from custom config with dynamic file route initialization

* refactor: use file upload limiters on post routes only

* refactor(fileConfig): add endpoints field for endpoint specific fileconfigs, add mergeConfig function, add tests

* refactor: fileConfig route, dynamic multer instances used on all '/' and '/images' POST routes, data service and query hook

* feat: supportedMimeTypesSchema, test for array of regex

* feat: configurable file config limits

* chore: clarify assistants file knowledge prereq.

* chore(useTextarea): default to localized 'Assistant' if assistant name is empty

* feat: configurable file limits and toggle file upload per endpoint

* fix(useUploadFileMutation): prevent updating assistant.files cache if file upload is a message_file attachment

* fix(AssistantSelect): set last selected assistant only when timeout successfully runs

* refactor(queries): disable assistant queries if assistants endpoint is not enabled

* chore(Switcher): add localization

* chore: pluralize `assistant` for `EModelEndpoint key and value

* feat: show/hide assistant UI components based on endpoint availability; librechat.yaml config for disabling builder section and setting polling/timeout intervals

* fix(compactEndpointSchemas): use EModelEndpoint for schema access

* feat(runAssistant): use configured values from `librechat.yaml` for `pollIntervalMs` and `timeout`

* fix: naming issue

* wip: revert landing

* 🎉 happy birthday LibreChat (#1768)

* happy birthday LibreChat

* Refactor endpoint condition in Landing component

* Update birthday message in Eng.tsx

* fix(/config): avoid nesting ternaries

* refactor(/config): check birthday

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>

* fix: landing

* fix: landing

* fix(useMessageHelpers): hardcoded check to use EModelEndpoint instead

* fix(ci): convo test revert to main

* fix(assistants/chat): fix issue where assistant_id was being saved as model for convo

* chore: added logging, promises racing to prevent longer timeouts, explicit setting of maxRetries and timeouts, robust catching of invalid abortRun params

* refactor: use recoil state for `showStopButton` and only show for assistants endpoint after syncing conversation data

* refactor: optimize abortRun strategy using localStorage, refactor `abortConversation` to use async/await and await the result, refactor how the abortKey cache is set for runs

* fix(checkMessageGaps): assign `assistant_id` to synced messages if defined; prevents UI from showing blank assistant for cancelled messages

* refactor: re-order sequence of chat route, only allow aborting messages after run is created, cancel abortRun if there was a cancelling error (likely due already cancelled in chat route), and add extra logging

* chore(typedefs): add httpAgent type to OpenAIClient

* refactor: use custom implementation of retrieving run with axios to allow for timing out run query

* fix(waitForRun): handle timed out run retrieval query

* refactor: update preset conditions:
- presets will retain settings when a different endpoint is selected; for existing convos, either when modular or is assistant switch
- no longer use `navigateToConvo` on preset select

* fix: temporary calculator hack as expects string input when invoked

* fix: cancel abortRun only when cancelling error is a result of the run already being cancelled

* chore: remove use of `fileMaxSizeMB` and total counterpart (redundant)

* docs: custom config documentation update

* docs: assistants api setup and dotenv, new custom config fields

* refactor(Switcher): make Assistant switcher sticky in SidePanel

* chore(useSSE): remove console log of data and message index

* refactor(AssistantPanel): button styling and add secondary select button to bottom of panel

* refactor(OpenAIClient): allow passing conversationId to RunManager through titleConvo and initializeLLM to properly record title context tokens used in cases where conversationId was not defined by the client

* feat(assistants): token tracking for assistant runs

* chore(spendTokens): improve logging

* feat: support/exclude specific assistant Ids

* chore: add update `librechat.example.yaml`, optimize `AppService` handling, new tests for `AppService`, optimize missing/outdate config logging

* chore: mount docker logs to root of project

* chore: condense axios errors

* chore: bump vite

* chore: vite hot reload fix using latest version

* chore(getOpenAIModels): sort instruct models to the end of models list

* fix(assistants): user provided key

* fix(assistants): user provided key, invalidate more queries on revoke

---------

Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
2024-02-13 20:42:27 -05:00
Danny Avila
cd2786441a 🐋 chore: add tag for latest for ci/cd docker builds (#1791) 2024-02-13 11:31:48 -05:00
Danny Avila
050eeb1211 chore: Update Docker build and push actions (#1790) 2024-02-13 10:59:46 -05:00
Danny Avila
6ccf4d6ed2 Release: v0.6.9 (#1789) 2024-02-13 09:40:33 -05:00
Fuegovic
7ff2418d87 📝 docs: clarifications - override file & balance/token count (#1783)
* 📝 docs: override file clarification

* 📝 docs: override file clarification

* 📝 docs: balance & token count clarification
2024-02-12 15:03:56 -05:00
Fuegovic
d8d79aba16 ✔️ docs: update custom_config.md (#1774)
Add link to https://yamlchecker.com/
2024-02-12 10:26:36 -05:00
Fuegovic
5ccdec730b 🐳 docs: update to compose v2 (#1767) 2024-02-12 10:26:05 -05:00
Marco Beretta
a91042b6b9 🎉 happy birthday LibreChat (#1768)
* happy birthday LibreChat

* Refactor endpoint condition in Landing component

* Update birthday message in Eng.tsx

* fix(/config): avoid nesting ternaries

* refactor(/config): check birthday

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-02-11 09:10:12 -05:00
Trevor Swanson
14b61fc861 🖌️ style: Update conversation history groups (#1770)
* style: Add month groups to conversation history

* style: Change "Last x days" to "Previous x days" to match ChatGPT

* style: Add "Yesterday" to conversation groups to match ChatGPT

* fix: use startOfDay for Yesterday conversation group

* fix: Output month name instead of number in conversation group name

* test: Validate new conversation groups are created properly

* fix: Formatting of month category string was wrong
2024-02-11 08:53:44 -05:00
Danny Avila
50adb1b3c6 🧪 ci: Fix Conversation Grouping Tests 2024-02-11 08:46:14 -05:00
Danny Avila
d2494e6b3b 🔌 fix: Minor Plugins Improvements (#1766)
* fix(PluginsClient): don't invoke `getFunctionModelName` when using Azure OpenAI

* fix: plugins styling fix with new cursor

* ci(PluginsClient): test azure exception for getFunctionModelName
2024-02-10 13:13:38 -05:00
Raí Santos
a2e85b7053 ⬤ style: Circular Streaming Cursor (#1736)
* Updated Style Cursor like ChatGPT

* style(Markdown.tsx): add space before cursor when there is text

* fix: revert OpenAIClient.tokens.js change

* fix:(Markdown.tsx): revert change of unused file

* fix(convos.spec.ts): test fix

* chore: remove raw HTML for cursor animations

---------

Co-authored-by: Danny Avila <danacordially@gmail.com>
Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-02-10 11:07:57 -05:00
Danny Avila
92a41fbf47 🐳 feat: Push Container Images to DockerHub (#1762) 2024-02-10 08:27:52 -05:00
Danny Avila
39caeb2027 🧩 feat: Support Alternate API Keys for Plugins (#1760)
* refactor(DALL-E): retrieve env variables at runtime and not from memory

* feat(plugins): add alternate env variable handling to allow setting one api key for multiple plugins

* docs: update docs
2024-02-09 10:38:50 -05:00
bsu3338
927ce5395b 🦙 docs: Ollama Docs Update (#1756)
* Update to use docker-compose.overridge.yml

Add GPU Acceleration links

* Update litellm.md
2024-02-09 07:11:09 -05:00
Danny Avila
ff057152e2 👤 feat: User ID in Model Query; chore: cleanup ModelService (#1753)
* feat: send the LibreChat user ID as a query param when fetching the list of models

* chore: update bun

* chore: change bun command for building data-provider

* refactor: prefer use of `getCustomConfig` to access custom config, also move to `server/services/Config`

* refactor: make endpoints/custom option for the config optional, add userIdQuery, and use modelQueries log store in ModelService

* refactor(ModelService): use env variables at runtime, use default models from data-provider, and add tests

* docs: add `userIdQuery`

* fix(ci): import changed
2024-02-08 10:06:58 -05:00
Danny Avila
d06e5d2e02 🛠️ chore: Refactor Update Script to Utilize Docker Compose v2 (#1752) 2024-02-07 22:06:10 -05:00
Fuegovic
7f2264fd5c ✏️ update dotenv.md (#1740)
update the note about rebuilding LibreChat after configuration changes since the .env file is now mounted into the volume
2024-02-07 08:30:06 -05:00
Aleksei Lisikhin
7188cbde3d 📖 docs: fix link pointing to dotenv guide (#1739) 2024-02-06 21:50:52 -05:00
Danny Avila
b151cd9911 📇 refactor(convoSchema): index updatedAt field (#1732) 2024-02-05 15:27:06 -05:00
Danny Avila
f30d6bd689 🔧 fix: socialLogins default value (#1730)
* fix: socialLogins default value

* ci: add test for `AppService`
2024-02-05 11:26:12 -05:00
Marco Beretta
a2c35e8415 🔄🔐 refactor: auth; style: match OpenAI; feat: custom social login order (#1421)
* refactor(Login & Registration)

* fix(Registration) test errors

* refactor(LoginForm & ResetPassword)

* fix(LoginForm): display 'undefined' when loading page; style(SocialButton): match OpenAI's graphics

* some refactor and style update for social logins

* style: width like OpenAI; feat: custom social login order; refactor: alphabetical socials

* fix(Registration & Login) test

* Update .env.example

* Update .env.example

* Update dotenv.md

* refactor: remove `SOCIAL_LOGIN_ORDER` for `socialLogins` configured from `librechat.yaml`
- initialized by AppService, attached as app.locals property
- rename socialLoginOrder and loginOrder to socialLogins app-wide for consistency
- update types and docs
- initialize config variable as array and not singular string to parse
- bump data-provider to 0.3.9

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-02-05 03:31:18 -05:00
Marco Beretta
25da90657d 🔒✉️ feat: allow only certain domain (#1562)
* feat: allow only certain domain

* Update dotenv.md

* refactor( registrationController) & handle ALLOWED_REGISTRATION_DOMAINS not specified

* cleanup and moved to AuthService for better  error handling

* refactor: replace environment variable with librechat config item, add typedef for custom config, update docs for new registration object and allowedDomains values

* ci(AuthService): test for `isDomainAllowed`

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-02-05 02:14:52 -05:00
Fuegovic
b5c2fb93c1 🖋️ docs: Formatting Fix (#1726)
* 🍃 docs: Formatting Fix

* 🖋️ docs: Formatting Fix

* 🖋️ docs: Formatting Fix
2024-02-05 01:28:08 -05:00
Yuanlin Lin
d1cf02b5a8 🐳 docs: Add deploy to Zeabur button and guide (#1727)
* 🐳 docs: Add deploy to Zeabur button in README.md

* 🐳 docs: Add deploy to Zeabur guide in docs
2024-02-05 01:27:48 -05:00
Danny Avila
c31d5d9a1d 🐳 docs: Formatting Fix (#1725) 2024-02-04 17:18:12 -05:00
Danny Avila
7b38586716 🐳 docs: How to Authenticate MongoDB (#1724)
* refactor: remove `--noauth` flag from `mongod` command

* docs: add mongodb auth instructions

* Update manage_your_database.md

* chore: add example

* Update manage_your_database.md
2024-02-04 16:52:52 -05:00
MACHINSOFT
e7f6b22b5d 📱 style: Settings UI Enhancements for Mobile (#1721)
* Fix the interface for the mobile version.

* Make uniform margins for buttons.
2024-02-04 08:44:09 -05:00
Ryohei Kamiya
d25ff7632a 🐳 fix: Update .devcontainer Files (#1712)
* fix: modify the base docker image for devcontainer

* fix: restore package-lock.json from main
2024-02-04 08:39:19 -05:00
Danny Avila
335980ac98 🔧 fix: Patch incorrect Package Installation (#1720) 2024-02-03 20:41:02 -05:00
Danny Avila
74459d6261 ♾️ style: Infinite Scroll Nav and Sort Convos by Date/Usage (#1708)
* Style: Infinite Scroll and Group convos by date

* Style: Infinite Scroll and Group convos by date- Redesign NavBar

* Style: Infinite Scroll and Group convos by date- Redesign NavBar - Clean code

* Style: Infinite Scroll and Group convos by date- Redesign NavBar - Redesign NewChat Component

* Style: Infinite Scroll and Group convos by date- Redesign NavBar - Redesign NewChat Component

* Style: Infinite Scroll and Group convos by date- Redesign NavBar - Redesign NewChat Component

* Including OpenRouter and Mistral icon

* refactor(Conversations): cleanup use of utility functions and typing

* refactor(Nav/NewChat): use localStorage `lastConversationSetup` to determine the endpoint to use, as well as icons -> JSX components, remove use of `endpointSelected`

* refactor: remove use of `isFirstToday`

* refactor(Nav): remove use of `endpointSelected`, consolidate scrolling logic to its own hook `useNavScrolling`, remove use of recoil `conversation`

* refactor: Add spinner to bottom of list, throttle fetching, move query hooks to client workspace

* chore: sort by `updatedAt` field

* refactor: optimize conversation infinite query, use optimistic updates, add conversation helpers for managing pagination, remove unnecessary operations

* feat: gen_title route for generating the title for the conversation

* style(Convo): change hover bg-color

* refactor: memoize groupedConversations and return as array of tuples, correctly update convos pre/post message stream, only call genTitle if conversation is new, make `addConversation` dynamically either add/update depending if convo exists in pages already, reorganize type definitions

* style: rename Header NewChat Button -> HeaderNewChat, add NewChatIcon, closely match main Nav New Chat button to ChatGPT

* style(NewChat): add hover bg color

* style: cleanup comments, match ChatGPT nav styling, redesign search bar, make part of new chat sticky header, move Nav under same parent as outlet/mobilenav, remove legacy code, search only if searchQuery is not empty

* feat: add tests for conversation helpers and ensure no duplicate conversations are ever grouped

* style: hover bg-color

* feat: alt-click on convo item to open conversation in new tab

* chore: send error message when `gen_title` fails

---------

Co-authored-by: Walber Cardoso <walbercardoso@gmail.com>
2024-02-03 20:25:35 -05:00
Fuegovic
13b2d6e34a 🖊️ README.md: update button layout (#1709)
change size and position of the one click deployment buttons
2024-02-03 00:05:29 -05:00
Danny Avila
7934cc5ec4 🪙 fix(getModelMaxTokens): Retrieve Correct Context Tokens for Azure OpenAI (#1710) 2024-02-02 23:53:50 -05:00
Raí Santos
296967eff0 🖌️feat: ScrolltoBottom & Mobile Improvements; Translation Update (#1651)
* 🖌️feat: Scrolltobottom Style

* 🖌️feat: ScrolltoBottom Style

* 📱Settings tab now centered on mobile / selection bug fixed 🐞, 🌍 Updated Translation

* 🛠️fix: Adjust the width of the settings dialog and address the issue of not seeing selection on the desktop.

* 🎨 Update settings tabs background color for dark mode.
Adjusts background color dynamically based on screen size.

* 🛠️fix: Reverted changes in ScrolltoBottom file
2024-02-02 10:31:30 -05:00
Danny Avila
5f6d431136 📷 fix: Pass Base64 to Gemini Vision Payload when using CDN URLs (#1705) 2024-02-02 01:37:40 -05:00
Danny Avila
8479ac7293 🚀 feat: Support for GPT-3.5 Turbo/0125 Model (#1704)
* 🚀 feat: Support for GPT-3.5 Turbo/0125 Model

* ci: fix tx test
2024-02-02 01:01:11 -05:00
Danny Avila
30e143e96d 🪙 feat: Use OpenRouter Model Data for Token Cost and Context (#1703)
* feat: use openrouter data for model token cost/context

* chore: add ttl for tokenConfig and refetch models if cache expired
2024-02-02 00:42:11 -05:00
marlonka
f1d974c513 🌍 : Update German Translations (#1683)
Co-authored-by: marlonka <marloc55@yahoo.de>
2024-02-01 01:53:42 -05:00
Fuegovic
2b4870892a 🥷 docs: Ninja - ChatGPT-browser reverse proxy (#1697)
* 🥷  docs: Ninja ChatGPT-browser reverse proxy

* 🥷  docs: breaking changes
2024-02-01 01:44:51 -05:00
zimmra
a9220375d3 🤖 docs: add copilot-gpt4-service AI setup info (#1695)
Adds information and setup details for [aaamon's copilot-gpt4-service](https://github.com/aaamoon/copilot-gpt4-service) to Unofficial APIs section of the documentation.

Utilizes Github's Copilot to access OpenAI api.
2024-01-31 16:21:12 -05:00
Fuegovic
b37f55cd3a 📝 docs update: remove ChatGPTbrowser and other small fixes (#1686)
* 🧼 docs: remove references to ChatGPTbrowser and PandoraNext

* docs: clean up .env file

Update OpenAI models with the list of automatically fetched models, update Plugin models with the list of models supporting functions, comment out ToC in custom_config.md since it conflicts with mkdock right sidebar ToC

* 🖋️ docs: fix formatting in linux_install.md

* docs: update example model lists in dotenv.md

* docs: update breaking_changesv.md
2024-01-31 08:20:07 -05:00
Danny Avila
972402e029 🛡️ : Security Enhancements (#1681)
* fix: sanitize HTTP params and do not send whole error objects backs

* fix: prevent path traversal

* fix: send custom error message for tokenizer route

* chore: handle info exposure vector

* chore(oauth): skip check due to false positive as oauth routes are rate-limited

* chore(app): disable `x-powered-by`

* chore: disable false positives or flagging of hardcoded secrets when they are fake values

* chore: add path traversal safety check
2024-01-30 14:34:02 -05:00
Danny Avila
9fad1b2cae 🔝fix: Re-order System Message to Top for Mistral API Payloads (#1678)
* fix: re-order System Message if Mistral AI API as it only allows System Message at start of Payload

* fix: re-introduce singular system message change role to `user` if `system`
2024-01-30 10:13:32 -05:00
Kakenyan
c4fd8a38e3 🌍 : Update Japanese translation (#1666)
* Language translation: japanese

* Language translation: japanese
2024-01-30 07:32:09 -05:00
Linus Gasser
35e611f113 👤 fix: Avatar Check in User Auth (#1677) 2024-01-30 07:31:49 -05:00
Danny Avila
f7f7f929a0 📋 feat: Log Custom Config File and Add Known Model Limits to Custom Endpoint (#1657)
* refactor(custom): add all recognized models to maxTokensMap for custom endpoint

* feat(librechat.yaml): log the custom config file on initial load

* fix(OpenAIClient): pass endpointType/endpoint to `getModelMaxTokens` call
2024-01-27 08:59:04 -05:00
Danny Avila
c470147ea2 🐞 fix: Bump @langchain/google-genai to Address Chinese Text Bug (#1654) 2024-01-26 16:08:02 -05:00
Danny Avila
0edfa0483e 🧹 Clean Up OpenAI Config and Show 'Set Azure Key' for Plugins (#1649)
* refactor(gptPlugins): prevent edge case where exact word `azure` could be found in azure api Key detection when not an azure key

* refactor(SetKeyDialog): cleanup OpenAI config, show \'set azure key\' when `PLUGINS_USE_AZURE` env var is enabled
2024-01-26 09:27:20 -05:00
Danny Avila
fcbaa74e4a 🚀 feat: Support for GPT-4 Turbo/0125 Models (#1643) 2024-01-25 22:57:18 -05:00
bsu3338
d0730d2515 📖 docs: Update litellm.md with Redis and Langfuse (#1618) 2024-01-25 08:17:38 -05:00
Jo
f0b30b87c8 📖 docs: Update multilingual_information.md (#1622)
* Update multilingual_information.md

Some minor grammatical errors in the French translation

* Update multilingual_information.md
2024-01-25 07:49:35 -05:00
Danny Avila
d2efc7b9df 🌡️ feat: Add Health Check Route to Backend (#1623) 2024-01-24 11:39:20 -05:00
bsu3338
81ff598eba 🔏 feat: Nginx SSL Secure Config (#1615)
* Update nginx.conf

Add Mozilla SSL Configuration Generator recommended options.

* Update nginx.conf Remove Space
2024-01-23 07:30:26 -05:00
bsu3338
5730028b83 📖 docs: Update litellm.md to add Ollama (#1616) 2024-01-22 20:45:00 -05:00
Danny Avila
36560d5d9b 🛠️ fix: Preserve Dollar Signs in Code Blocks for LaTeX Parsing (#1612) 2024-01-22 10:02:36 -05:00
fidoriel
367c78f8d2 🐋 feat: CI Docker builds for arm64v8 (#1607) 2024-01-21 19:46:30 -05:00
Danny Avila
a0dabcc855 📱 fix(Root.tsx): Update styling to use h-dvh instead of h-screen (#1608) 2024-01-21 19:41:52 -05:00
Wahit Fitriyanto
42de461a83 🌍 : Update indonesian translation (#1601) 2024-01-20 15:22:25 -05:00
Fuegovic
cf4cdf8b4f 📱🍎 feat: PWA on iOS (#1602) 2024-01-20 15:21:40 -05:00
Naosuke Yokoe
3ed6cef58f 🐛 fix: Converted the getter name and description of Azure AI Search Plugin to a property (#1599) 2024-01-20 08:41:09 -05:00
Fuegovic
5ac89b8f0e 🧹 chore: remove deprecated model from documentation (#1598) 2024-01-19 16:14:02 -05:00
Danny Avila
5a74ac9a60 : Release v0.6.6 (#1597) 2024-01-19 15:34:06 -05:00
Fuegovic
130e346228 🌍 : Translation update (Fr & It) (#1595) 2024-01-19 15:01:23 -05:00
Marco Beretta
9b7d7196e9 🌎: Update Italian Translation (#1594)
* translation update

* translation updatre + new transaltions

* Update language files for English and Italian
2024-01-19 15:00:45 -05:00
Danny Avila
e73608ba46 🪶 feat: Add Support for Azure OpenAI Base URL (#1596)
* refactor(extractBaseURL): add handling for all possible Cloudflare AI Gateway endpoints

* chore: added endpointoption todo for updating type and optimizing handling app-wide

* feat(azureUtils):
- `genAzureChatCompletion`: allow optional client pass to update azure property
- `constructAzureURL`: optionally replace placeholders for instance and deployment names of an azure baseURL
- add tests for module

* refactor(extractBaseURL): return entire input when cloudflare `azure-openai` suffix detected
- also add more tests for both construct and extract URL

* refactor(genAzureChatCompletion): only allow omitting instance name if baseURL is not set

* refactor(initializeClient): determine `reverseProxyUrl` based on endpoint (azure or openai)

* refactor: utitlize `constructAzureURL` when `AZURE_OPENAI_BASEURL` is set

* docs: update docs on `AZURE_OPENAI_BASEURL`

* fix(ci): update expected error message for `azureUtils` tests
2024-01-19 14:57:03 -05:00
Raí Santos
5c94f5330a 🌎 : Update Portuguese Translation (#1593)
* 🌎: Update Portuguese Translation

* 🌎: Update Portuguese Translation
2024-01-19 09:14:26 -05:00
Danny Avila
f133bb98fe 🖌️ feat: Optional Display Username in Messages, Send/Stop Button Style, Localization (#1592)
* 👤add: Username instead of 'You' when sending messages.

* 🌎: Added a new translation for 'You' and updated the existing translation for Spanish.

* fix: remove "!"

* Added: New setting Account for show username in messages
chore (StopButon and SendButon): Updated to new style of ChatGPT
chore Update and Added news translations: Spanish, English and Portuguese Brazilian

* fix: message component definition and imports order, remove unnecessary useEffect and localStorage set, fix localStorage key in store

* chore: update readme.md

* chore: optimize condition for messageLabel

* chore(Message.tsx): remove empty blocks

---------

Co-authored-by: Raí Santos <140329135+itzraiss@users.noreply.github.com>
2024-01-19 03:44:02 -05:00
Carson Yang
3df58532d9 🚆 docs: Add Sealos 1-Click Deployment (#1578)
Add deploy on Sealos
2024-01-19 03:13:49 -05:00
Danny Avila
83292a47a7 📜 refactor: Enhance Auto Scroll Speed and UseEffect Cleanup (#1591) 2024-01-18 21:25:57 -05:00
Danny Avila
a7c54573c4 🚀 feat: Add OPENAI_ORGANIZATION for all OpenAI Requests (#1590) 2024-01-18 20:39:30 -05:00
Danny Avila
7e2e19a134 🎯 feat(config): Custom Endpoint Request Headers (#1588) 2024-01-18 20:11:42 -05:00
Danny Avila
ab3339210a 🖼️ feat(DALL-E): Azure OpenAI Support & New Config Variables (#1586)
* feat(DALL-E-3/DALL-E-2): Azure OpenAI support. New Version specific environment credentials:
 - DALLEx_SYSTEM_PROMPT=
 - DALLEx_AZURE_API_VERSION=
 - DALLEx_BASEURL=
 - DALLEx_API_KEY=
 - replace `x` with `3` or `2`

* docs: update docs based on new env vars and Azure OpenAI support for DALL-E

* docs: breaking change for user provided DALLE_API_KEY:
- **DALL-E Update**: user-provided keys for DALL-E are now specific to each DALL-E version, i.e.:  and
- Note:  will work for both DALL-E-3 and DALL-E-2 when the admin provides the credential; in other words, this may only affect your users if DALLE_API_KEY is not set in the  file. In this case, they will simply have to uninstall the plugin, and provide their API key again.

* refactor: use process.env at runtime instead of from memory to fix testing DALLE3.spec.js, adjust test
2024-01-18 19:39:27 -05:00
Danny Avila
a8d6bfde7a ✏️ feat: LaTeX parsing for Messages (#1585)
* feat: Beta features tab in Settings and LaTeX Parsing toggle

* feat: LaTex parsing with spec
2024-01-18 14:44:10 -05:00
Danny Avila
638f9242e5 🪙 refactor: Update tokens.js for Added Context Buffer from Max (#1573)
* Update tokens.js

* chore: linting previous PR

* chore: adjust token limits, add buffers

* chore: linting

* chore: adjust 32k gpt-4 limit
2024-01-17 08:13:46 -05:00
Wahit Fitriyanto
963dbf3a1e 🌏: Add indonesia translation (#1563)
* Added Indonesian Language

* added indonesian to eng.tsx and general.tsx
2024-01-16 22:52:43 -05:00
Raí Santos
7b4e31ecc4 🎨: Updated Plugins Search Bar; 🌎: Added Translations (#1549)
* 🎨: layout search bar plugins: updated  / 🌎: translation update

* 🌎:Update Portuguese Translation

* fix: Refactored 'pluginstoredialog' code.

* chore(PopoverButtons): remove comments, re-organize imports

* chore: linting and reorganize useState declarations

* chore: linting and reorganize useState declarations

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2024-01-16 13:15:39 -05:00
Ganesh Krishnan
406940490b 🐳 docs: Update dotenv.md (#1564)
docker compose is now a plugin of docker
2024-01-15 17:52:32 -05:00
Danny Avila
dfe45f80c6 📕 fix: Update document Title based on appTitle and on "New Chat" (#1553) 2024-01-14 20:35:37 -05:00
Ikko Eltociear Ashimine
0f49642758 🧹 docs: update docker_override.md (#1546)
minor fix
2024-01-14 19:05:13 -05:00
Danny Avila
783f64a6e5 🏗️ fix(config): Resolve Paths Correctly for Helper Scripts on Linux and Windows (#1542) 2024-01-13 13:06:07 -05:00
Danny Avila
0c48a9dd6e 📑 docs: fix mistral ai api example safe_mode --> safe_prompt (#1541) 2024-01-13 08:19:09 -05:00
Tyler Mendenhall
690cb9caa1 📔 docs: Update Render hosting Guide (#1528)
* add the IntelliJ Idea config file to .gitignore

* Update the docs for using a user created key and restricting mongodb IP access to public IP addresses
2024-01-12 18:14:42 -05:00
Pascal Helfenstein
b9d2a8fbb2 🚆docs: update hetzner ubuntu example (#1539) 2024-01-12 18:12:39 -05:00
Danny Avila
74cf22b71b 📑 docs: Update Mistral AI API example about dropParams (#1538) 2024-01-11 15:50:04 -05:00
Danny Avila
d7b4ed3079 🐛 fix: Remove resendImages, imageDetail from modelOptions for Custom Endpoints (#1537) 2024-01-11 13:42:00 -05:00
Danny Avila
73f79a60f6 📄chore: Update Discord Link README.md 2024-01-11 11:41:31 -05:00
Marco Beretta
6542c71c2b 🚆docs: Railway support (#1529)
* Update README.md

* Update README.md

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2024-01-11 11:40:26 -05:00
Danny Avila
d20970f5c5 🚀 Feat: Streamline File Strategies & GPT-4-Vision Settings (#1535)
* chore: fix `endpoint` typescript issues and typo in console info message

* feat(api): files GET endpoint and save only file_id references to messages

* refactor(client): `useGetFiles` query hook, update file types, optimistic update of filesQuery on file upload

* refactor(buildTree): update to use params object and accept fileMap

* feat: map files to messages; refactor(ChatView): messages only available after files are fetched

* fix: fetch files only when authenticated

* feat(api): AppService
- rename app.locals.configs to app.locals.paths
- load custom config use fileStrategy from yaml config in app.locals

* refactor: separate Firebase and Local strategies, call based on config

* refactor: modularize file strategies and employ with use of DALL-E

* refactor(librechat.yaml): add fileStrategy field

* feat: add source to MongoFile schema, as well as BatchFile, and ExtendedFile types

* feat: employ file strategies for upload/delete files

* refactor(deleteFirebaseFile): add user id validation for firebase file deletion

* chore(deleteFirebaseFile): update jsdocs

* feat: employ strategies for vision requests

* fix(client): handle messages with deleted files

* fix(client): ensure `filesToDelete` always saves/sends `file.source`

* feat(openAI): configurable `resendImages` and `imageDetail`

* refactor(getTokenCountForMessage): recursive process only when array of Objects and only their values (not keys) aside from `image_url` types

* feat(OpenAIClient): calculateImageTokenCost

* chore: remove comment

* refactor(uploadAvatar): employ fileStrategy for avatars, from social logins or user upload

* docs: update docs on how to configure fileStrategy

* fix(ci): mock winston and winston related modules, update DALLE3.spec.js with changes made

* refactor(redis): change terminal message to reflect current development state

* fix(DALL-E-2): pass fileStrategy to dall-e
2024-01-11 11:37:54 -05:00
USAGI
28a6807176 🌏: Update Zh.tsx (#1527)
Chinese Translation Update
2024-01-11 11:37:42 -05:00
Raí Santos
79c1783e3d 🌎: Update Portuguese Translation (#1523) 2024-01-08 20:58:28 -05:00
MACHINSOFT
e3abd0d345 🌍: Correct Localized Text of "Save & Submit" Button (#1517)
* To provide a full translation option for the button in other languages

* Add a modification for the old version of the chat
2024-01-08 14:12:05 -05:00
Danny Avila
8f9ef13325 fix(getUserPluginAuthValue): throws error if no user matches (#1522) 2024-01-08 11:12:51 -05:00
Danny Avila
ead1c3c797 🔧 fix: Error Handling Improvements (#1518)
* style(Icon): remove error bubble from message icon

* fix(custom): `initializeClient` now throws error if apiKey or baseURL are admin provided but no env var was found

* refactor(tPresetSchema): match `conversationId` type to `tConversationSchema` but optional, use `extendedModelEndpointSchema` for `endpoint`

* fix(useSSE): minor improvements
- use `completed` set to avoid submitting unecessary abort request
- set preset with `newConversation` calls using initial conversation settings to prevent default Preset override as well as default settings
- return if there is a parsing error within `onerror` as expected errors from server are properly formatted
2024-01-08 09:30:38 -05:00
Danny Avila
c9aaf502af feat(saveImageFromUrl): Dynamic Extension Handling (#1514)
- Enhanced the `saveImageFromUrl` function to dynamically handle file extensions based on the content type of the fetched image.
- Replaced the method of appending a '.png' extension with a more robust approach using regular expressions and the path module.
- Allows for correct extension replacement or addition, ensuring filename consistency and compatibility with the actual image format.
- Prevents issues with double extensions (e.g., 'someimage.jpg.png') and aligns saved file types with their respective content types.
2024-01-07 15:22:33 -05:00
Danny Avila
050a92b318 🔄 refactor(config): Move connectWithTimeout Outside of Helpers Module (#1513) 2024-01-07 14:43:27 -05:00
Danny Avila
9144680ffb 🐛 fix: Load dotenv at Top of Call Stack (#1512) 2024-01-07 14:32:59 -05:00
Danny Avila
bebfffb2d9 🛠️ fix: Custom Endpoint issues, Improve SSE Response Handling (#1510)
* fix(custom): prevent presets using removed custom endpoints from causing frontend errors

* refactor(abortMiddleware): send 204 status when abortController is not found/active, set expected header `application/json` when not set

* fix(useSSE): general improvements:
- Add endpointType to fetch URL in useSSE hook
- use EndpointURLs enum
- handle 204 response by setting `data` to initiated response
- add better error handling UX, make clear when there is an explicit error
2024-01-07 13:49:59 -05:00
Raí Santos
84892b5b98 🌎: Update Portuguese Translation (#1505) 2024-01-07 13:49:41 -05:00
Danny Avila
24cb9957cd ⬆️ fix(SendButton): correct dark theme bg when enabled (#1503) 2024-01-06 15:57:47 -05:00
Danny Avila
e870e6e83f 🧹 docs: formatting fix for custom_config.md 2024-01-06 12:12:59 -05:00
Danny Avila
2990f32f48 docs: minor mkdocs formatting issue in custom_config.md 2024-01-06 12:03:29 -05:00
Marco Beretta
9838a9e29e ⚖️ Update LICENSE.md Year: 2023 -> 2024 (#1501) 2024-01-06 12:00:20 -05:00
Danny Avila
3183d6b678 🐳 : Further Docker build Cleanup & Docs Update (#1502)
* refactor: post-cleanup changes:
- add more unnecessary paths to .dockerignore
- remove librechat.yaml from main compose file (prevents from being required)
- do not create librechat.yaml during build (does nothing)

* docs: make config file instructions easier to read, more info throughout other docs

* docs: add custom config to menu

* Update custom_config.md

* Update docker_compose_install.md
2024-01-06 11:59:08 -05:00
Linus Gasser
5d7869d3d5 🐳 : Improving Docker Build (#1415)
* Improving builds

When adding LibreChat to ansible, it rebuilt way too often, even if I
only changed the configuration.
With this PR, it should build only when the files of the app change.
Also removed the 'volumes' section for the 'api' in the docker-compose.yml.
At least with our installation it works fine like this.

* @danny-avila's comments

- removed 'env_file' from docker-compose.yml
- re-added link to '.env' in volumes

* Adding latest changes from main

* @danny-avila's comments

* Updating installation instructions

* @danny-avila's comments

- Remove unused environment in docker-compose.yml
- Re-add some steps for cleaning docker images
2024-01-06 11:44:49 -05:00
Danny Avila
8848b8a569 refactor: Mount Config File in Docker and Add to .dockerignore (#1493) 2024-01-04 22:14:53 -05:00
Danny Avila
9864fc8700 🔧 fix: Improve Endpoint Handling and Address Edge Cases (#1486)
* fix(TEndpointsConfig): resolve property access issues with typesafe helper function

* fix: undefined or null endpoint edge case

* refactor(mapEndpoints -> endpoints): renamed module to be more general for endpoint handling, wrote unit tests, export all helpers
2024-01-04 10:17:15 -05:00
Danny Avila
42f2353509 🗨️ refactor: Open New Tab for Ctrl+Click or Button Combo in NewChat (#1484) 2024-01-03 19:34:41 -05:00
Danny Avila
e1a529b5ae 🧪 feat: Experimental: Enable Switching Endpoints Mid-Conversation (#1483)
* fix: load all existing conversation settings on refresh

* refactor(buildDefaultConvo): use `lastConversationSetup.endpointType` before `conversation.endpointType`

* refactor(TMessage/messageSchema): add `endpoint` field to messages to differentiate generation origin

* feat(useNewConvo): `keepLatestMessage` param to prevent reseting the `latestMessage` mid-conversation

* style(Settings): adjust height styling to allow more space in dialog for additional settings

* feat: Modular Chat: experimental setting to Enable switching Endpoints mid-conversation

* fix(ChatRoute): fix potential parsing issue with tPresetSchema
2024-01-03 19:17:42 -05:00
Danny Avila
4befee829b 🛠️ fix: Error Message Parsing and ChatOpenAI credentials (#1482)
* refactor(createLLM): ensure ChatOpenAI class always uses client-defined openAIApiKey; move typedefs to main def file

* refactor(useSSE): improve error message parsing in error handler
2024-01-03 14:26:13 -05:00
Danny Avila
d6d3d2ba13 🔧 fix: langchain packages mismatch, mount config file for deploy-compose.yaml, silence config not found error (#1481)
* fix(api): version mismatch between langchain packages `@langchain/google-genai` & `langchain`

* chore(loadYaml): silence config file not found error

* chore: improve firebase init message when not configured (generalized)

* fix(deploy-compose.yml): mount `librechat.yaml` config file
2024-01-03 10:59:38 -05:00
Danny Avila
ac9543a673 🎨 style: Add Dynamic Height to Endpoint/Model Menus (#1480)
* style(EndpointsMenu): add scrolling and dynamic height

* style(SelectDropDownPop): add dynamic height
2024-01-03 10:07:36 -05:00
Danny Avila
29473a72db 💫 feat: Config File & Custom Endpoints (#1474)
* WIP(backend/api): custom endpoint

* WIP(frontend/client): custom endpoint

* chore: adjust typedefs for configs

* refactor: use data-provider for cache keys and rename enums and custom endpoint for better clarity and compatibility

* feat: loadYaml utility

* refactor: rename back to  from  and proof-of-concept for creating schemas from user-defined defaults

* refactor: remove custom endpoint from default endpointsConfig as it will be exclusively managed by yaml config

* refactor(EndpointController): rename variables for clarity

* feat: initial load custom config

* feat(server/utils): add simple `isUserProvided` helper

* chore(types): update TConfig type

* refactor: remove custom endpoint handling from model services as will be handled by config, modularize fetching of models

* feat: loadCustomConfig, loadConfigEndpoints, loadConfigModels

* chore: reorganize server init imports, invoke loadCustomConfig

* refactor(loadConfigEndpoints/Models): return each custom endpoint as standalone endpoint

* refactor(Endpoint/ModelController): spread config values after default (temporary)

* chore(client): fix type issues

* WIP: first pass for multiple custom endpoints
- add endpointType to Conversation schema
- add update zod schemas for both convo/presets to allow non-EModelEndpoint value as endpoint (also using type assertion)
- use `endpointType` value as `endpoint` where mapping to type is necessary using this field
- use custom defined `endpoint` value and not type for mapping to modelsConfig
- misc: add return type to `getDefaultEndpoint`
- in `useNewConvo`, add the endpointType if it wasn't already added to conversation
- EndpointsMenu: use user-defined endpoint name as Title in menu
- TODO: custom icon via custom config, change unknown to robot icon

* refactor(parseConvo): pass args as an object and change where used accordingly; chore: comment out 'create schema' code

* chore: remove unused availableModels field in TConfig type

* refactor(parseCompactConvo): pass args as an object and change where used accordingly

* feat: chat through custom endpoint

* chore(message/convoSchemas): avoid saving empty arrays

* fix(BaseClient/saveMessageToDatabase): save endpointType

* refactor(ChatRoute): show Spinner if endpointsQuery or modelsQuery are still loading, which is apparent with slow fetching of models/remote config on first serve

* fix(useConversation): assign endpointType if it's missing

* fix(SaveAsPreset): pass real endpoint and endpointType when saving Preset)

* chore: recorganize types order for TConfig, add `iconURL`

* feat: custom endpoint icon support:
- use UnknownIcon in all icon contexts
- add mistral and openrouter as known endpoints, and add their icons
- iconURL support

* fix(presetSchema): move endpointType to default schema definitions shared between convoSchema and defaults

* refactor(Settings/OpenAI): remove legacy `isOpenAI` flag

* fix(OpenAIClient): do not invoke abortCompletion on completion error

* feat: add responseSender/label support for custom endpoints:
- use defaultModelLabel field in endpointOption
- add model defaults for custom endpoints in `getResponseSender`
- add `useGetSender` hook which uses EndpointsQuery to determine `defaultModelLabel`
- include defaultModelLabel from endpointConfig in custom endpoint client options
- pass `endpointType` to `getResponseSender`

* feat(OpenAIClient): use custom options from config file

* refactor: rename `defaultModelLabel` to `modelDisplayLabel`

* refactor(data-provider): separate concerns from `schemas` into `parsers`, `config`, and fix imports elsewhere

* feat: `iconURL` and extract environment variables from custom endpoint config values

* feat: custom config validation via zod schema, rename and move to `./projectRoot/librechat.yaml`

* docs: custom config docs and examples

* fix(OpenAIClient/mistral): mistral does not allow singular system message, also add `useChatCompletion` flag to use openai-node for title completions

* fix(custom/initializeClient): extract env var and use `isUserProvided` function

* Update librechat.example.yaml

* feat(InputWithLabel): add className props, and forwardRef

* fix(streamResponse): handle error edge case where either messages or convos query throws an error

* fix(useSSE): handle errorHandler edge cases where error response is and is not properly formatted from API, especially when a conversationId is not yet provided, which ensures stream is properly closed on error

* feat: user_provided keys for custom endpoints

* fix(config/endpointSchema): do not allow default endpoint values in custom endpoint `name`

* feat(loadConfigModels): extract env variables and optimize fetching models

* feat: support custom endpoint iconURL for messages and Nav

* feat(OpenAIClient): add/dropParams support

* docs: update docs with default params, add/dropParams, and notes to use config file instead of `OPENAI_REVERSE_PROXY`

* docs: update docs with additional notes

* feat(maxTokensMap): add mistral models (32k context)

* docs: update openrouter notes

* Update ai_setup.md

* docs(custom_config): add table of contents and fix note about custom name

* docs(custom_config): reorder ToC

* Update custom_config.md

* Add note about `max_tokens` field in custom_config.md
2024-01-03 09:22:48 -05:00
Fuegovic
3f98f92d4c remove 'MEILI_HTTP_ADDR' (#1475) 2024-01-03 08:59:55 -05:00
Danny Avila
2b3fa327a3 fix(OpenAIClient): do not invoke abortCompletion on completion error (#1473) 2024-01-02 08:40:26 -05:00
Marco Beretta
c7306395e9 👮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
2024-01-01 15:08:02 -05:00
Raí Santos
1cd5fdf4f0 🌎: Update Portuguese Translation (#1461) 2024-01-01 12:01:38 -05:00
Fuegovic
52142b47ec 🌎: Update French Translation (#1472)
* 🌎: Update French Translation

* 🌎: Update French Translation

* 🌎: Update French Translation

* 🌎: Update French Translation
2024-01-01 12:01:06 -05:00
Fuegovic
659ba4374b update pull_request_template.md (#1466)
add "Translation update" the the PR type choices
2023-12-30 22:50:47 -05:00
Danny Avila
431fc6284f 🛠️ 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
2023-12-30 14:34:32 -05:00
MACHINSOFT
e4c555f95a Add Russian translation for the new functionality in the settings (#1457) 2023-12-30 13:25:37 -05:00
Linus Gasser
1a95bef677 📃 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
2023-12-30 13:25:12 -05:00
Fuegovic
8735db0980 doc update: firebase.md (#1456) 2023-12-30 12:39:30 -05:00
Danny Avila
379e470e38 🧹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
2023-12-30 12:34:23 -05:00
Marco Beretta
f19f5dca8e 🔥🚀 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 <messagedaniel@protonmail.com>
2023-12-29 21:42:19 -05:00
Marco Beretta
bd4d23d314 🚫🔍 feat: disallow search indexing (#1409)
* feat: disallow search indexing

* Update index.js

* Update .env.example

* added middleware
2023-12-29 20:42:04 -05:00
Danny Avila
c3d5a08b26 🐛 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
2023-12-29 20:20:57 -05:00
Nikita Dybov
20971aa005 🌎: Update Russian translations (#1413)
* Update Russian localization

* Update Ru.tsx

* fix: russian translation typing errors
2023-12-29 17:28:59 -05:00
Marco Beretta
443b491286 feat: allow FWA (#1440) 2023-12-29 17:28:07 -05:00
Marco Beretta
8be2b6f380 🌎: Italian translation update & refactor: translations (#1414)
* italian translation update

* fix: removed some translations

* refactor(Translation)
2023-12-28 17:10:58 -05:00
Fuegovic
bce4f41fae 🪪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
2023-12-28 17:10:06 -05:00
Fuegovic
18cd02d44e Update French Translation (#1444) 2023-12-28 17:07:11 -05:00
Fuegovic
51050cc4d3 🧹📚 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
2023-12-22 08:36:42 -05:00
Danny Avila
5c27fa304a Update pull_request_template.md (#1417) 2023-12-22 07:26:24 -05:00
Danny Avila
5b28362282 Release v0.6.5 (#1391)
*  Release v0.6.5

* fix(ci): use dynamic currentDateString
2023-12-19 01:09:42 -05:00
Danny Avila
8d563d61f1 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
2023-12-18 18:43:50 -05:00
Danny Avila
c9d3e0ab6a 🚩 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
2023-12-17 18:56:01 -05:00
Danny Avila
7c2134fb12 fix: revert fonts resolution path in vite.config.ts 2023-12-17 16:36:24 -05:00
Danny Avila
0c326797dd 📸 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
2023-12-16 20:45:27 -05:00
Fuegovic
676f133545 🍏Update mac_install.md (#1373)
The manual installation guide for mac was very outdated. This brings it up to date with the current method
2023-12-16 20:26:47 -05:00
Danny Avila
2dfade1c42 Update package.json 2023-12-15 15:52:56 -05:00
Danny Avila
509b1e5c63 🔄 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
2023-12-15 15:47:40 -05:00
Danny Avila
0958db3825 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
2023-12-15 13:27:13 -05:00
Fuegovic
072a7e5f05 update: docker-compose.yaml 🐋 (#1341) 2023-12-15 02:41:33 -05:00
Danny Avila
ff59a2e41d fix: Avoid Throwing Errors for Unsupported Token Count Endpoints 🪙 (#1356) 2023-12-15 02:40:15 -05:00
Danny Avila
561ce8e86a 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
2023-12-15 02:18:07 -05:00
Danny Avila
d259431316 fix: Add error handling for missing role in OpenAIClient.js (#1352) 2023-12-14 10:53:40 -05:00
Danny Avila
ea1dd59ef4 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
2023-12-14 07:49:27 -05:00
Ed Burnette
49571ac635 chore: Get the latest of all github actions (#1335) 2023-12-14 07:44:38 -05:00
Fuegovic
1f5cb71a64 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
2023-12-14 07:44:20 -05:00
Fuegovic
bff365785a update: issue templates (#1346)
* update BUG-REPORT.yml

remove the contact field asking for the email address since it exposes them to the web when used

* update FEATURE-REQUEST.yml

remove the contact field since it exposes users email address

* Update QUESTION.yml

remove the contact field since it exposes users email address
2023-12-13 12:42:18 -05:00
Fuegovic
f2fc47e741 📚 documentation update (#1340) 2023-12-13 09:45:03 -05:00
Fuegovic
44755c964f update huggingface.md (#1342)
change the URL of the space to duplicate for a cleaner template I made
2023-12-13 06:52:21 -05:00
Danny Avila
fac2580a19 fix(librechat-data-provider): Update types paths in package.json (#1333)
* fix(librechat-data-provider): Update types paths in package.json

* chore: bump version

* fix(librechat-data-provider): Update types paths in react-query/package.json
2023-12-12 09:16:29 -05:00
Danny Avila
6829d66c1f Update docker_compose_install.md 2023-12-11 22:44:27 -05:00
Danny Avila
4df6a261d3 Update README.md 2023-12-11 22:43:37 -05:00
Danny Avila
e69644d7b4 fix: docker issues with volume mapping (#1330)
* fix: docker issues with volume mapping

* Update docker-compose.yml

---------

Co-authored-by: stunt_pilot <twitchstuntpilot@gmail.com>
2023-12-11 22:39:24 -05:00
Fuegovic
9db3d792cc update .env.example (#1327)
remove client_id and secret values for discord and github so they won't show in the UI without being properly configured
2023-12-11 17:23:38 -05:00
Danny Avila
df1dfa7d46 refactor: Use librechat-data-provider app-wide 🔄 (#1326)
* chore: bump vite, vitejs/plugin-react, mark client package as esm, move react-query as a peer dep in data-provider

* chore: import changes due to new data-provider export strategy, also fix type imports where applicable

* chore: export react-query services as separate to avoid react dependencies in /api/

* chore: suppress sourcemap warnings and polyfill node:path which is used by filenamify
TODO: replace filenamify with an alternative and REMOVE polyfill

* chore: /api/ changes to support `librechat-data-provider`

* refactor: rewrite Dockerfile.multi in light of /api/ changes to support `librechat-data-provider`

* chore: remove volume mapping to node_modules directories in default compose file

* chore: remove schemas from /api/ as is no longer needed with use of `librechat-data-provider`

* fix(ci): jest `librechat-data-provider/react-query` module resolution
2023-12-11 14:48:40 -05:00
Ed Burnette
d4c846b543 fix: fonts warning (#1318)
* fix: Fix a fonts warning
    The warning was:
    files in the public directory are served at the root path.
    Instead of /public/fonts/soehne-buch.woff2, use /fonts/soehne-buch.woff2.

* See if it likes /fonts better
2023-12-10 17:19:51 -05:00
Danny Avila
968b8ccdbd 🔒 fix: Robust Cache Reset on User Logout (#1324)
* refactor(Logout): rely on hooks for mutation behavior

* fix: logging out now correctly resets cache, disallowing any cache mixing between the next logged in user on the same browser

* chore: remove additional localStorage values on logout
2023-12-10 17:13:42 -05:00
Danny Avila
583e978a82 feat(Google): Support all Text/Chat Models, Response streaming, PaLM -> Google 🤖 (#1316)
* feat: update PaLM icons

* feat: add additional google models

* POC: formatting inputs for Vertex AI streaming

* refactor: move endpoints services outside of /routes dir to /services/Endpoints

* refactor: shorten schemas import

* refactor: rename PALM to GOOGLE

* feat: make Google editable endpoint

* feat: reusable Ask and Edit controllers based off Anthropic

* chore: organize imports/logic

* fix(parseConvo): include examples in googleSchema

* fix: google only allows odd number of messages to be sent

* fix: pass proxy to AnthropicClient

* refactor: change `google` altName to `Google`

* refactor: update getModelMaxTokens and related functions to handle maxTokensMap with nested endpoint model key/values

* refactor: google Icon and response sender changes (Codey and Google logo instead of PaLM in all cases)

* feat: google support for maxTokensMap

* feat: google updated endpoints with Ask/Edit controllers, buildOptions, and initializeClient

* feat(GoogleClient): now builds prompt for text models and supports real streaming from Vertex AI through langchain

* chore(GoogleClient): remove comments, left before for reference in git history

* docs: update google instructions (WIP)

* docs(apis_and_tokens.md): add images to google instructions

* docs: remove typo apis_and_tokens.md

* Update apis_and_tokens.md

* feat(Google): use default settings map, fully support context for both text and chat models, fully support examples for chat models

* chore: update more PaLM references to Google

* chore: move playwright out of workflows to avoid failing tests
2023-12-10 14:54:13 -05:00
Ed Burnette
8a1968b2f8 chore: Clean up the devcontainer files (#1317)
* Spruce up docker-compose for dev container

* Update devcontainer.json to remove unused lines

* It's /workspaces not /workspace
2023-12-10 09:49:21 -05:00
Marco Beretta
34d2da1ffc 📚 docs: Facebook update (#1312)
* Update user_auth_system.md

* Update user_auth_system.md

* Update user_auth_system.md
2023-12-08 14:05:24 -05:00
eniyiweb
427b05b891 🌎: Additional Turkish translations (#1310)
* Language translation:Turkish translation update

* 🌍: Turkish Translation - Update

* Additional Turkish translations

* Additional Turkish translations
2023-12-08 06:58:47 -05:00
Fuegovic
c2d8ae8616 📚 documentation update (#1307)
* fix .env.example

comment out DALLE_API_KEY by default and remove `user_provided` as default value since it is not supported

* 📚 docs update
2023-12-07 22:44:26 -05:00
Danny Avila
9041fe7472 feat: Make Nav Visible by Default on Clean Install 🧭 (#1309) 2023-12-07 22:42:14 -05:00
Marco Beretta
20b93ad065 feat: open sidebar popup for new users (#1308) 2023-12-07 22:40:23 -05:00
Danny Avila
10ace5fa75 🌎: Add Supported Languages to README.md 2023-12-07 14:37:56 -05:00
Fuegovic
b822cd48d2 bug fix: remove 3rd party code interpreter (#1306) 2023-12-07 14:27:52 -05:00
Ken Taniguchi
4d528efaf6 🇯🇵🗾: Japanese Translations (add and improve) (#1298) 2023-12-07 09:35:22 -05:00
Danny Avila
0bae503a0a refactor: Speed up Config fetching and Setup Config Groundwork 👷🚧 (#1297)
* refactor: move endpoint services to own directory

* refactor: make endpointconfig handling more concise, separate logic, and cache result for subsequent serving

* refactor: ModelController gets same treatment as EndpointController, draft OverrideController

* wip: flesh out override controller more to return real value

* refactor: client/api changes in anticipation of override
2023-12-06 19:36:57 -05:00
Danny Avila
9b2359fc27 🛠️ refactor: Improve Input Placeholder Handling and Error Management 🔄 (#1296)
* chore: identify new chat buttons with testid

* fix: avoid parsing error in useSSE, which causes errorHandler to fail

* fix: ensure last message isn't setting latestMessage when conversationId is `new` and text is the same due to possible re-renders

* refactor: set placeholder through inputRef and useEffect

* Update useSSE.ts

* Update useSSE.ts
2023-12-06 14:10:06 -05:00
Marco Beretta
2e390596ea return 404 instead of 200 (#1294) 2023-12-06 14:08:15 -05:00
Danny Avila
ca64efec1b feat: Implement Default Preset Selection for Conversations 📌 (#1275)
* fix: type issues with icons

* refactor: use react query for presets, show toasts on preset crud, refactor mutations, remove presetsQuery from Root (breaking change)

* refactor: change preset titling

* refactor: update preset schemas and methods for necessary new properties `order` and `defaultPreset`

* feat: add `defaultPreset` Recoil value

* refactor(getPresetTitle): make logic cleaner and more concise

* feat: complete UI portion of defaultPreset feature, with animations added to preset items

* chore: remove console.logs()

* feat: complete default preset handling

* refactor: remove user sensitive values on logout

* fix: allow endpoint selection without default preset overwriting
2023-12-06 14:00:15 -05:00
Marco Beretta
fdb65366d7 📧 feat: disable login (ALLOW_EMAIL_LOGIN) (#1282)
* added ALLOW_EMAIL_LOGIN

* update .env.example

* fix(config) email login true by default

* Update dotenv.md
2023-12-06 07:08:49 -05:00
Fuegovic
1706886a64 doc: PandoraNext guide (#1276) 2023-12-06 06:57:10 -05:00
Danny Avila
00b6af8c74 fix: Ensure Message Send Requires Key 🔑 (#1281)
* fix: only allow message send when key is provided when required
- create useRequiresKey hook
- pass same disabled prop to Textarea, AttachFile, and SendButton
- EndpointItem: add localization, stopPropagation, and remove commented code
- separate some hooks to new Input dir
- completely remove textareaHeight recoil state as is not needed
- update imports for moved hooks
- pass disabled prop to useTextarea

* feat: add localization to textarea placeholders
2023-12-05 09:38:04 -05:00
Fuegovic
f6118879e5 🔎 update: meilisearch (#1269) 2023-12-05 08:38:41 -05:00
MACHINSOFT
270031c783 🪟 Minor UI Styling Changes (#1260)
* Correct the display of the interface when hovering with the mouse

* Replace ring-1 with border completely.

* Fix it so that the text does not overlap with the SVG in mobile navigation.

* Remove the extra class -ml-0.5 -mt-0.5, there is no need to shift the buttons
2023-12-05 08:37:36 -05:00
Danny Avila
f1bc711cd7 🐛 fix: Prevent Node Server Crash Due to Unhandled ChatCompletionMessage Error (#1278)
* refactor(addTitle): avoid generating title when a request was aborted

* chore: bump openai to latest

* fix: catch OpenAIError Uncaught error as last resort

* fix: handle final messages excludes role=assistant

* Update OpenAIClient.js

* chore: fix linting errors
2023-12-04 22:58:23 -05:00
eniyiweb
076a9b9b9c 🇹🇷: Additional Turkish translations (#1265)
* Language translation:Turkish translation update

* 🌍: Turkish Translation - Update

* Additional Turkish translations
2023-12-04 16:50:33 -05:00
Danny Avila
329aa6d164 docs: update README. with features on top 2023-12-03 11:43:59 -05:00
Fuegovic
9d21d1c5b9 Refactor .env.example and Add dotenv.md (#1258) 2023-12-03 09:52:13 -05:00
Raí Santos
25f460f454 Fix: AzureAISearch Plugin Files 🔎 (#1259)
* Update azure_cognitive_search.md

* Updated: Azure Cognitive Search Plugin to Azure AI Search Plugin.

Update Docs: Azure Cognitive Search Plugin to Azure AI Search Plugin.

Updated:.env.example Azure Cognitive Search to Azure AI Search

Updated: mkdocs.yml link

Updated: SDK Azure 11.3.2 to 12.0.0

* fix:.env AZURE- to AZURE_

* Update azure_ai_search.md

* Updated:(api/package.json, package-lock.json): updated for new version the
  plugin (@azure/search-documents)

* fix:Resolved incorrect file name AzureAISearch

* fix:.env Azure AI Search

* fix:"-" to "_"

* Update Docs: Azure AI Search ith an improved tutorial featuring images and easier-to-understand instructions

fix: Change name of plugin "Azure Ai Search" to "Azure AI Search" i

* Update:Version of REST API versions (Azure AI Search)

* Update azure_ai_search.md

* Update azure_ai_search.md

* Update azure_ai_search.md

* fix: docs Azure AI Seach Images were not appearing.

* fix:Updated to the new repository with working APIs

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: o AzureAiSearch.js

* Atualizar o AzureAISearch.js

* Update/fix:EnvironmentVariablesForDeprecation

* fix:The file is outdated and needs to be updated.

* fix:The file is outdated and needs to be updated.

* update: translation portuguese brazilian

* Refactor:Improve Readability and Cleanliness of AzureAISearch Class

* Update AzureAiSearch.js

* Update AzureAISearch.js

* fix:getServiceEndpoint

* fix: getServiceEndpoint

* fix:AzureAISearch

* fix: Fix functions erros AzureAISearch

* fix: Update API_VERSION

* fix: AzureAISearch files outdated
2023-12-02 19:22:12 -05:00
Danny Avila
4674a54c70 refactor: Consolidate Message Scrolling & other Logic to Custom Hooks 🔄 (#1257)
* refactor: remove unnecessary drilling/invoking of ScrollToBottom
- feat: useMessageScrolling: consolidates all scrolling logic to hook
- feat: useMessageHelpers: creates message utilities and consolidates logic from UI component

* fix: ensure automatic scrolling is triggered by messagesTree re-render and is throttled
2023-12-01 19:54:09 -05:00
MACHINSOFT
ebd23f7295 🌎: Additional Russian translations (#1251) 2023-12-01 18:10:17 -05:00
Danny Avila
1d24f39830 style: update Mobile Nav with new ChatGPT styling; fix: New Chat on Mobile will correctly reset conversation state (#1255) 2023-12-01 17:00:57 -05:00
Danny Avila
3e7a29c9dd 🎨 fix: Update MessagesView Styling (#1254)
* style: update MessagesView to exact ChatGPT styling

* style(ScrollToBottom): make fixed for larger screens

* fix(ScrollToBottom): revert to absolute from fixed and change bottom values
2023-12-01 10:11:39 -05:00
Danny Avila
98827440eb fix(images/resize): invoke rotate to auto-orient the image based on the EXIF data (#1250) 2023-11-30 13:59:51 -05:00
Ishaan Jaff
2bcfb04a72 📚 docs: Add LiteLLM Proxy - Load balance 100+ LLMs & Spend Tracking ⚖️🤖📈 (#1249)
* (docs) add instructions on using litellm

* Update litellm.md

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-11-30 13:59:16 -05:00
Raí Santos
d327c8f5d2 Updated: Azure Cognitive Search Plugin/ Free AI APIs 🔎 (#1230)
* Update azure_cognitive_search.md

* Updated: Azure Cognitive Search Plugin to Azure AI Search Plugin.

Update Docs: Azure Cognitive Search Plugin to Azure AI Search Plugin.

Updated:.env.example Azure Cognitive Search to Azure AI Search

Updated: mkdocs.yml link

Updated: SDK Azure 11.3.2 to 12.0.0

* fix:.env AZURE- to AZURE_

* Update azure_ai_search.md

* Updated:(api/package.json, package-lock.json): updated for new version the
  plugin (@azure/search-documents)

* fix:Resolved incorrect file name AzureAISearch

* fix:.env Azure AI Search

* fix:"-" to "_"

* Update Docs: Azure AI Search ith an improved tutorial featuring images and easier-to-understand instructions

fix: Change name of plugin "Azure Ai Search" to "Azure AI Search" i

* Update:Version of REST API versions (Azure AI Search)

* Update azure_ai_search.md

* Update azure_ai_search.md

* Update azure_ai_search.md

* fix: docs Azure AI Seach Images were not appearing.

* fix:Updated to the new repository with working APIs

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: Added Compatibility for Previous Environment Variable Names in AzureAISearch Plugin

* Update: o AzureAiSearch.js

* Atualizar o AzureAISearch.js

* Update/fix:EnvironmentVariablesForDeprecation

* fix:The file is outdated and needs to be updated.

* fix:The file is outdated and needs to be updated.

* update: translation portuguese brazilian

* Refactor:Improve Readability and Cleanliness of AzureAISearch Class

* Update AzureAiSearch.js

* Update AzureAISearch.js
2023-11-30 13:50:28 -05:00
Fuegovic
690acf1c93 docs: mongo-express 🍃 (#1247)
* docs: mongo-express

* Update manage_your_database.md

add screenshot and mongodb logo

* Update manage_your_database.md

upload smaller mongodb logo
2023-11-30 13:49:39 -05:00
Danny Avila
53d0ffcd11 Fix: catch uncaught OpenAIError handling in server index.js and log info (#1248) 2023-11-30 13:29:55 -05:00
Danny Avila
94df631c44 fix(PluginsClient/getFunctionModelName): returns input if agent model is function capable (#1246) 2023-11-30 12:50:52 -05:00
Danny Avila
166a4fa44f 🛠️ fix: Improve SSE Handling and Fix Typo in sendEmail Template (#1245)
* fix: typo for passwordReset.handlebars

* fix(useSSE): prevent unnecessary JSON.parse abort error, handle immediate abort-submit gracefully by reverting to previous state before immediate abort-submit, add showStopButton state to explicitly render disabled sendButton when message generation is cancelled, filter undefined messages and replace undefined convo for cancelHandler
2023-11-30 10:23:57 -05:00
Danny Avila
e13b146d6d 🔧 fix: Correct Properties Passed to getResponseSender, Catch OpenAI Errors (#1244)
* fix: attempt to catch more errors, especially when generation started

* fix: pass the right properties to getResponseSender

* chore: Update .eslintrc.js and fix sendEmail.js linting errors
2023-11-29 13:40:16 -05:00
David
ae03267d9b 📧 feat: Allow usage of custom SMTP server (#1219)
Co-authored-by: David Reis <post@d-reis.com>
2023-11-28 18:00:07 -05:00
Marco Beretta
3838ff4617 🌍: Dutch translation, fix translation, style(Dropdown): added scroll (#1218)
* italian translation

* some translation fixes + dutch translation

* fix(Dropdown) more dynamic

* fix(Nl)

* fix(Nl)

* added comment in Nl.tsx
2023-11-28 17:42:31 -05:00
Jonas Wunderlich
822914d521 fix: conversation title truncation (#1237) 2023-11-27 21:07:32 -05:00
Danny Avila
f5f5b2bbdb fix: Resolve Token Credit Balance Issues for Instruct Models 🛠️ (#1232)
* Fix: balance update error and add environment variable check

* fix(ChatGPTClient): return promptTokens for instruct/davinci models

* chore: remove unnecessary comments
2023-11-26 18:12:27 -05:00
Danny Avila
d7ef4590ea 🔧 Fix: Resolve Anthropic Client Issues 🧠 (#1226)
* fix: correct preset title for Anthropic endpoint

* fix(Settings/Anthropic): show correct default value for LLM temperature

* fix(AnthropicClient): use `getModelMaxTokens` to get the correct LLM max context tokens, correctly set default temperature to 1, use only 2 params for class constructor, use `getResponseSender` to add correct sender to response message

* refactor(/api/ask|edit/anthropic): save messages to database after the final response is sent to the client, and do not save conversation from route controller

* fix(initializeClient/anthropic): correctly pass client options (endpointOption) to class initialization

* feat(ModelService/Anthropic): add claude-1.2
2023-11-26 14:44:57 -05:00
Danny Avila
4b289640f2 fix: will correctly switch endpoints and reset the convo if the first message returned was an error (#1225) 2023-11-26 14:24:43 -05:00
Danny Avila
12209fe0dd refactor: address potential issues with deploy-compose.yml (#1220)
* chore: remove /config/loader

* chore: remove config/loader steps from Dockerfile.multi

* chore: remove install script
2023-11-25 16:34:51 -05:00
Danny Avila
4dab094855 fix: prevent convo overwrite on convo refresh and combine with remote models handling (#1217) 2023-11-25 09:19:13 -05:00
MACHINSOFT
ebe62ad250 style: Login and registration 🛂 (#1214) 2023-11-24 16:49:14 -05:00
Danny Avila
cc39074e0a 🛠️ refactor: Handle .webp, Improve File Life Cycle 📁 (#1213)
* fix: handle webp images correctly

* refactor: use the userPath from the start of the filecycle to avoid handling the blob, whose loading may fail upon user request

* refactor: delete temp files on reload and new chat
2023-11-24 16:45:06 -05:00
Danny Avila
650759306d feat: deploy-compose NGINX volume mapping, add image removal to update script (#1211) 2023-11-22 23:35:08 -05:00
Danny Avila
398687fad0 fix: Increase client_max_body_size for larger file uploads through nginx (#1210) 2023-11-22 19:07:01 -05:00
Danny Avila
55cdd2eec6 refactor: use fileSize limiternative to multer (#1209) 2023-11-22 18:42:21 -05:00
Danny Avila
5e6f8cbce7 fix: Correct Default Model Name in Response Sender and Update Anthropics 🤖 (#1208)
* feat: add claude-2.1 to default anthropic models

* chore: remove console log in NavLinks

* fix: issue with response sender not using model name, change anthropic default value to Claude

* fix: preset will not be selected on edit
2023-11-22 18:29:09 -05:00
Danny Avila
f3402401f1 feat: Order/disable endpoints with ENDPOINTS env var (#1206)
* fix: endpoint will not be select if disabled

* feat: order and disable endpoints with ENDPOINTS env var

* chore: remove console.log
2023-11-22 13:56:38 -05:00
Danny Avila
f05f6826f5 fix: Textarea Scroll fix, Duplicate Key fix (#1204)
* fix(Es): duplicate keys

* fix(Textarea): overflowY auto
2023-11-22 08:29:22 -05:00
Danny Avila
317cdd3f77 feat: Vision Support + New UI (#1203)
* feat: add timer duration to showToast, show toast for preset selection

* refactor: replace old /chat/ route with /c/. e2e tests will fail here

* refactor: move typedefs to root of /api/ and add a few to assistant types in TS

* refactor: reorganize data-provider imports, fix dependency cycle, strategize new plan to separate react dependent packages

* feat: add dataService for uploading images

* feat(data-provider): add mutation keys

* feat: file resizing and upload

* WIP: initial API image handling

* fix: catch JSON.parse of localStorage tools

* chore: experimental: use module-alias for absolute imports

* refactor: change temp_file_id strategy

* fix: updating files state by using Map and defining react query callbacks in a way that keeps them during component unmount, initial delete handling

* feat: properly handle file deletion

* refactor: unexpose complete filepath and resize from server for higher fidelity

* fix: make sure resized height, width is saved, catch bad requests

* refactor: use absolute imports

* fix: prevent setOptions from being called more than once for OpenAIClient, made note to fix for PluginsClient

* refactor: import supportsFiles and models vars from schemas

* fix: correctly replace temp file id

* refactor(BaseClient): use absolute imports, pass message 'opts' to buildMessages method, count tokens for nested objects/arrays

* feat: add validateVisionModel to determine if model has vision capabilities

* chore(checkBalance): update jsdoc

* feat: formatVisionMessage: change message content format dependent on role and image_urls passed

* refactor: add usage to File schema, make create and updateFile, correctly set and remove TTL

* feat: working vision support
TODO: file size, type, amount validations, making sure they are styled right, and making sure you can add images from the clipboard/dragging

* feat: clipboard support for uploading images

* feat: handle files on drop to screen, refactor top level view code to Presentation component so the useDragHelpers hook  has ChatContext

* fix(Images): replace uploaded images in place

* feat: add filepath validation to protect sensitive files

* fix: ensure correct file_ids are push and not the Map key values

* fix(ToastContext): type issue

* feat: add basic file validation

* fix(useDragHelpers): correct context issue with `files` dependency

* refactor: consolidate setErrors logic to setError

* feat: add dialog Image overlay on image click

* fix: close endpoints menu on click

* chore: set detail to auto, make note for configuration

* fix: react warning (button desc. of button)

* refactor: optimize filepath handling, pass file_ids to images for easier re-use

* refactor: optimize image file handling, allow re-using files in regen, pass more file metadata in messages

* feat: lazy loading images including use of upload preview

* fix: SetKeyDialog closing, stopPropagation on Dialog content click

* style(EndpointMenuItem): tighten up the style, fix dark theme showing in lightmode, make menu more ux friendly

* style: change maxheight of all settings textareas to 138px from 300px

* style: better styling for textarea and enclosing buttons

* refactor(PresetItems): swap back edit and delete icons

* feat: make textarea placeholder dynamic to endpoint

* style: show user hover buttons only on hover when message is streaming

* fix: ordered list not going past 9, fix css

* feat: add User/AI labels; style: hide loading spinner

* feat: add back custom footer, change original footer text

* feat: dynamic landing icons based on endpoint

* chore: comment out assistants route

* fix: autoScroll to newest on /c/ view

* fix: Export Conversation on new UI

* style: match message style of official more closely

* ci: fix api jest unit tests, comment out e2e tests for now as they will fail until addressed

* feat: more file validation and use blob in preview field, not filepath, to fix temp deletion

* feat: filefilter for multer

* feat: better AI labels based on custom name, model, and endpoint instead of  `ChatGPT`
2023-11-21 20:12:48 -05:00
eniyiweb
345f4b2e85 🌍: Turkish Translation - Update (#1195)
* Language translation:Turkish translation update

* 🌍: Turkish Translation - Update
2023-11-21 11:33:04 -05:00
madonchik123
d043a849a9 Added Reverse Proxy for Anthropic (#1106)
* Update AnthropicClient.js

Added BaseURL

* Update .env.example

Added ANTHROPIC_REVERSE_PROXY ENV

* Update initializeClient.js

Added Reverse_Proxy

* Update .env.example

* Update initializeClient.js

* Update AnthropicClient.js

* Update .env.example

Request

* Update initializeClient.js

Mae ANTHROPIC_REVERSE_PROXY let instead of const

* fix: lint errors, refactor(initializeClient)

* chore: change casing of reverseProxy

---------

Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-11-20 20:12:53 -05:00
Raí Santos
b7dcc4264d 🌍: Portuguese and Spanish Translation - Update (#1202)
* Update Translation to Portugues Brasil

* Update Translation to Language Portugues Brasileiro

* Update Translation to Language Español

* Fix Translation to Language Español

* Update Es.tsx

* Update Br.tsx
2023-11-19 16:03:27 -05:00
Marco Beretta
ab5c81d063 style: Beta UI fixes (#1199)
* fix(EditPresetDialog) endpoint menu

* style: update anthropic's icon & removed hover:bg in PresetItems

* style(OptionsPopover) rounded SaveAsPreset

* style(PresetItems) removed hover:bg

* style(NavLinks) match to openai

* style(NavLinks)

* fix(EditPresetDialog): remove empty cn call

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-11-18 16:45:51 -05:00
ngoctuanai
1fc896d0bd Update Vi.tsx (#1200) 2023-11-18 10:42:50 -05:00
Danny Avila
1ba8d4ffa9 style: Minor Beta UI fixes (#1197)
* style(Header): hide scrollbar but still allow side scroll/swipe/drag/touch

* feat: make menu buttons flexiblewith min-width, delete passed in classes, add pointer-cursor

* refactor: use conditional for visibility of plugins settings

* fix: make advanced settings popover appear over nav

* refactor(textarea): minor padding restyling, add max height

* style: make menuItem checkmark invisible instead of hidden so it takes up width space

* style: make presetsMenu trigger an icon button, remove max-width of presets except in mobile view

* style: improve advanced settings mobile styling

* feat: newchat and convo items toggle nav on small screens

* style: improve no presets view

* style: make sure toggle hover effect does not apply on smaller screens
2023-11-17 08:00:42 -05:00
Danny Avila
c64970525b feat: allow any reverse proxy URLs, add proxy support to model fetching (#1192)
* feat: allow any reverse proxy URLs

* feat: add proxy support to model fetching
2023-11-16 18:56:09 -05:00
Danny Avila
bac1fb67d2 WIP: Update UI to match Official Style; Vision and Assistants 👷🏽 (#1190)
* wip: initial client side code

* wip: initial api code

* refactor: export query keys from own module, export assistant hooks

* refactor(SelectDropDown): more customization via props

* feat: create Assistant and render real Assistants

* refactor: major refactor of UI components to allow multi-chat, working alongside CreationPanel

* refactor: move assistant routes to own directory

* fix(CreationHeader): state issue with assistant select

* refactor: style changes for form, fix setSiblingIdx from useChatHelpers to use latestMessageParentId, fix render issue with ChatView and change location

* feat: parseCompactConvo: begin refactor of slimmer JSON payloads between client/api

* refactor(endpoints): add assistant endpoint, also use EModelEndpoint as much as possible

* refactor(useGetConversationsQuery): use object to access query data easily

* fix(MultiMessage): react warning of bad state set, making use of effect during render (instead of useEffect)

* fix(useNewConvo): use correct atom key (index instead of convoId) for reset latestMessageFamily

* refactor: make routing navigation/conversation change simpler

* chore: add removeNullishValues for smaller payloads, remove unused fields, setup frontend pinging of assistant endpoint

* WIP: initial complete assistant run handling

* fix: CreationPanel form correctly setting internal state

* refactor(api/assistants/chat): revise functions to working run handling strategy

* refactor(UI): initial major refactor of ChatForm and options

* feat: textarea hook

* refactor: useAuthRedirect hook and change directory name

* feat: add ChatRoute (/c/), make optionsBar absolute and change on textarea height, add temp header

* feat: match new toggle Nav open button to ChatGPT's

* feat: add OpenAI custom classnames

* feat: useOriginNavigate

* feat: messages loading view

* fix: conversation navigation and effects

* refactor: make toggle change nav opacity

* WIP: new endpoint menu

* feat: NewEndpointsMenu complete

* fix: ensure set key dialog shows on endpoint change, and new conversation resets messages

* WIP: textarea styling fix, add temp footer, create basic file handling component

* feat: image file handling (UI)

* feat: PopOver and ModelSelect in Header, remove GenButtons

* feat: drop file handling

* refactor: bug fixes
use SSE at route level
add opts to useOriginNavigate
delay render of unfinishedMessage to avoid flickering
pass params (convoId) to chatHelpers to set messages query data based on param when the route is new (fixes can't continue convo on /new/)
style(MessagesView): matches height to official
fix(SSE): pass paramId and invalidate convos
style(Message): make bg uniform

* refactor(useSSE): setStorage within setConversation updates

* feat: conversationKeysAtom, allConversationsSelector, update convos query data on created message (if new), correctly handle convo deletion (individual)

* feat: add popover select dropdowns to allow options in header while allowing horizontal scroll for mobile

* style(pluginsSelect): styling changes

* refactor(NewEndpointsMenu): make UI components modular

* feat: Presets complete

* fix: preset editing, make by index

* fix: conversations not setting on inital navigation, fix getMessages() based on query param

* fix: changing preset no longer resets latestMessage

* feat: useOnClickOutside for OptionsPopover and fix bug that causes selection of preset when deleting

* fix: revert /chat/ switchToConvo, also use NewDeleteButton in Convo

* fix: Popover correctly closes on close Popover button using custom condition for useOnClickOutside

* style: new message and nav styling

* style: hover/sibling buttons and preset menu scrolling

* feat: new convo header button

* style(Textarea): minor style changes to textarea buttons

* feat: stop/continue generating and hide hoverbuttons when submitting

* feat: compact AI Provider schemas to make json payloads and db saves smaller

* style: styling changes for consistency on chat route

* fix: created usePresetIndexOptions to prevent bugs between /c/ and /chat/ routes when editing presets, removed redundant code from the new dialog

* chore: make /chat/ route default for now since we still lack full image support
2023-11-16 10:42:24 -05:00
Danny Avila
adbeb46399 v0.6.1 (#1189) 2023-11-16 08:53:09 -05:00
Marco Beretta
9ad47b6660 style: update graphics (#1138)
* style: update new icon and NavLinks scale

* style: new username update

* refactor(Dropdown); style: general settings

* style(Dropdown); adjust theme

* style: dropdown and settings text

* fix(Dropdown) system theme not working

* style: topbar sticky; fix: general's menu settings transparent with light theme

* fix(SubmitButton) stop generate button

* fix: user_provided dialog for new dropdown

* fix: TS error 'display'

* fix(EditPresetDialog): for new dropdown

* style: added green send button

* converted textchat in tsx

* style(SubmitButton): tooltip

* test: fixed ThemeSelector and LangSelector

* removed transition-opacity

* fix all tests

* removed empty cn call

* chore: Update General.tsx to add Arabic option

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-11-16 08:42:03 -05:00
Z1xus
8b28fdf240 style: update symbols for the next/previous page buttons to make navigation more intuitive (#1182) 2023-11-16 08:35:18 -05:00
QuitCool
1ec8e53db8 🌐: Add Arabic Translations (#1180)
* 🌐: Add Arabic Translations

* 🌐: Add Arabic Translations (Updated)
2023-11-16 08:34:26 -05:00
Fuegovic
405be4b408 fix: .env.example (#1185)
Comment out `UID` and `GID` by default in the .env.example file
2023-11-15 19:17:40 -05:00
Fuegovic
b171369aa6 🌍: French translation - Update (#1176) 2023-11-15 07:29:47 -05:00
Marco Beretta
ddb42b23cb added com_ui_stop in italian (#1175) 2023-11-15 07:29:33 -05:00
USAGI
037ea8cc0b Fix: style and translation issues (#1172)
* Update Chinese Translation

* Update

* Update

* Update Anthropic UI
2023-11-15 07:29:11 -05:00
Danny Avila
e383ecba85 chore: bump langchain (#1174) 2023-11-13 11:17:43 -05:00
Danny Avila
c7205c9bb2 feat: Add DALL-E reverse proxy settings and handle errors in image generation (#1173)
* feat: Add DALL-E reverse proxy settings and handle errors in image generation

* fix(ci): avoid importing extra utilities
2023-11-13 11:05:59 -05:00
Fuegovic
25402fd208 doc fix: license ⚖️ (#1171)
* add: License

* Delete .github/LICENSE.md
2023-11-12 20:49:41 -05:00
MACHINSOFT
216f6da79e 🌐: Add Russian Translations (#1169)
* Add translation for the button and some elements.

* Make changes for tests.
2023-11-12 20:49:12 -05:00
eniyiweb
cbfe47a9d5 🌐: Turkish translations (#1168)
* Language translation: Turkish translation

* Language translation: Turkish translation
2023-11-12 20:49:02 -05:00
USAGI
e5e04c1cb8 🌐: Update simplified Chinese translation (#1152)
* Completing simplified Chinese translations

* Update

* Update CN translation.
2023-11-12 20:48:34 -05:00
Danny Avila
5d95433c83 chore: remove jose as Bun now supports JWT 🍞 (#1167)
* chore: remove jose as Bun now supports JWT

* chore: npm audit
2023-11-12 00:44:46 -05:00
Danny Avila
9ca84edb9a fix(openai/completions): use old method for instruct/davinci/text gen models (#1166) 2023-11-10 10:33:56 -05:00
Danny Avila
d5259e1525 feat(OpenAIClient): AZURE_USE_MODEL_AS_DEPLOYMENT_NAME, AZURE_OPENAI_DEFAULT_MODEL (#1165)
* feat(OpenAIClient): AZURE_USE_MODEL_AS_DEPLOYMENT_NAME, AZURE_OPENAI_DEFAULT_MODEL

* ci: fix initializeClient test
2023-11-10 09:58:17 -05:00
Cfomodz
9d100ec0fc docs: Update heroku.md 📄 (#1160)
* Update heroku.md

Copying the config/install.js expected by RUN npm ci
Heroku CLI would not take the push without this and errored out consistently due to the expected file being missing.
2023-11-10 07:41:18 -05:00
Danny Avila
efe057e0d8 fix: correctly pass modelsData to newConversation, also prevent navigation in this case (#1162) 2023-11-09 14:32:03 -05:00
Danny Avila
5ab9802aa9 fix(OpenAIClient): use official SDK to identify client and avoid false Rate Limit Error (#1161)
* chore: add eslint ignore unused var pattern

* feat: add extractBaseURL helper for valid OpenAI reverse proxies, with tests

* feat(OpenAIClient): add new chatCompletion using official OpenAI node SDK

* fix(ci): revert change to FORCE_PROMPT condition
2023-11-09 14:04:36 -05:00
Fuegovic
ed3d7c9f80 docs: Update docker_compose_install.md 📚(#1153)
* Update docker_compose_install.md

add instruction to stop the container

* Update docker_compose_install.md

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-11-08 13:13:10 -05:00
Jacob Root
9d565ec8a5 docs: manual user creation 📖 (#1151) 2023-11-08 07:54:50 -05:00
Danny Avila
43d7a751d6 feat: allow config of DALL-E-3 System Prompt via env 🎨 (#1150) 2023-11-07 18:52:23 -05:00
Danny Avila
4f3b66756a refactor: condense dall-e instructions, add style parameter (#1148) 2023-11-06 20:07:01 -05:00
Danny Avila
3a38b4b842 feat: DALL-E-3 support 🎨 (#1147)
* feat: DALL-E-3 support

* fix(ci): lock-in openai dependency for types used in data-provider
2023-11-06 19:45:59 -05:00
Danny Avila
48c087cc06 chore: add token rate support for 11/06 models (#1146)
* chore: update model rates with 11/06 rates

* chore: add new models to env.example for OPENAI_MODELS

* chore: reference actual maxTokensMap in ci tests
2023-11-06 15:26:16 -05:00
Danny Avila
4b63eb5a2c fix: correct conditional statement in ModelService.js (#1145) 2023-11-06 14:42:20 -05:00
Danny Avila
5f3ecef575 fix(config/scripts): Enhance User Creation and Ban Handling, Standardize Imports (#1144)
* chore: use relative imports for scripts

* fix(create-user): newUser.save() now properly awaited, double-check user creation, use relative imports, catch exception

* fix(ban-user): catch exception, handle case where IP is undefined, proper check of user ban on login
2023-11-06 09:19:43 -05:00
Danny Avila
a2ee57568a fix: force navigation to /chat/new on endpoint change and conversation deletions (#1141) 2023-11-04 20:33:24 -04:00
Danny Avila
0886441461 feat(azureOpenAI): Allow Switching Deployment Name by Model Name (#1137)
* feat(azureOpenAI): allow switching deployment name by model name

* ci: add unit tests and throw error on no api key provided to avoid API call

* fix(gptPlugins/initializeClient): check if azure is enabled; ci: add unit tests for gptPlugins/initializeClient

* fix(ci): fix expected error message for partial regex match:  unexpected token
2023-11-04 15:03:31 -04:00
Marco Beretta
a7b5639da1 feat: ban-user command (#1121)
* feat: ban-user command

* clean up code

* added duration

* fix(package-lock) revert commit
2023-11-04 11:38:58 -04:00
Marco Beretta
34148885b7 Update windows_install.md (#1132) 2023-11-02 19:04:51 -04:00
ngoctuanai
c11fbde9a7 🌐: Vietnamese translation (#1129)
* Create vi.tsx

* Rename vi.tsx to Vi.tsx

* Update Translation.tsx

* Update General.tsx

* Update Vi.tsx

* Update Translation.tsx

* Update Vi.tsx

* Create ViTraditional.tsx

* Update Translation.tsx

* Delete client/src/localization/languages/ViTraditional.tsx

* Update Vi.tsx

* fix and clean up

---------

Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
2023-11-02 17:37:40 -04:00
Raí
9a31df026d 🌐: Update Brasil translation for general settings (#1126) 2023-11-01 08:01:38 -04:00
Super12138
b031dea127 🌐: Update Chinese translation (#1115)
* Update Chinese translation

* Fix bug

* Add new string in Chinese String

* Update Chinese translation and add more strings
2023-10-30 10:05:20 -04:00
Danny Avila
9f5d77eeb0 feat(useToastContext): add useContext hook for simpler use (#1125) 2023-10-30 10:00:20 -04:00
Danny Avila
8f328ec6a3 feat(Tx): Add timestamps to transaction schema (#1123) 2023-10-30 09:41:07 -04:00
Danny Avila
af69763103 refactor(addImages): use in functions agent response and assure generated images are included in the response (#1120) 2023-10-29 15:36:00 -04:00
Danny Avila
5c1e44eff7 feat(OpenAIClient): Add HttpsProxyAgent to initializeLLM (#1119)
* feat(OpenAIClient): Add HttpsProxyAgent to initializeLLM

* chore: fix linting error in ModelService
2023-10-29 13:20:30 -04:00
Danny Avila
7b30ab3a41 docs: Add LibreChat reverse proxy setup with Basic Auth & Misc section (#1118) 2023-10-29 12:23:25 -04:00
Danny Avila
2017ec5693 docs: Update installation instructions for Docker and (#1117)
local (npm) on Linux, macOS, and Windows
2023-10-29 11:52:31 -04:00
Marco Beretta
c878289adc docs: fix typo "nginx" (#1116)
* Update linode.md

* Update cloudflare.md
2023-10-29 10:56:21 -04:00
Danny Avila
5cafe0900c feat(client): Toast Provider to show Toasts from higher on the DOM tree (#1110) 2023-10-27 17:10:07 -04:00
Danny Avila
81a90d245b feat(Toast): add Toast nearly identical to ChatGPT's (#1108) 2023-10-27 15:48:05 -04:00
Walber Cardoso
ba5ab86037 Update ModelService.js (#1105)
Failed to fetch models from OpenAI API when set OPENROUTER_API_KEY on .env file
2023-10-26 21:18:03 -04:00
Super12138
11dd3b487f 🌐: Update Chinese translation (#1102)
* Update Chinese translation

* Fix bug
2023-10-24 20:04:44 -04:00
Danny Avila
bc39bd12a5 chore: make tag workflow push only, make manual workflow from main (#1100) 2023-10-23 21:37:21 -04:00
Danny Avila
05c4c7e551 feat: add CUSTOM_FOOTER env variable (#1098) 2023-10-23 21:08:18 -04:00
Danny Avila
4ce585f77d chore: allow manual trigger of tag image workflow (#1099) 2023-10-23 20:31:10 -04:00
Danny Avila
c7bfb2ab40 hotfix(useGetEndpointsQuery): set default variable value when transforming data with select (#1097) 2023-10-23 17:48:18 -04:00
liukaixiang817
3d4a8778d5 Update the localisation of Chinese and Traditional Chinese (#1095)
* Update the localization of Chinese and Traditional Chinese

Update the localization of ""

* 12345

* 12345

12345

* Revert "12345"

This reverts commit 70483dbb48.

* Revert "12345"

This reverts commit db0f554b02.
2023-10-23 17:48:01 -04:00
1192 changed files with 149866 additions and 24354 deletions

5
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,5 @@
FROM node:18-bullseye
RUN useradd -m -s /bin/bash vscode
RUN mkdir -p /workspaces && chown -R vscode:vscode /workspaces
WORKDIR /workspaces

View File

@@ -1,58 +1,18 @@
// {
// "name": "LibreChat_dev",
// // Update the 'dockerComposeFile' list if you have more compose files or use different names.
// "dockerComposeFile": "docker-compose.yml",
// // The 'service' property is the name of the service for the container that VS Code should
// // use. Update this value and .devcontainer/docker-compose.yml to the real service name.
// "service": "librechat",
// // The 'workspaceFolder' property is the path VS Code should open by default when
// // connected. Corresponds to a volume mount in .devcontainer/docker-compose.yml
// "workspaceFolder": "/workspace"
// //,
// // // Set *default* container specific settings.json values on container create.
// // "settings": {},
// // // Add the IDs of extensions you want installed when the container is created.
// // "extensions": [],
// // Uncomment the next line if you want to keep your containers running after VS Code shuts down.
// // "shutdownAction": "none",
// // Uncomment the next line to use 'postCreateCommand' to run commands after the container is created.
// // "postCreateCommand": "uname -a",
// // Comment out to connect as root instead. To add a non-root user, see: https://aka.ms/vscode-remote/containers/non-root.
// // "remoteUser": "vscode"
// }
{
// "name": "LibreChat_dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
// "image": "node:19-alpine",
// "workspaceFolder": "/workspaces",
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
// "overrideCommand": true,
"customizations": {
"vscode": {
"extensions": [],
"settings": {
"terminal.integrated.profiles.linux": {
"bash": null
}
}
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces",
"customizations": {
"vscode": {
"extensions": [],
"settings": {
"terminal.integrated.profiles.linux": {
"bash": null
}
},
"postCreateCommand": "",
// "workspaceMount": "src=${localWorkspaceFolder},dst=/code,type=bind,consistency=cached"
// "runArgs": [
// "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined",
// "-v", "/tmp/.X11-unix:/tmp/.X11-unix",
// "-v", "${env:XAUTHORITY}:/root/.Xauthority:rw",
// "-v", "/home/${env:USER}/.cdh:/root/.cdh",
// "-e", "DISPLAY=${env:DISPLAY}",
// "--name=tgw_assistant_backend_dev",
// "--network=host"
// ],
// "settings": {
// "terminal.integrated.shell.linux": "/bin/bash"
// },
"features": {"ghcr.io/devcontainers/features/git:1": {}}
}
}
},
"postCreateCommand": "",
"features": { "ghcr.io/devcontainers/features/git:1": {} },
"remoteUser": "vscode"
}

View File

@@ -1,17 +1,11 @@
version: '3.4'
version: "3.8"
services:
app:
# container_name: LibreChat_dev
image: node:19-bullseye
# Using a Dockerfile is optional, but included for completeness.
# build:
# context: .
# dockerfile: Dockerfile
# # [Optional] You can use build args to set options. e.g. 'VARIANT' below affects the image in the Dockerfile
# args:
# VARIANT: buster
# network_mode: "host"
build:
context: ..
dockerfile: .devcontainer/Dockerfile
# restart: always
links:
- mongodb
- meilisearch
@@ -21,17 +15,16 @@ services:
- "host.docker.internal:host-gateway"
volumes:
# # This is where VS Code should expect to find your project's source code and the value of "workspaceFolder" in .devcontainer/devcontainer.json
- ..:/workspace:cached
# # - /app/client/node_modules
# # - ./api:/app/api
# # - ./.env:/app/.env
# # - ./.env.development:/app/.env.development
# # - ./.env.production:/app/.env.production
# # - /app/api/node_modules
# # Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker-compose for details.
# # - /var/run/docker.sock:/var/run/docker.sock
# This is where VS Code should expect to find your project's source code and the value of "workspaceFolder" in .devcontainer/devcontainer.json
- ..:/workspaces:cached
# Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker-compose for details.
# - /var/run/docker.sock:/var/run/docker.sock
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
# Runs app on the same network as the service container, allows "forwardPorts" in devcontainer.json function.
# network_mode: service:another-service
@@ -39,45 +32,34 @@ services:
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
# Uncomment the next line to use a non-root user for all processes - See https://aka.ms/vscode-remote/containers/non-root for details.
# user: vscode
# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
# cap_add:
# - SYS_PTRACE
# security_opt:
# - seccomp:unconfined
# Use a non-root user for all processes - See https://aka.ms/vscode-remote/containers/non-root for details.
user: vscode
# Overrides default command so things don't shut down after the process ends.
command: /bin/sh -c "while sleep 1000; do :; done"
mongodb:
container_name: chat-mongodb
# network_mode: "host"
expose:
- 27017
# ports:
# - 27018:27017
image: mongo
# restart: always
# restart: always
volumes:
- ./data-node:/data/db
command: mongod --noauth
meilisearch:
container_name: chat-meilisearch
image: getmeili/meilisearch:v1.0
# network_mode: "host"
image: getmeili/meilisearch:v1.5
# restart: always
expose:
- 7700
# ports:
# - 7700:7700
# env_file:
# - .env
# Uncomment this to access meilisearch from outside docker
# ports:
# - 7700:7700 # if exposing these ports, make sure your master key is not the default value
environment:
- SEARCH=false
- MEILI_HOST=http://0.0.0.0:7700
- MEILI_HTTP_ADDR=0.0.0.0:7700
- MEILI_NO_ANALYTICS=true
- MEILI_MASTER_KEY=5c71cf56d672d009e36070b5bc5e47b743535ae55c818ae3b735bb6ebfb4ba63
volumes:
- ./meili_data:/meili_data
- ./meili_data_v1.5:/meili_data

View File

@@ -1,5 +1,17 @@
**/.circleci
**/.editorconfig
**/.dockerignore
**/.git
**/.DS_Store
**/.vscode
**/node_modules
client/dist/images
# Specific patterns to ignore
data-node
.env
**/.env
meili_data*
librechat*
Dockerfile*
docs
# Ignore all hidden files
.*

View File

@@ -1,44 +1,285 @@
##########################
# Server configuration:
##########################
#=====================================================================#
# LibreChat Configuration #
#=====================================================================#
# Please refer to the reference documentation for assistance #
# with configuring your LibreChat environment. #
# #
# https://www.librechat.ai/docs/configuration/dotenv #
#=====================================================================#
APP_TITLE=LibreChat
#==================================================#
# Server Configuration #
#==================================================#
# The server will listen to localhost:3080 by default. You can change the target IP as you want.
# If you want to make this server available externally, for example to share the server with others
# or expose this from a Docker container, set host to 0.0.0.0 or your external IP interface.
# Tips: Setting host to 0.0.0.0 means listening on all interfaces. It's not a real IP.
# Use localhost:port rather than 0.0.0.0:port to access the server.
# Set Node env to development if running in dev mode.
HOST=localhost
PORT=3080
# Note: the following enables user balances, which you can add manually
# or you will need to build out a balance accruing system for users.
# For more info, see https://docs.librechat.ai/features/token_usage.html
MONGO_URI=mongodb://127.0.0.1:27017/LibreChat
# To manually add balances, run the following command:
# `npm run add-balance`
DOMAIN_CLIENT=http://localhost:3080
DOMAIN_SERVER=http://localhost:3080
# You can also specify the email and token credit amount to add, e.g.:
# `npm run add-balance example@example.com 1000`
NO_INDEX=true
# This works well to track your own usage for personal use; 1000 credits = $0.001 (1 mill USD)
#===============#
# JSON Logging #
#===============#
# Set to true to enable token credit balances for the OpenAI/Plugins endpoints
CHECK_BALANCE=false
# Use when process console logs in cloud deployment like GCP/AWS
CONSOLE_JSON=false
# Automated Moderation System
# 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.
#===============#
# Debug Logging #
#===============#
BAN_VIOLATIONS=true # Whether or not to enable banning users for violations (they will still be logged)
BAN_DURATION=1000 * 60 * 60 * 2 # how long the user and associated IP are banned for
BAN_INTERVAL=20 # a user will be banned everytime their score reaches/crosses over the interval threshold
DEBUG_LOGGING=true
DEBUG_CONSOLE=false
# The score for each violation
#=============#
# Permissions #
#=============#
# UID=1000
# GID=1000
#===============#
# Configuration #
#===============#
# Use an absolute path, a relative path, or a URL
# CONFIG_PATH="/alternative/path/to/librechat.yaml"
#===================================================#
# Endpoints #
#===================================================#
# ENDPOINTS=openAI,assistants,azureOpenAI,bingAI,google,gptPlugins,anthropic
PROXY=
#===================================#
# Known Endpoints - librechat.yaml #
#===================================#
# https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints
# ANYSCALE_API_KEY=
# APIPIE_API_KEY=
# FIREWORKS_API_KEY=
# GROQ_API_KEY=
# HUGGINGFACE_TOKEN=
# MISTRAL_API_KEY=
# OPENROUTER_KEY=
# PERPLEXITY_API_KEY=
# SHUTTLEAI_API_KEY=
# TOGETHERAI_API_KEY=
#============#
# Anthropic #
#============#
ANTHROPIC_API_KEY=user_provided
# ANTHROPIC_MODELS=claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307,claude-2.1,claude-2,claude-1.2,claude-1,claude-1-100k,claude-instant-1,claude-instant-1-100k
# ANTHROPIC_REVERSE_PROXY=
#============#
# Azure #
#============#
# Note: these variables are DEPRECATED
# Use the `librechat.yaml` configuration for `azureOpenAI` instead
# You may also continue to use them if you opt out of using the `librechat.yaml` configuration
# AZURE_OPENAI_DEFAULT_MODEL=gpt-3.5-turbo # Deprecated
# AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4 # Deprecated
# AZURE_USE_MODEL_AS_DEPLOYMENT_NAME=TRUE # Deprecated
# AZURE_API_KEY= # Deprecated
# AZURE_OPENAI_API_INSTANCE_NAME= # Deprecated
# AZURE_OPENAI_API_DEPLOYMENT_NAME= # Deprecated
# AZURE_OPENAI_API_VERSION= # Deprecated
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME= # Deprecated
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME= # Deprecated
# PLUGINS_USE_AZURE="true" # Deprecated
#============#
# BingAI #
#============#
BINGAI_TOKEN=user_provided
# BINGAI_HOST=https://cn.bing.com
#============#
# Google #
#============#
GOOGLE_KEY=user_provided
# GOOGLE_REVERSE_PROXY=
# Gemini API
# GOOGLE_MODELS=gemini-1.5-flash-latest,gemini-1.0-pro,gemini-1.0-pro-001,gemini-1.0-pro-latest,gemini-1.0-pro-vision-latest,gemini-1.5-pro-latest,gemini-pro,gemini-pro-vision
# Vertex AI
# GOOGLE_MODELS=gemini-1.5-flash-preview-0514,gemini-1.5-pro-preview-0514,gemini-1.0-pro-vision-001,gemini-1.0-pro-002,gemini-1.0-pro-001,gemini-pro-vision,gemini-1.0-pro
# Google Gemini Safety Settings
# NOTE (Vertex AI): You do not have access to the BLOCK_NONE setting by default.
# To use this restricted HarmBlockThreshold setting, you will need to either:
#
# (a) Get access through an allowlist via your Google account team
# (b) Switch your account type to monthly invoiced billing following this instruction:
# https://cloud.google.com/billing/docs/how-to/invoiced-billing
#
# GOOGLE_SAFETY_SEXUALLY_EXPLICIT=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_HATE_SPEECH=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_HARASSMENT=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_DANGEROUS_CONTENT=BLOCK_ONLY_HIGH
#============#
# OpenAI #
#============#
OPENAI_API_KEY=user_provided
# OPENAI_MODELS=gpt-4o,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k
DEBUG_OPENAI=false
# TITLE_CONVO=false
# OPENAI_TITLE_MODEL=gpt-3.5-turbo
# OPENAI_SUMMARIZE=true
# OPENAI_SUMMARY_MODEL=gpt-3.5-turbo
# OPENAI_FORCE_PROMPT=true
# OPENAI_REVERSE_PROXY=
# OPENAI_ORGANIZATION=
#====================#
# Assistants API #
#====================#
ASSISTANTS_API_KEY=user_provided
# ASSISTANTS_BASE_URL=
# ASSISTANTS_MODELS=gpt-4o,gpt-3.5-turbo-0125,gpt-3.5-turbo-16k-0613,gpt-3.5-turbo-16k,gpt-3.5-turbo,gpt-4,gpt-4-0314,gpt-4-32k-0314,gpt-4-0613,gpt-3.5-turbo-0613,gpt-3.5-turbo-1106,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview
#==========================#
# Azure Assistants API #
#==========================#
# Note: You should map your credentials with custom variables according to your Azure OpenAI Configuration
# The models for Azure Assistants are also determined by your Azure OpenAI configuration.
# More info, including how to enable use of Assistants with Azure here:
# https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints/azure#using-assistants-with-azure
#============#
# OpenRouter #
#============#
# !!!Warning: Use the variable above instead of this one. Using this one will override the OpenAI endpoint
# OPENROUTER_API_KEY=
#============#
# Plugins #
#============#
# PLUGIN_MODELS=gpt-4o,gpt-4,gpt-4-turbo-preview,gpt-4-0125-preview,gpt-4-1106-preview,gpt-4-0613,gpt-3.5-turbo,gpt-3.5-turbo-0125,gpt-3.5-turbo-1106,gpt-3.5-turbo-0613
DEBUG_PLUGINS=true
CREDS_KEY=f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0
CREDS_IV=e2341419ec3dd3d19b13a1a87fafcbfb
# Azure AI Search
#-----------------
AZURE_AI_SEARCH_SERVICE_ENDPOINT=
AZURE_AI_SEARCH_INDEX_NAME=
AZURE_AI_SEARCH_API_KEY=
AZURE_AI_SEARCH_API_VERSION=
AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE=
AZURE_AI_SEARCH_SEARCH_OPTION_TOP=
AZURE_AI_SEARCH_SEARCH_OPTION_SELECT=
# DALL·E
#----------------
# DALLE_API_KEY=
# DALLE3_API_KEY=
# DALLE2_API_KEY=
# DALLE3_SYSTEM_PROMPT=
# DALLE2_SYSTEM_PROMPT=
# DALLE_REVERSE_PROXY=
# DALLE3_BASEURL=
# DALLE2_BASEURL=
# DALL·E (via Azure OpenAI)
# Note: requires some of the variables above to be set
#----------------
# DALLE3_AZURE_API_VERSION=
# DALLE2_AZURE_API_VERSION=
# Google
#-----------------
GOOGLE_SEARCH_API_KEY=
GOOGLE_CSE_ID=
# SerpAPI
#-----------------
SERPAPI_API_KEY=
# Stable Diffusion
#-----------------
SD_WEBUI_URL=http://host.docker.internal:7860
# Tavily
#-----------------
TAVILY_API_KEY=
# Traversaal
#-----------------
TRAVERSAAL_API_KEY=
# WolframAlpha
#-----------------
WOLFRAM_APP_ID=
# Zapier
#-----------------
ZAPIER_NLA_API_KEY=
#==================================================#
# Search #
#==================================================#
SEARCH=true
MEILI_NO_ANALYTICS=true
MEILI_HOST=http://0.0.0.0:7700
MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt
#==================================================#
# Speech to Text & Text to Speech #
#==================================================#
STT_API_KEY=
TTS_API_KEY=
#===================================================#
# User System #
#===================================================#
#========================#
# Moderation #
#========================#
OPENAI_MODERATION=false
OPENAI_MODERATION_API_KEY=
# OPENAI_MODERATION_REVERSE_PROXY=
BAN_VIOLATIONS=true
BAN_DURATION=1000 * 60 * 60 * 2
BAN_INTERVAL=20
LOGIN_VIOLATION_SCORE=1
REGISTRATION_VIOLATION_SCORE=1
@@ -46,360 +287,132 @@ CONCURRENT_VIOLATION_SCORE=1
MESSAGE_VIOLATION_SCORE=1
NON_BROWSER_VIOLATION_SCORE=20
# Login and registration rate limiting.
LOGIN_MAX=7
LOGIN_WINDOW=5
REGISTER_MAX=5
REGISTER_WINDOW=60
LOGIN_MAX=7 # The max amount of logins allowed per IP per LOGIN_WINDOW
LOGIN_WINDOW=5 # in minutes, determines the window of time for LOGIN_MAX logins
REGISTER_MAX=5 # The max amount of registrations allowed per IP per REGISTER_WINDOW
REGISTER_WINDOW=60 # in minutes, determines the window of time for REGISTER_MAX registrations
LIMIT_CONCURRENT_MESSAGES=true
CONCURRENT_MESSAGE_MAX=2
# Message rate limiting (per user & IP)
LIMIT_MESSAGE_IP=true
MESSAGE_IP_MAX=40
MESSAGE_IP_WINDOW=1
LIMIT_CONCURRENT_MESSAGES=true # Whether to limit the amount of messages a user can send per request
CONCURRENT_MESSAGE_MAX=2 # The max amount of messages a user can send per request
LIMIT_MESSAGE_USER=false
MESSAGE_USER_MAX=40
MESSAGE_USER_WINDOW=1
LIMIT_MESSAGE_IP=true # Whether to limit the amount of messages an IP can send per MESSAGE_IP_WINDOW
MESSAGE_IP_MAX=40 # The max amount of messages an IP can send per MESSAGE_IP_WINDOW
MESSAGE_IP_WINDOW=1 # in minutes, determines the window of time for MESSAGE_IP_MAX messages
ILLEGAL_MODEL_REQ_SCORE=5
# Note: You can utilize both limiters, but default is to limit by IP only.
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
#========================#
# Balance #
#========================#
# 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.
UID=1000
GID=1000
CHECK_BALANCE=false
# Change this to proxy any API request.
# It's useful if your machine has difficulty calling the original API server.
# PROXY=
#========================#
# Registration and Login #
#========================#
# Change this to your MongoDB URI if different. I recommend appending LibreChat.
MONGO_URI=mongodb://127.0.0.1:27018/LibreChat
##########################
# OpenAI Endpoint:
##########################
# Access key from OpenAI platform.
# Leave it blank to disable this feature.
# Set to "user_provided" to allow the user to provide their API key from the UI.
OPENAI_API_KEY=user_provided
DEBUG_OPENAI=false # Set to true to enable debug mode for the OpenAI endpoint
# Identify the available models, separated by commas *without spaces*.
# The first will be default.
# Leave it blank to use internal settings.
# OPENAI_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-16k,gpt-3.5-turbo-0301,text-davinci-003,gpt-4,gpt-4-0314,gpt-4-0613
# Titling is enabled by default when initiating a conversation.
# Uncomment the following variable to disable this feature.
# TITLE_CONVO=false
# (Optional) The default model used for titling by is gpt-3.5-turbo-0613
# You can change it by uncommenting the following and setting the desired model
# Must be compatible with the OpenAI Endpoint.
# OPENAI_TITLE_MODEL=gpt-3.5-turbo
# (Optional/Experimental) Enable message summarization by uncommenting the following:
# Note: this may affect response time when a summary is being generated.
# OPENAI_SUMMARIZE=true
# Not yet implemented: this will be a conversation option enabled by default to save users on tokens
# We are using the ConversationSummaryBufferMemory method to summarize messages.
# To learn more about this, see this article:
# https://www.pinecone.io/learn/series/langchain/langchain-conversational-memory/
# (Optional) The default model used for summarizing is gpt-3.5-turbo
# You can change it by uncommenting the following and setting the desired model
# Must be compatible with the OpenAI Endpoint.
# OPENAI_SUMMARY_MODEL=gpt-3.5-turbo
# Reverse proxy settings for OpenAI:
# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy
# OPENAI_REVERSE_PROXY=
# (Advanced) Sometimes when using Local LLM APIs, you may need to force the API
# to be called with a `prompt` payload instead of a `messages` payload; to mimic the
# a `/v1/completions` request instead of `/v1/chat/completions`
# This may be the case for LocalAI with some models. To do so, uncomment the following:
# OPENAI_FORCE_PROMPT=true
##########################
# OpenRouter (overrides OpenAI and Plugins Endpoints):
##########################
# OpenRouter is a legitimate proxy service to a multitude of LLMs, both closed and open source, including:
# OpenAI models, Anthropic models, Meta's Llama models, pygmalionai/mythalion-13b
# and many more open source models. Newer integrations are usually discounted, too!
# Note: this overrides the OpenAI and Plugins Endpoints.
# See ./docs/install/free_ai_apis.md for more info.
# OPENROUTER_API_KEY=
##########################
# AZURE Endpoint:
##########################
# To use Azure with this project, set the following variables. These will be used to build the API URL.
# Chat completion:
# `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 `OPENAI_MODELS` variable above to the models available in your instance/deployment.
# Note: I've noticed that the Azure API is much faster than the OpenAI API, so the streaming looks almost instantaneous.
# Note "AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME" and "AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME" are optional but might be used in the future
# AZURE_API_KEY=
# AZURE_OPENAI_API_INSTANCE_NAME=
# AZURE_OPENAI_API_DEPLOYMENT_NAME=
# AZURE_OPENAI_API_VERSION=
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME=
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME=
# Identify the available models, separated by commas *without spaces*.
# The first will be default.
# Leave it blank to use internal settings.
AZURE_OPENAI_MODELS=gpt-3.5-turbo,gpt-4
# To use Azure with the Plugins endpoint, you need the variables above, and uncomment the following variable:
# NOTE: This may not work as expected and Azure OpenAI may not support OpenAI Functions yet
# Omit/leave it commented to use the default OpenAI API
# PLUGINS_USE_AZURE="true"
##########################
# ChatGPT Endpoint:
##########################
# ChatGPT Browser Client (free but use at your own risk)
# Access token from https://chat.openai.com/api/auth/session
# Exposes your access token to `CHATGPT_REVERSE_PROXY`
# Set to "user_provided" to allow the user to provide its token from the UI.
# Leave it blank to disable this endpoint
CHATGPT_TOKEN=user_provided
# Identify the available models, separated by commas. The first will be default.
# Leave it blank to use internal settings.
CHATGPT_MODELS=text-davinci-002-render-sha,gpt-4
# NOTE: you can add 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
# Also: text-davinci-002-render-paid is deprecated as of May 2023
# Reverse proxy setting for OpenAI
# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy
# By default it will use the node-chatgpt-api recommended proxy, (it's a third party server)
# CHATGPT_REVERSE_PROXY=<YOUR REVERSE PROXY>
##########################
# BingAI Endpoint:
##########################
# Also used for Sydney and jailbreak
# To get your Access token for Bing, login to https://www.bing.com
# Use dev tools or an extension while logged into the site to copy the content of the _U cookie.
# If this fails, follow these instructions https://github.com/danny-avila/LibreChat/issues/370#issuecomment-1560382302 to provide the full cookie strings
# or check out our discord https://discord.com/channels/1086345563026489514/1143941308684177429
# Set to "user_provided" to allow the user to provide its token from the UI.
# Leave it blank to disable this endpoint.
BINGAI_TOKEN=user_provided
# BingAI Host:
# Necessary for some people in different countries, e.g. China (https://cn.bing.com)
# Leave it blank to use default server.
# BINGAI_HOST=https://cn.bing.com
#############################
# Plugins:
#############################
# Identify the available models, separated by commas *without spaces*.
# The first will be default.
# Leave it blank to use internal settings.
# PLUGIN_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-16k,gpt-3.5-turbo-0301,gpt-4,gpt-4-0314,gpt-4-0613
DEBUG_PLUGINS=true # Set to false or comment out to disable debug mode for plugins
# For securely storing credentials, you need a fixed key and IV. You can set them here for prod and dev environments
# If you don't set them, the app will crash on startup.
# You need a 32-byte key (64 characters in hex) and 16-byte IV (32 characters in hex)
# Use this replit to generate some quickly: https://replit.com/@daavila/crypto#index.js
# Here are some examples (THESE ARE NOT SECURE!)
CREDS_KEY=f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0
CREDS_IV=e2341419ec3dd3d19b13a1a87fafcbfb
# AI-Assisted Google Search
# This bot supports searching google for answers to your questions with assistance from GPT!
# See detailed instructions here: https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/google_search.md
GOOGLE_API_KEY=
GOOGLE_CSE_ID=
# StableDiffusion WebUI
# This bot supports StableDiffusion WebUI, using it's API to generated requested images.
# See detailed instructions here: https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/stable_diffusion.md
# Use "http://127.0.0.1:7860" with local install and "http://host.docker.internal:7860" for docker
SD_WEBUI_URL=http://host.docker.internal:7860
# Azure Cognitive Search
# This plugin supports searching Azure Cognitive Search for answers to your questions.
# See detailed instructions here: https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/azure_cognitive_search.md
AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT=
AZURE_COGNITIVE_SEARCH_INDEX_NAME=
AZURE_COGNITIVE_SEARCH_API_KEY=
AZURE_COGNITIVE_SEARCH_API_VERSION=
AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE=
AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP=
AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_SELECT=
##########################
# PaLM (Google) Endpoint:
##########################
# Follow the instruction here to setup:
# https://github.com/danny-avila/LibreChat/blob/main/docs/install/apis_and_tokens.md
PALM_KEY=user_provided
# In case you need a reverse proxy for this endpoint:
# GOOGLE_REVERSE_PROXY=
##########################
# Anthropic Endpoint:
##########################
# Access key from https://console.anthropic.com/
# Leave it blank to disable this feature.
# Set to "user_provided" to allow the user to provide their API key from the UI.
# Note that access to claude-1 may potentially become unavailable with the release of claude-2.
ANTHROPIC_API_KEY=user_provided
ANTHROPIC_MODELS=claude-1,claude-instant-1,claude-2
##########################
# Proxy: To be Used by all endpoints
##########################
PROXY=
##########################
# Search:
##########################
# ENABLING SEARCH MESSAGES/CONVOS
# Requires the installation of the free self-hosted Meilisearch or a paid Remote Plan (Remote not tested)
# The easiest setup for this is through docker-compose, which takes care of it for you.
SEARCH=true
# HIGHLY RECOMMENDED: Disable anonymized telemetry analytics for MeiliSearch for absolute privacy.
MEILI_NO_ANALYTICS=true
# REQUIRED FOR SEARCH: MeiliSearch Host, mainly for the API server to connect to the search server.
# Replace '0.0.0.0' with 'meilisearch' if serving MeiliSearch with docker-compose.
MEILI_HOST=http://0.0.0.0:7700
# REQUIRED FOR SEARCH: MeiliSearch HTTP Address, mainly for docker-compose to expose the search server.
# Replace '0.0.0.0' with 'meilisearch' if serving MeiliSearch with docker-compose.
MEILI_HTTP_ADDR=0.0.0.0:7700
# REQUIRED FOR SEARCH: In production env., a secure key is needed. You can generate your own.
# This master key must be at least 16 bytes, composed of valid UTF-8 characters.
# MeiliSearch will throw an error and refuse to launch if no master key is provided,
# or if it is under 16 bytes. MeiliSearch will suggest a secure autogenerated master key.
# Using docker, it seems recognized as production so use a secure key.
# This is a ready made secure key for docker-compose, you can replace it with your own.
MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt
##########################
# User System:
##########################
# Allow Public Registration
ALLOW_EMAIL_LOGIN=true
ALLOW_REGISTRATION=true
# Allow Social Registration
ALLOW_SOCIAL_LOGIN=false
# Allow Social Registration (WORKS ONLY for Google, Github, Discord)
ALLOW_SOCIAL_REGISTRATION=false
# JWT Secrets
# You should use secure values. The examples given are 32-byte keys (64 characters in hex)
# Use this replit to generate some quickly: https://replit.com/@daavila/crypto#index.js
SESSION_EXPIRY=1000 * 60 * 15
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef
JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418
# Google:
# Add your Google Client ID and Secret here, you must register an app with Google Cloud to get these values
# https://cloud.google.com/
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=/oauth/google/callback
# Discord
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_CALLBACK_URL=/oauth/discord/callback
# Facebook:
# Add your Facebook Client ID and Secret here, you must register an app with Facebook to get these values
# https://developers.facebook.com/
# Facebook
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_CALLBACK_URL=/oauth/facebook/callback
# OpenID:
# See OpenID provider to get the below values
# Create random string for OPENID_SESSION_SECRET
# For Azure AD
# ISSUER: https://login.microsoftonline.com/(tenant id)/v2.0/
# SCOPE: openid profile email
# GitHub
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK_URL=/oauth/github/callback
# Google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=/oauth/google/callback
# OpenID
OPENID_CLIENT_ID=
OPENID_CLIENT_SECRET=
OPENID_ISSUER=
OPENID_SESSION_SECRET=
OPENID_SCOPE="openid profile email"
OPENID_CALLBACK_URL=/oauth/openid/callback
# If LABEL and URL are left empty, then the default OpenID label and logo are used.
OPENID_REQUIRED_ROLE=
OPENID_REQUIRED_ROLE_TOKEN_KIND=
OPENID_REQUIRED_ROLE_PARAMETER_PATH=
OPENID_BUTTON_LABEL=
OPENID_IMAGE_URL=
# Set the expiration delay for the secure cookie with the JWT token
# Recommend session expiry to be 15 minutes
# Delay is in millisecond e.g. 7 days is 1000*60*60*24*7
SESSION_EXPIRY=1000 * 60 * 15
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
# LDAP
LDAP_URL=
LDAP_BIND_DN=
LDAP_BIND_CREDENTIALS=
LDAP_USER_SEARCH_BASE=
LDAP_SEARCH_FILTER=mail={{username}}
LDAP_CA_CERT_PATH=
# Github:
# Get the Client ID and Secret from your Discord Application
# Add your Discord Client ID and Client Secret here:
#========================#
# Email Password Reset #
#========================#
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
EMAIL_SERVICE=
EMAIL_HOST=
EMAIL_PORT=25
EMAIL_ENCRYPTION=
EMAIL_ENCRYPTION_HOSTNAME=
EMAIL_ALLOW_SELFSIGNED=
EMAIL_USERNAME=
EMAIL_PASSWORD=
EMAIL_FROM_NAME=
EMAIL_FROM=noreply@librechat.ai
# Discord:
# Get the Client ID and Secret from your Discord Application
# Add your Github Client ID and Client Secret here:
#========================#
# Firebase CDN #
#========================#
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
FIREBASE_API_KEY=
FIREBASE_AUTH_DOMAIN=
FIREBASE_PROJECT_ID=
FIREBASE_STORAGE_BUCKET=
FIREBASE_MESSAGING_SENDER_ID=
FIREBASE_APP_ID=
###########################
# Application Domains
###########################
#===================================================#
# UI #
#===================================================#
# Note:
# Server = Backend
# Client = Public (the client is the url you visit)
# For the Google login to work in dev mode, you will need to change DOMAIN_SERVER to localhost:3090 or place it in .env.development
APP_TITLE=LibreChat
# CUSTOM_FOOTER="My custom footer"
HELP_AND_FAQ_URL=https://librechat.ai
DOMAIN_CLIENT=http://localhost:3080
DOMAIN_SERVER=http://localhost:3080
# SHOW_BIRTHDAY_ICON=true
###########################
# Email
###########################
#==================================================#
# Others #
#==================================================#
# You should leave the following commented out #
# Email is used for password reset. Note that all 4 values must be set for email to work.
# Failing to set the 4 values will result in LibreChat using the unsecured password reset!
EMAIL_SERVICE= # eg. gmail
EMAIL_USERNAME= # eg. your email address if using gmail
EMAIL_PASSWORD= # eg. this is the "app password" if using gmail
EMAIL_FROM=noreply@librechat.ai # email address for from field, it is required to set a value here even in the cases where it's not porperly working.
# NODE_ENV=
# REDIS_URI=
# USE_REDIS=
# E2E_USER_EMAIL=
# E2E_USER_PASSWORD=

View File

@@ -19,6 +19,10 @@ module.exports = {
'e2e/playwright-report/**/*',
'packages/data-provider/types/**/*',
'packages/data-provider/dist/**/*',
'packages/data-provider/test_bundle/**/*',
'data-node/**/*',
'meili_data/**/*',
'node_modules/**/*',
],
parser: '@typescript-eslint/parser',
parserOptions: {
@@ -61,6 +65,7 @@ module.exports = {
'no-restricted-syntax': 'off',
'react/prop-types': ['off'],
'react/display-name': ['off'],
'no-unused-vars': ['error', { varsIgnorePattern: '^_' }],
quotes: ['error', 'single'],
},
overrides: [
@@ -127,6 +132,19 @@ module.exports = {
},
],
},
{
files: './config/translations/**/*.ts',
parser: '@typescript-eslint/parser',
parserOptions: {
project: './config/translations/tsconfig.json',
},
},
{
files: ['./packages/data-provider/specs/**/*.ts'],
parserOptions: {
project: './packages/data-provider/tsconfig.spec.json',
},
},
],
settings: {
react: {

View File

@@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement here on GitHub or
on the official [Discord Server](https://discord.gg/uDyZ5Tzhct).
on the official [Discord Server](https://discord.librechat.ai).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

View File

@@ -8,7 +8,7 @@ If the feature you would like to contribute has not already received prior appro
Please note that a pull request involving a feature that has not been reviewed and approved by the project maintainers may be rejected. We appreciate your understanding and cooperation.
If you would like to discuss the changes you wish to make, join our [Discord community](https://discord.gg/uDyZ5Tzhct), where you can engage with other contributors and seek guidance from the community.
If you would like to discuss the changes you wish to make, join our [Discord community](https://discord.librechat.ai), where you can engage with other contributors and seek guidance from the community.
## Our Standards

View File

@@ -7,14 +7,6 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: input
id: contact
attributes:
label: Contact Details
description: How can we get in touch with you if we need more info?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what-happened
attributes:
@@ -58,7 +50,7 @@ body:
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/CODE_OF_CONDUCT.md)
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/.github/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -7,14 +7,6 @@ body:
attributes:
value: |
Thank you for taking the time to fill this out!
- type: input
id: contact
attributes:
label: Contact Details
description: How can we contact you if we need more information?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what
attributes:
@@ -51,7 +43,7 @@ body:
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/CODE_OF_CONDUCT.md)
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/.github/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -7,14 +7,6 @@ body:
attributes:
value: |
Thanks for taking the time to fill this!
- type: input
id: contact
attributes:
label: Contact Details
description: How can we get in touch with you if we need more info?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what-is-your-question
attributes:
@@ -52,7 +44,7 @@ body:
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/CODE_OF_CONDUCT.md)
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/.github/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

6
.github/SECURITY.md vendored
View File

@@ -12,7 +12,7 @@ When reporting a security vulnerability, you have the following options to reach
- **Option 2: GitHub Issues**: You can initiate first contact via GitHub Issues. However, please note that initial contact through GitHub Issues should not include any sensitive details.
- **Option 3: Discord Server**: You can join our [Discord community](https://discord.gg/5rbRxn4uME) and initiate first contact in the `#issues` channel. However, please ensure that initial contact through Discord does not include any sensitive details.
- **Option 3: Discord Server**: You can join our [Discord community](https://discord.librechat.ai) and initiate first contact in the `#issues` channel. However, please ensure that initial contact through Discord does not include any sensitive details.
_After the initial contact, we will establish a private communication channel for further discussion._
@@ -39,11 +39,11 @@ Please note that as a security-conscious community, we may not always disclose d
This security policy applies to the following GitHub repository:
- Repository: [LibreChat](https://github.com/danny-avila/LibreChat)
- Repository: [LibreChat](https://github.librechat.ai)
## Contact
If you have any questions or concerns regarding the security of our project, please join our [Discord community](https://discord.gg/NGaa9RPCft) and report them in the appropriate channel. You can also reach out to us by [opening an issue](https://github.com/danny-avila/LibreChat/issues/new) on GitHub. Please note that the response time may vary depending on the nature and severity of the inquiry.
If you have any questions or concerns regarding the security of our project, please join our [Discord community](https://discord.librechat.ai) and report them in the appropriate channel. You can also reach out to us by [opening an issue](https://github.com/danny-avila/LibreChat/issues/new) on GitHub. Please note that the response time may vary depending on the nature and severity of the inquiry.
## Acknowledgments

72
.github/playwright.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# name: Playwright Tests
# on:
# pull_request:
# branches:
# - main
# - dev
# - release/*
# paths:
# - 'api/**'
# - 'client/**'
# - 'packages/**'
# - 'e2e/**'
# jobs:
# tests_e2e:
# name: Run Playwright tests
# if: github.event.pull_request.head.repo.full_name == 'danny-avila/LibreChat'
# timeout-minutes: 60
# runs-on: ubuntu-latest
# env:
# NODE_ENV: CI
# CI: true
# SEARCH: false
# BINGAI_TOKEN: user_provided
# CHATGPT_TOKEN: user_provided
# MONGO_URI: ${{ secrets.MONGO_URI }}
# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }}
# E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }}
# JWT_SECRET: ${{ secrets.JWT_SECRET }}
# JWT_REFRESH_SECRET: ${{ secrets.JWT_REFRESH_SECRET }}
# CREDS_KEY: ${{ secrets.CREDS_KEY }}
# CREDS_IV: ${{ secrets.CREDS_IV }}
# DOMAIN_CLIENT: ${{ secrets.DOMAIN_CLIENT }}
# DOMAIN_SERVER: ${{ secrets.DOMAIN_SERVER }}
# PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 # Skip downloading during npm install
# PLAYWRIGHT_BROWSERS_PATH: 0 # Places binaries to node_modules/@playwright/test
# TITLE_CONVO: false
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-node@v4
# with:
# node-version: 18
# cache: 'npm'
# - name: Install global dependencies
# run: npm ci
# # - name: Remove sharp dependency
# # run: rm -rf node_modules/sharp
# # - name: Install sharp with linux dependencies
# # run: cd api && SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp
# - name: Build Client
# run: npm run frontend
# - name: Install Playwright
# run: |
# npx playwright install-deps
# npm install -D @playwright/test@latest
# npx playwright install chromium
# - name: Run Playwright tests
# run: npm run e2e:ci
# - name: Upload playwright report
# uses: actions/upload-artifact@v3
# if: always()
# with:
# name: playwright-report
# path: e2e/playwright-report/
# retention-days: 30

View File

@@ -1,7 +1,10 @@
# Pull Request Template
⚠️ Before Submitting a PR, Please Review:
- Please ensure that you have thoroughly read and understood the [Contributing Docs](https://github.com/danny-avila/LibreChat/blob/main/.github/CONTRIBUTING.md) before submitting your Pull Request.
### ⚠️ Before Submitting a PR, read the [Contributing Docs](./CONTRIBUTING.md) in full!
⚠️ Documentation Updates Notice:
- Kindly note that documentation updates are managed in this repository: [librechat.ai](https://github.com/LibreChat-AI/librechat.ai)
## Summary
@@ -15,7 +18,7 @@ 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
- [ ] Translation update
## Testing
@@ -25,6 +28,8 @@ Please describe your test process and include instructions so that we can reprod
## Checklist
Please delete any irrelevant options.
- [ ] My code adheres to this project's style guidelines
- [ ] I have performed a self-review of my own code
- [ ] I have commented in any complex areas of my code
@@ -33,3 +38,4 @@ Please describe your test process and include instructions so that we can reprod
- [ ] I have written tests demonstrating that my changes are effective or that my feature works
- [ ] Local unit tests pass with my changes
- [ ] Any changes dependent on mine have been merged and published in downstream modules.
- [ ] A pull request for updating the documentation has been submitted.

View File

@@ -23,9 +23,9 @@ jobs:
BAN_INTERVAL: ${{ secrets.BAN_INTERVAL }}
NODE_ENV: CI
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
@@ -35,11 +35,32 @@ jobs:
- name: Install Data Provider
run: npm run build:data-provider
- name: Create empty auth.json file
run: |
mkdir -p api/data
echo '{}' > api/data/auth.json
- name: Check for Circular dependency in rollup
working-directory: ./packages/data-provider
run: |
output=$(npm run rollup:api)
echo "$output"
if echo "$output" | grep -q "Circular dependency"; then
echo "Error: Circular dependency detected!"
exit 1
fi
- name: Prepare .env.test file
run: cp api/test/.env.test.example api/test/.env.test
- name: Run unit tests
run: cd api && npm run test:ci
- name: Run librechat-data-provider unit tests
run: cd packages/data-provider && npm run test:ci
- name: Run linters
uses: wearerequired/lint-action@v2
with:
eslint: true
eslint: true

View File

@@ -1,52 +0,0 @@
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@v2
# Set up Docker
- name: Set up Docker
uses: docker/setup-buildx-action@v1
# 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

View File

@@ -11,8 +11,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
- run: cd packages/data-provider && npm ci
@@ -22,8 +22,8 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
registry-url: 'https://registry.npmjs.org'

View File

@@ -17,7 +17,7 @@ jobs:
steps:
# checkout the repo
- name: 'Checkout GitHub Action'
uses: actions/checkout@main
uses: actions/checkout@v4
- name: 'Login via Azure CLI'
uses: azure/login@v1

View File

@@ -8,19 +8,33 @@ on:
paths:
- 'api/**'
- 'client/**'
- 'packages/**'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: api-build
file: Dockerfile.multi
image_name: librechat-dev-api
- target: node
file: Dockerfile
image_name: librechat-dev
steps:
# Check out the repository
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
# Set up Docker
- name: Set up Docker
uses: docker/setup-buildx-action@v1
# Set up QEMU
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# Set up Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
@@ -30,22 +44,29 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Build Docker images
- name: Build Docker images
# Login to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Prepare the environment
- name: Prepare environment
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
# Build and push Docker images for each target
- name: Build and push Docker images
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.file }}
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.sha }}
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.sha }}
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
platforms: linux/amd64,linux/arm64
target: ${{ matrix.target }}

View File

@@ -1,11 +1,6 @@
#github action to run unit tests for frontend with jest
name: Frontend Unit Tests
on:
# push:
# branches:
# - main
# - dev
# - release/*
pull_request:
branches:
- main
@@ -14,15 +9,16 @@ on:
paths:
- 'client/**'
- 'packages/**'
jobs:
tests_frontend:
name: Run frontend unit tests
tests_frontend_ubuntu:
name: Run frontend unit tests on Ubuntu
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
@@ -35,4 +31,26 @@ jobs:
- name: Run unit tests
run: npm run test:ci --verbose
working-directory: client
working-directory: client
tests_frontend_windows:
name: Run frontend unit tests on Windows
timeout-minutes: 60
runs-on: windows-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

View File

@@ -0,0 +1,20 @@
name: 'generate_embeddings'
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'docs/**'
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: supabase/embeddings-generator@v0.0.5
with:
supabase-url: ${{ secrets.SUPABASE_URL }}
supabase-service-role-key: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
openai-key: ${{ secrets.OPENAI_DOC_EMBEDDINGS_KEY }}
docs-root-path: 'docs'

View File

@@ -0,0 +1,69 @@
name: Docker Compose Build Latest Main Image Tag (Manual Dispatch)
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: api-build
file: Dockerfile.multi
image_name: librechat-api
- target: node
file: Dockerfile
image_name: librechat
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Fetch tags and set the latest tag
run: |
git fetch --tags
echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV
# Set up QEMU
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# Set up Docker Buildx
- name: Set up Docker Buildx
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 }}
# Login to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Prepare the environment
- name: Prepare environment
run: |
cp .env.example .env
# Build and push Docker images for each target
- name: Build and push Docker images
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.file }}
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ env.LATEST_TAG }}
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ env.LATEST_TAG }}
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
platforms: linux/amd64,linux/arm64
target: ${{ matrix.target }}

View File

@@ -1,24 +0,0 @@
name: mkdocs
on:
push:
branches:
- main
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- 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: mkdocs gh-deploy --force

View File

@@ -1,72 +0,0 @@
name: Playwright Tests
on:
pull_request:
branches:
- main
- dev
- release/*
paths:
- 'api/**'
- 'client/**'
- 'packages/**'
- 'e2e/**'
jobs:
tests_e2e:
name: Run Playwright tests
if: github.event.pull_request.head.repo.full_name == 'danny-avila/LibreChat'
timeout-minutes: 60
runs-on: ubuntu-latest
env:
NODE_ENV: CI
CI: true
SEARCH: false
BINGAI_TOKEN: user_provided
CHATGPT_TOKEN: user_provided
MONGO_URI: ${{ secrets.MONGO_URI }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }}
E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
JWT_REFRESH_SECRET: ${{ secrets.JWT_REFRESH_SECRET }}
CREDS_KEY: ${{ secrets.CREDS_KEY }}
CREDS_IV: ${{ secrets.CREDS_IV }}
DOMAIN_CLIENT: ${{ secrets.DOMAIN_CLIENT }}
DOMAIN_SERVER: ${{ secrets.DOMAIN_SERVER }}
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 # Skip downloading during npm install
PLAYWRIGHT_BROWSERS_PATH: 0 # Places binaries to node_modules/@playwright/test
TITLE_CONVO: false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install global dependencies
run: npm ci
# - name: Remove sharp dependency
# run: rm -rf node_modules/sharp
# - name: Install sharp with linux dependencies
# run: cd api && SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp
- name: Build Client
run: npm run frontend
- name: Install Playwright
run: |
npx playwright install-deps
npm install -D @playwright/test@latest
npx playwright install chromium
- name: Run Playwright tests
run: npm run e2e:ci
- name: Upload playwright report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: e2e/playwright-report/
retention-days: 30

67
.github/workflows/tag-images.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Docker Images Build on Tag
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: api-build
file: Dockerfile.multi
image_name: librechat-api
- target: node
file: Dockerfile
image_name: librechat
steps:
# Check out the repository
- name: Checkout
uses: actions/checkout@v4
# Set up QEMU
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# Set up Docker Buildx
- name: Set up Docker Buildx
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 }}
# Login to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Prepare the environment
- name: Prepare environment
run: |
cp .env.example .env
# Build and push Docker images for each target
- name: Build and push Docker images
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.file }}
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.ref_name }}
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.ref_name }}
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
platforms: linux/amd64,linux/arm64
target: ${{ matrix.target }}

31
.gitignore vendored
View File

@@ -2,7 +2,7 @@
# Logs
data-node
meili_data
meili_data*
data/
logs
*.log
@@ -21,6 +21,10 @@ coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# translation services
config/translations/stores/*
client/src/localization/languages/*_missing_keys.json
# Compiled Dirs (http://nodejs.org/api/addons.html)
build/
dist/
@@ -40,7 +44,7 @@ meili_data/
api/node_modules/
client/node_modules/
bower_components/
types/
*.d.ts
# Floobits
.floo
@@ -48,6 +52,10 @@ types/
.floo
.flooignore
#config file
librechat.yaml
librechat.yml
# Environment
.npmrc
.env*
@@ -65,11 +73,20 @@ src/style - official.css
/playwright/.cache/
.DS_Store
*.code-workspace
.idx
monospace.json
.idea
*.iml
*.pem
config.local.ts
**/storageState.json
junit.xml
**/.venv/
**/venv/
# docker override file
docker-compose.override.yaml
docker-compose.override.yml
# meilisearch
meilisearch
@@ -78,4 +95,12 @@ data.ms/*
auth.json
/packages/ux-shared/
/images
/images
!client/src/components/Nav/SettingsTabs/Data/
# User uploads
uploads/
# owner
release/

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env sh
#!/usr/bin/env sh
set -e
. "$(dirname -- "$0")/_/husky.sh"
[ -n "$CI" ] && exit 0

View File

@@ -1,21 +1,32 @@
# Base node image
FROM node:19-alpine AS node
# v0.7.2
COPY . /app
# Base node image
FROM node:20-alpine AS node
RUN apk --no-cache add curl
RUN mkdir -p /app && chown node:node /app
WORKDIR /app
# Install call deps - Install curl for health check
RUN apk --no-cache add curl && \
# We want to inherit env from the container, not the file
# This will preserve any existing env file if it's already in souce
# otherwise it will create a new one
touch .env && \
# Build deps in seperate
npm ci
USER node
# React client build
ENV NODE_OPTIONS="--max-old-space-size=2048"
RUN npm run frontend
COPY --chown=node:node . .
RUN \
# Allow mounting of these files, which have no default
touch .env ; \
# Create directories for the volumes to inherit the correct permissions
mkdir -p /app/client/public/images /app/api/logs ; \
npm config set fetch-retry-maxtimeout 600000 ; \
npm config set fetch-retries 5 ; \
npm config set fetch-retry-mintimeout 15000 ; \
npm install --no-audit; \
# React client build
NODE_OPTIONS="--max-old-space-size=2048" npm run frontend; \
npm prune --production; \
npm cache clean --force
RUN mkdir -p /app/client/public/images /app/api/logs
# Node API setup
EXPOSE 3080

View File

@@ -1,34 +1,37 @@
# v0.7.2
# Build API, Client and Data Provider
FROM node:19-alpine AS base
FROM node:20-alpine AS base
WORKDIR /app
COPY config/loader.js ./config/
RUN npm install dotenv
WORKDIR /app/api
COPY api/package*.json ./
COPY api/ ./
RUN npm install
# Build data-provider
FROM base AS data-provider-build
WORKDIR /app/packages/data-provider
COPY ./packages/data-provider ./
RUN npm install; npm cache clean --force
RUN npm run build
RUN npm prune --production
# React client build
FROM base AS client-build
WORKDIR /app/client
COPY ./client/package*.json ./
# Copy data-provider to client's node_modules
COPY --from=data-provider-build /app/packages/data-provider/ /app/client/node_modules/librechat-data-provider/
RUN npm install; npm cache clean --force
COPY ./client/ ./
WORKDIR /app/packages/data-provider
COPY ./packages/data-provider ./
RUN npm install
RUN npm run build
RUN mkdir -p /app/client/node_modules/librechat-data-provider/
RUN cp -R /app/packages/data-provider/* /app/client/node_modules/librechat-data-provider/
WORKDIR /app/client
RUN npm install
ENV NODE_OPTIONS="--max-old-space-size=2048"
RUN npm run build
# Node API setup
FROM base AS api-build
WORKDIR /app/api
COPY api/package*.json ./
COPY api/ ./
# Copy helper scripts
COPY config/ ./
# Copy data-provider to API's node_modules
COPY --from=data-provider-build /app/packages/data-provider/ /app/api/node_modules/librechat-data-provider/
RUN npm install --include prod; npm cache clean --force
COPY --from=client-build /app/client/dist /app/client/dist
EXPOSE 3080
ENV HOST=0.0.0.0

View File

@@ -1,8 +1,6 @@
# MIT License
MIT License
Copyright (c) 2023 Danny Avila
---
Copyright (c) 2024 LibreChat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -14,8 +12,6 @@ furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
##
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -23,7 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## [Go Back to ReadMe](../README.md)

210
README.md
View File

@@ -1,14 +1,14 @@
<p align="center">
<a href="https://docs.librechat.ai">
<img src="docs/assets/LibreChat.svg" height="256">
</a>
<a href="https://docs.librechat.ai">
<h1 align="center">LibreChat</h1>
<a href="https://librechat.ai">
<img src="client/public/assets/logo.svg" height="256">
</a>
<h1 align="center">
<a href="https://librechat.ai">LibreChat</a>
</h1>
</p>
<p align="center">
<a href="https://discord.gg/NGaa9RPCft">
<a href="https://discord.librechat.ai">
<img
src="https://img.shields.io/discord/1086345563026489514?label=&logo=discord&style=for-the-badge&logoWidth=20&logoColor=white&labelColor=000000&color=blueviolet">
</a>
@@ -20,145 +20,119 @@
<img
src="https://img.shields.io/badge/DOCS-blue.svg?style=for-the-badge&logo=read-the-docs&logoColor=white&labelColor=000000&logoWidth=20">
</a>
<a aria-label="Sponsors" href="#sponsors">
<a aria-label="Sponsors" href="https://github.com/sponsors/danny-avila">
<img
src="https://img.shields.io/badge/SPONSORS-brightgreen.svg?style=for-the-badge&logo=github-sponsors&logoColor=white&labelColor=000000&logoWidth=20">
</a>
</p>
## All-In-One AI Conversations with LibreChat ##
<p align="center">
<a href="https://railway.app/template/b5k2mn?referralCode=myKrVZ">
<img src="https://railway.app/button.svg" alt="Deploy on Railway" height="30">
</a>
<a href="https://zeabur.com/templates/0X2ZY8">
<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30"/>
</a>
<a href="https://template.cloud.sealos.io/deploy?templateName=librechat">
<img src="https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg" alt="Deploy on Sealos" height="30">
</a>
</p>
# 📃 Features
- 🖥️ UI matching ChatGPT, including Dark mode, Streaming, and latest updates
- 🤖 AI model selection:
- OpenAI, Azure OpenAI, BingAI, ChatGPT, Google Vertex AI, Anthropic (Claude), Plugins, Assistants API (including Azure Assistants)
- ✅ Compatible across both **[Remote & Local AI services](https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints):**
- groq, Ollama, Cohere, Mistral AI, Apple MLX, koboldcpp, OpenRouter, together.ai, Perplexity, ShuttleAI, and more
- 💾 Create, Save, & Share Custom Presets
- 🔀 Switch between AI Endpoints and Presets, mid-chat
- 🔄 Edit, Resubmit, and Continue Messages with Conversation branching
- 🌿 Fork Messages & Conversations for Advanced Context control
- 💬 Multimodal Chat:
- Upload and analyze images with Claude 3, GPT-4 (including `gpt-4o`), and Gemini Vision 📸
- Chat with Files using Custom Endpoints, OpenAI, Azure, Anthropic, & Google. 🗃️
- Advanced Agents with Files, Code Interpreter, Tools, and API Actions 🔦
- Available through the [OpenAI Assistants API](https://platform.openai.com/docs/assistants/overview) 🌤️
- Non-OpenAI Agents in Active Development 🚧
- 🌎 Multilingual UI:
- English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro,
- Русский, 日本語, Svenska, 한국어, Tiếng Việt, 繁體中文, العربية, Türkçe, Nederlands, עברית
- 🎨 Customizable Dropdown & Interface: Adapts to both power users and newcomers.
- 📥 Import Conversations from LibreChat, ChatGPT, Chatbot UI
- 📤 Export conversations as screenshots, markdown, text, json.
- 🔍 Search all messages/conversations
- 🔌 Plugins, including web access, image generation with DALL-E-3 and more
- 👥 Multi-User, Secure Authentication with Moderation and Token spend tools
- ⚙️ Configure Proxy, Reverse Proxy, Docker, & many Deployment options:
- Use completely local or deploy on the cloud
- 📖 Completely Open-Source & Built in Public
- 🧑‍🤝‍🧑 Community-driven development, support, and feedback
[For a thorough review of our features, see our docs here](https://docs.librechat.ai/) 📚
## 🪶 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.
<!-- https://github.com/danny-avila/LibreChat/assets/110412045/c1eb0c0f-41f6-4335-b982-84b278b53d59 -->
[![Watch the video](https://img.youtube.com/vi/pNIOs1ovsXw/maxresdefault.jpg)](https://youtu.be/pNIOs1ovsXw)
[![Watch the video](https://img.youtube.com/vi/YLVUW5UP9N0/maxresdefault.jpg)](https://www.youtube.com/watch?v=YLVUW5UP9N0)
Click on the thumbnail to open the video☝
# Features
- Response streaming identical to ChatGPT through server-sent events
- UI from original ChatGPT, including Dark mode
- AI model selection: OpenAI API, BingAI, ChatGPT Browser, PaLM2, Anthropic (Claude), Plugins
- Create, Save, & Share custom presets - [More info on prompt presets here](https://github.com/danny-avila/LibreChat/releases/tag/v0.3.0)
- Edit and Resubmit messages with conversation branching
- Search all messages/conversations - [More info here](https://github.com/danny-avila/LibreChat/releases/tag/v0.1.0)
- Plugins now available (including web access, image generation and more)
---
## 🌐 Resources
**GitHub Repo:**
- **RAG API:** [github.com/danny-avila/rag_api](https://github.com/danny-avila/rag_api)
- **Website:** [github.com/LibreChat-AI/librechat.ai](https://github.com/LibreChat-AI/librechat.ai)
**Other:**
- **Website:** [librechat.ai](https://librechat.ai)
- **Documentation:** [docs.librechat.ai](https://docs.librechat.ai)
- **Blog:** [blog.librechat.ai](https://docs.librechat.ai)
---
## ⚠️ [Breaking Changes](docs/general_info/breaking_changes.md) ⚠️
## 📝 Changelog
**Please read this before updating from a previous version**
Keep up with the latest updates by visiting the releases page and notes:
- [Releases](https://github.com/danny-avila/LibreChat/releases)
- [Changelog](https://www.librechat.ai/changelog)
**⚠️ Please consult the [changelog](https://www.librechat.ai/changelog) for breaking changes before updating.**
---
## Changelog
Keep up with the latest updates by visiting the releases page - [Releases](https://github.com/danny-avila/LibreChat/releases)
## ⭐ Star History
<p align="center">
<a href="https://star-history.com/#danny-avila/LibreChat&Date">
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date&theme=dark" onerror="this.src='https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date'" />
</a>
</p>
<p align="center">
<a href="https://trendshift.io/repositories/4685" target="_blank" style="padding: 10px;">
<img src="https://trendshift.io/api/badge/repositories/4685" alt="danny-avila%2FLibreChat | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
</a>
<a href="https://runacap.com/ross-index/q1-24/" target="_blank" rel="noopener" style="margin-left: 20px;">
<img style="width: 260px; height: 56px" src="https://runacap.com/wp-content/uploads/2024/04/ROSS_badge_white_Q1_2024.svg" alt="ROSS Index - Fastest Growing Open-Source Startups in Q1 2024 | Runa Capital" width="260" height="56"/>
</a>
</p>
---
<h1>Table of Contents</h1>
## ✨ Contributions
<details open>
<summary><strong>Getting Started</strong></summary>
* 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
* [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)
</details>
<details>
<summary><strong>General Information</strong></summary>
* [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)
</details>
<details>
<summary><strong>Features</strong></summary>
* **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)
* [Third-Party Tools](docs/features/third_party.md)
* [Proxy](docs/features/proxy.md)
* [Bing Jailbreak](docs/features/bing_jailbreak.md)
* [Token Usage](docs/features/token_usage.md)
</details>
<details>
<summary><strong>Cloud Deployment</strong></summary>
* [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)
</details>
<details>
<summary><strong>Contributions</strong></summary>
* [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)
</details>
Contributions, suggestions, bug reports and fixes are welcome!
For new features, components, or extensions, please open an issue and discuss before sending a PR.
---
## Star History
## 💖 This project exists in its current state thanks to all the people who contribute
<a href="https://star-history.com/#danny-avila/LibreChat&Date">
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date&theme=dark" onerror="this.src='https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date'" />
</a>
---
## Sponsors
Sponsored by <a href="https://github.com/mjtechguy"><b>@mjtechguy</b></a>, <a href="https://github.com/SphaeroX"><b>@SphaeroX</b></a>, <a href="https://github.com/DavidDev1334"><b>@DavidDev1334</b></a>, <a href="https://github.com/fuegovic"><b>@fuegovic</b></a>, <a href="https://github.com/Pharrcyde"><b>@Pharrcyde</b></a>
---
## Contributors
Contributions and suggestions bug reports and fixes are welcome!
Please read the documentation before you do!
---
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
---
<a href="https://github.com/danny-avila/LibreChat/graphs/contributors">
<img src="https://contrib.rocks/image?repo=danny-avila/LibreChat" />
</a>

View File

@@ -1,6 +1,8 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const { getUserKey, checkUserKeyExpiry } = require('../server/services/UserService');
const { EModelEndpoint } = require('librechat-data-provider');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const { logger } = require('~/config');
const askBing = async ({
text,
@@ -22,10 +24,7 @@ const askBing = async ({
let key = null;
if (expiresAt && isUserProvided) {
checkUserKeyExpiry(
expiresAt,
'Your BingAI Cookies have expired. Please provide your cookies again.',
);
checkUserKeyExpiry(expiresAt, EModelEndpoint.bingAI);
key = await getUserKey({ userId, name: 'bingAI' });
}
@@ -100,7 +99,7 @@ const askBing = async ({
}
}
console.log('bing options', options);
logger.debug('bing options', options);
const res = await bingAIClient.sendMessage(text, options);

View File

@@ -1,5 +1,6 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const { Constants, EModelEndpoint } = require('librechat-data-provider');
const { getUserKey, checkUserKeyExpiry } = require('../server/services/UserService');
const browserClient = async ({
@@ -17,10 +18,7 @@ const browserClient = async ({
let key = null;
if (expiresAt && isUserProvided) {
checkUserKeyExpiry(
expiresAt,
'Your ChatGPT Access Token has expired. Please provide your token again.',
);
checkUserKeyExpiry(expiresAt, EModelEndpoint.chatGPTBrowser);
key = await getUserKey({ userId, name: 'chatGPTBrowser' });
}
@@ -48,7 +46,7 @@ const browserClient = async ({
options = { ...options, parentMessageId, conversationId };
}
if (parentMessageId === '00000000-0000-0000-0000-000000000000') {
if (parentMessageId === Constants.NO_PARENT) {
delete options.conversationId;
}

View File

@@ -1,20 +1,43 @@
// const { Agent, ProxyAgent } = require('undici');
const BaseClient = require('./BaseClient');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const Anthropic = require('@anthropic-ai/sdk');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const {
getResponseSender,
EModelEndpoint,
validateVisionModel,
} = require('librechat-data-provider');
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
const {
truncateText,
formatMessage,
titleFunctionPrompt,
parseParamFromPrompt,
createContextHandlers,
} = require('./prompts');
const spendTokens = require('~/models/spendTokens');
const { getModelMaxTokens } = require('~/utils');
const BaseClient = require('./BaseClient');
const { logger } = require('~/config');
const HUMAN_PROMPT = '\n\nHuman:';
const AI_PROMPT = '\n\nAssistant:';
const tokenizersCache = {};
/** Helper function to introduce a delay before retrying */
function delayBeforeRetry(attempts, baseDelay = 1000) {
return new Promise((resolve) => setTimeout(resolve, baseDelay * attempts));
}
class AnthropicClient extends BaseClient {
constructor(apiKey, options = {}, cacheOptions = {}) {
super(apiKey, options, cacheOptions);
constructor(apiKey, options = {}) {
super(apiKey, options);
this.apiKey = apiKey || process.env.ANTHROPIC_API_KEY;
this.sender = 'Anthropic';
this.userLabel = HUMAN_PROMPT;
this.assistantLabel = AI_PROMPT;
this.contextStrategy = options.contextStrategy
? options.contextStrategy.toLowerCase()
: 'discard';
this.setOptions(options);
}
@@ -40,13 +63,22 @@ class AnthropicClient extends BaseClient {
...modelOptions,
// set some good defaults (check for undefined in some cases because they may be 0)
model: modelOptions.model || 'claude-1',
temperature: typeof modelOptions.temperature === 'undefined' ? 0.7 : modelOptions.temperature, // 0 - 1, 0.7 is recommended
temperature: typeof modelOptions.temperature === 'undefined' ? 1 : modelOptions.temperature, // 0 - 1, 1 is default
topP: typeof modelOptions.topP === 'undefined' ? 0.7 : modelOptions.topP, // 0 - 1, default: 0.7
topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK, // 1-40, default: 40
stop: modelOptions.stop, // no stop method for now
};
this.maxContextTokens = this.options.maxContextTokens || 99999;
this.isClaude3 = this.modelOptions.model.includes('claude-3');
this.useMessages = this.isClaude3 || !!this.options.attachments;
this.defaultVisionModel = this.options.visionModel ?? 'claude-3-sonnet-20240229';
this.options.attachments?.then((attachments) => this.checkVisionRequest(attachments));
this.maxContextTokens =
this.options.maxContextTokens ??
getModelMaxTokens(this.modelOptions.model, EModelEndpoint.anthropic) ??
100000;
this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1500;
this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
@@ -59,6 +91,14 @@ class AnthropicClient extends BaseClient {
);
}
this.sender =
this.options.sender ??
getResponseSender({
model: this.modelOptions.model,
endpoint: EModelEndpoint.anthropic,
modelLabel: this.options.modelLabel,
});
this.startToken = '||>';
this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('cl100k_base');
@@ -77,17 +117,95 @@ class AnthropicClient extends BaseClient {
return this;
}
/**
* Get the initialized Anthropic client.
* @returns {Anthropic} The Anthropic client instance.
*/
getClient() {
if (this.options.reverseProxyUrl) {
return new Anthropic({
apiKey: this.apiKey,
baseURL: this.options.reverseProxyUrl,
});
} else {
return new Anthropic({
apiKey: this.apiKey,
});
/** @type {Anthropic.default.RequestOptions} */
const options = {
fetch: this.fetch,
apiKey: this.apiKey,
};
if (this.options.proxy) {
options.httpAgent = new HttpsProxyAgent(this.options.proxy);
}
if (this.options.reverseProxyUrl) {
options.baseURL = this.options.reverseProxyUrl;
}
return new Anthropic(options);
}
getTokenCountForResponse(response) {
return this.getTokenCountForMessage({
role: 'assistant',
content: response.text,
});
}
/**
*
* Checks if the model is a vision model based on request attachments and sets the appropriate options:
* - Sets `this.modelOptions.model` to `gpt-4-vision-preview` if the request is a vision request.
* - Sets `this.isVisionModel` to `true` if vision request.
* - Deletes `this.modelOptions.stop` if vision request.
* @param {MongoFile[]} attachments
*/
checkVisionRequest(attachments) {
const availableModels = this.options.modelsConfig?.[EModelEndpoint.anthropic];
this.isVisionModel = validateVisionModel({ model: this.modelOptions.model, availableModels });
const visionModelAvailable = availableModels?.includes(this.defaultVisionModel);
if (
attachments &&
attachments.some((file) => file?.type && file?.type?.includes('image')) &&
visionModelAvailable &&
!this.isVisionModel
) {
this.modelOptions.model = this.defaultVisionModel;
this.isVisionModel = true;
}
}
/**
* Calculate the token cost in tokens for an image based on its dimensions and detail level.
*
* For reference, see: https://docs.anthropic.com/claude/docs/vision#image-costs
*
* @param {Object} image - The image object.
* @param {number} image.width - The width of the image.
* @param {number} image.height - The height of the image.
* @returns {number} The calculated token cost measured by tokens.
*
*/
calculateImageTokenCost({ width, height }) {
return Math.ceil((width * height) / 750);
}
async addImageURLs(message, attachments) {
const { files, image_urls } = await encodeAndFormat(
this.options.req,
attachments,
EModelEndpoint.anthropic,
);
message.image_urls = image_urls.length ? image_urls : undefined;
return files;
}
async recordTokenUsage({ promptTokens, completionTokens, model, context = 'message' }) {
await spendTokens(
{
context,
user: this.user,
conversationId: this.conversationId,
model: model ?? this.modelOptions.model,
endpointTokenConfig: this.options.endpointTokenConfig,
},
{ promptTokens, completionTokens },
);
}
async buildMessages(messages, parentMessageId) {
@@ -95,32 +213,148 @@ class AnthropicClient extends BaseClient {
messages,
parentMessageId,
});
if (this.options.debug) {
console.debug('AnthropicClient: orderedMessages', orderedMessages, parentMessageId);
logger.debug('[AnthropicClient] orderedMessages', { orderedMessages, parentMessageId });
if (this.options.attachments) {
const attachments = await this.options.attachments;
const images = attachments.filter((file) => file.type.includes('image'));
if (images.length && !this.isVisionModel) {
throw new Error('Images are only supported with the Claude 3 family of models');
}
const latestMessage = orderedMessages[orderedMessages.length - 1];
if (this.message_file_map) {
this.message_file_map[latestMessage.messageId] = attachments;
} else {
this.message_file_map = {
[latestMessage.messageId]: attachments,
};
}
const files = await this.addImageURLs(latestMessage, attachments);
this.options.attachments = files;
}
const formattedMessages = orderedMessages.map((message) => ({
author: message.isCreatedByUser ? this.userLabel : this.assistantLabel,
content: message?.content ?? message.text,
}));
if (this.message_file_map) {
this.contextHandlers = createContextHandlers(
this.options.req,
orderedMessages[orderedMessages.length - 1].text,
);
}
const formattedMessages = orderedMessages.map((message, i) => {
const formattedMessage = this.useMessages
? formatMessage({
message,
endpoint: EModelEndpoint.anthropic,
})
: {
author: message.isCreatedByUser ? this.userLabel : this.assistantLabel,
content: message?.content ?? message.text,
};
const needsTokenCount = this.contextStrategy && !orderedMessages[i].tokenCount;
/* If tokens were never counted, or, is a Vision request and the message has files, count again */
if (needsTokenCount || (this.isVisionModel && (message.image_urls || message.files))) {
orderedMessages[i].tokenCount = this.getTokenCountForMessage(formattedMessage);
}
/* If message has files, calculate image token cost */
if (this.message_file_map && this.message_file_map[message.messageId]) {
const attachments = this.message_file_map[message.messageId];
for (const file of attachments) {
if (file.embedded) {
this.contextHandlers?.processFile(file);
continue;
}
orderedMessages[i].tokenCount += this.calculateImageTokenCost({
width: file.width,
height: file.height,
});
}
}
formattedMessage.tokenCount = orderedMessages[i].tokenCount;
return formattedMessage;
});
if (this.contextHandlers) {
this.augmentedPrompt = await this.contextHandlers.createContext();
this.options.promptPrefix = this.augmentedPrompt + (this.options.promptPrefix ?? '');
}
let { context: messagesInWindow, remainingContextTokens } =
await this.getMessagesWithinTokenLimit(formattedMessages);
const tokenCountMap = orderedMessages
.slice(orderedMessages.length - messagesInWindow.length)
.reduce((map, message, index) => {
const { messageId } = message;
if (!messageId) {
return map;
}
map[messageId] = orderedMessages[index].tokenCount;
return map;
}, {});
logger.debug('[AnthropicClient]', {
messagesInWindow: messagesInWindow.length,
remainingContextTokens,
});
let lastAuthor = '';
let groupedMessages = [];
for (let message of formattedMessages) {
for (let i = 0; i < messagesInWindow.length; i++) {
const message = messagesInWindow[i];
const author = message.role ?? message.author;
// If last author is not same as current author, add to new group
if (lastAuthor !== message.author) {
groupedMessages.push({
author: message.author,
if (lastAuthor !== author) {
const newMessage = {
content: [message.content],
});
lastAuthor = message.author;
};
if (message.role) {
newMessage.role = message.role;
} else {
newMessage.author = message.author;
}
groupedMessages.push(newMessage);
lastAuthor = author;
// If same author, append content to the last group
} else {
groupedMessages[groupedMessages.length - 1].content.push(message.content);
}
}
groupedMessages = groupedMessages.map((msg, i) => {
const isLast = i === groupedMessages.length - 1;
if (msg.content.length === 1) {
const content = msg.content[0];
return {
...msg,
// reason: final assistant content cannot end with trailing whitespace
content:
isLast && this.useMessages && msg.role === 'assistant' && typeof content === 'string'
? content?.trim()
: content,
};
}
if (!this.useMessages && msg.tokenCount) {
delete msg.tokenCount;
}
return msg;
});
let identityPrefix = '';
if (this.options.userLabel) {
identityPrefix = `\nHuman's name: ${this.options.userLabel}`;
@@ -146,9 +380,10 @@ class AnthropicClient extends BaseClient {
// Prompt AI to respond, empty if last message was from AI
let isEdited = lastAuthor === this.assistantLabel;
const promptSuffix = isEdited ? '' : `${promptPrefix}${this.assistantLabel}\n`;
let currentTokenCount = isEdited
? this.getTokenCount(promptPrefix)
: this.getTokenCount(promptSuffix);
let currentTokenCount =
isEdited || this.useMessages
? this.getTokenCount(promptPrefix)
: this.getTokenCount(promptSuffix);
let promptBody = '';
const maxTokenCount = this.maxPromptTokens;
@@ -216,7 +451,69 @@ class AnthropicClient extends BaseClient {
return true;
};
await buildPromptBody();
const messagesPayload = [];
const buildMessagesPayload = async () => {
let canContinue = true;
if (promptPrefix) {
this.systemMessage = promptPrefix;
}
while (currentTokenCount < maxTokenCount && groupedMessages.length > 0 && canContinue) {
const message = groupedMessages.pop();
let tokenCountForMessage = message.tokenCount ?? this.getTokenCountForMessage(message);
const newTokenCount = currentTokenCount + tokenCountForMessage;
const exceededMaxCount = newTokenCount > maxTokenCount;
if (exceededMaxCount && messagesPayload.length === 0) {
throw new Error(
`Prompt is too long. Max token count is ${maxTokenCount}, but prompt is ${newTokenCount} tokens long.`,
);
} else if (exceededMaxCount) {
canContinue = false;
break;
}
delete message.tokenCount;
messagesPayload.unshift(message);
currentTokenCount = newTokenCount;
// Switch off isEdited after using it once
if (isEdited && message.role === 'assistant') {
isEdited = false;
}
// Wait for next tick to avoid blocking the event loop
await new Promise((resolve) => setImmediate(resolve));
}
};
const processTokens = () => {
// Add 2 tokens for metadata after all messages have been counted.
currentTokenCount += 2;
// Use up to `this.maxContextTokens` tokens (prompt + response), but try to leave `this.maxTokens` tokens for the response.
this.modelOptions.maxOutputTokens = Math.min(
this.maxContextTokens - currentTokenCount,
this.maxResponseTokens,
);
};
if (this.modelOptions.model.startsWith('claude-3')) {
await buildMessagesPayload();
processTokens();
return {
prompt: messagesPayload,
context: messagesInWindow,
promptTokens: currentTokenCount,
tokenCountMap,
};
} else {
await buildPromptBody();
processTokens();
}
if (nextMessage.remove) {
promptBody = promptBody.replace(nextMessage.messageString, '');
@@ -226,20 +523,24 @@ class AnthropicClient extends BaseClient {
let prompt = `${promptBody}${promptSuffix}`;
// Add 2 tokens for metadata after all messages have been counted.
currentTokenCount += 2;
// Use up to `this.maxContextTokens` tokens (prompt + response), but try to leave `this.maxTokens` tokens for the response.
this.modelOptions.maxOutputTokens = Math.min(
this.maxContextTokens - currentTokenCount,
this.maxResponseTokens,
);
return { prompt, context };
return { prompt, context, promptTokens: currentTokenCount, tokenCountMap };
}
getCompletion() {
console.log('AnthropicClient doesn\'t use getCompletion (all handled in sendCompletion)');
logger.debug('AnthropicClient doesn\'t use getCompletion (all handled in sendCompletion)');
}
/**
* Creates a message or completion response using the Anthropic client.
* @param {Anthropic} client - The Anthropic client instance.
* @param {Anthropic.default.MessageCreateParams | Anthropic.default.CompletionCreateParams} options - The options for the message or completion.
* @param {boolean} useMessages - Whether to use messages or completions. Defaults to `this.useMessages`.
* @returns {Promise<Anthropic.default.Message | Anthropic.default.Completion>} The response from the Anthropic client.
*/
async createResponse(client, options, useMessages) {
return useMessages ?? this.useMessages
? await client.messages.create(options)
: await client.completions.create(options);
}
async sendCompletion(payload, { onProgress, abortController }) {
@@ -254,12 +555,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 = {
@@ -276,61 +572,107 @@ class AnthropicClient extends BaseClient {
topP: top_p,
topK: top_k,
} = this.modelOptions;
const requestOptions = {
prompt: payload,
model,
stream: stream || true,
max_tokens_to_sample: maxOutputTokens || 1500,
stop_sequences,
temperature,
metadata,
top_p,
top_k,
};
if (this.options.debug) {
console.log('AnthropicClient: requestOptions');
console.dir(requestOptions, { depth: null });
}
const response = await client.completions.create(requestOptions);
signal.addEventListener('abort', () => {
if (this.options.debug) {
console.log('AnthropicClient: message aborted!');
}
response.controller.abort();
});
for await (const completion of response) {
if (this.options.debug) {
// Uncomment to debug message stream
// console.debug(completion);
}
text += completion.completion;
onProgress(completion.completion);
if (this.useMessages) {
requestOptions.messages = payload;
requestOptions.max_tokens = maxOutputTokens || 1500;
} else {
requestOptions.prompt = payload;
requestOptions.max_tokens_to_sample = maxOutputTokens || 1500;
}
signal.removeEventListener('abort', () => {
if (this.options.debug) {
console.log('AnthropicClient: message aborted!');
if (this.systemMessage) {
requestOptions.system = this.systemMessage;
}
logger.debug('[AnthropicClient]', { ...requestOptions });
const handleChunk = (currentChunk) => {
if (currentChunk) {
text += currentChunk;
onProgress(currentChunk);
}
response.controller.abort();
});
};
const maxRetries = 3;
async function processResponse() {
let attempts = 0;
while (attempts < maxRetries) {
let response;
try {
response = await this.createResponse(client, requestOptions);
signal.addEventListener('abort', () => {
logger.debug('[AnthropicClient] message aborted!');
if (response.controller?.abort) {
response.controller.abort();
}
});
for await (const completion of response) {
// Handle each completion as before
if (completion?.delta?.text) {
handleChunk(completion.delta.text);
} else if (completion.completion) {
handleChunk(completion.completion);
}
}
// Successful processing, exit loop
break;
} catch (error) {
attempts += 1;
logger.warn(
`User: ${this.user} | Anthropic Request ${attempts} failed: ${error.message}`,
);
if (attempts < maxRetries) {
await delayBeforeRetry(attempts, 350);
} else {
throw new Error(`Operation failed after ${maxRetries} attempts: ${error.message}`);
}
} finally {
signal.removeEventListener('abort', () => {
logger.debug('[AnthropicClient] message aborted!');
if (response.controller?.abort) {
response.controller.abort();
}
});
}
}
}
await processResponse.bind(this)();
return text.trim();
}
getSaveOptions() {
return {
maxContextTokens: this.options.maxContextTokens,
promptPrefix: this.options.promptPrefix,
modelLabel: this.options.modelLabel,
resendFiles: this.options.resendFiles,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
...this.modelOptions,
};
}
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 = {}) {
@@ -350,6 +692,78 @@ class AnthropicClient extends BaseClient {
getTokenCount(text) {
return this.gptEncoder.encode(text, 'all').length;
}
/**
* Generates a concise title for a conversation based on the user's input text and response.
* Involves sending a chat completion request with specific instructions for title generation.
*
* This function capitlizes on [Anthropic's function calling training](https://docs.anthropic.com/claude/docs/functions-external-tools).
*
* @param {Object} params - The parameters for the conversation title generation.
* @param {string} params.text - The user's input.
* @param {string} [params.responseText=''] - The AI's immediate response to the user.
*
* @returns {Promise<string | 'New Chat'>} A promise that resolves to the generated conversation title.
* In case of failure, it will return the default title, "New Chat".
*/
async titleConvo({ text, responseText = '' }) {
let title = 'New Chat';
const convo = `<initial_message>
${truncateText(text)}
</initial_message>
<response>
${JSON.stringify(truncateText(responseText))}
</response>`;
const { ANTHROPIC_TITLE_MODEL } = process.env ?? {};
const model = this.options.titleModel ?? ANTHROPIC_TITLE_MODEL ?? 'claude-3-haiku-20240307';
const system = titleFunctionPrompt;
const titleChatCompletion = async () => {
const content = `<conversation_context>
${convo}
</conversation_context>
Please generate a title for this conversation.`;
const titleMessage = { role: 'user', content };
const requestOptions = {
model,
temperature: 0.3,
max_tokens: 1024,
system,
stop_sequences: ['\n\nHuman:', '\n\nAssistant', '</function_calls>'],
messages: [titleMessage],
};
try {
const response = await this.createResponse(this.getClient(), requestOptions, true);
let promptTokens = response?.usage?.input_tokens;
let completionTokens = response?.usage?.output_tokens;
if (!promptTokens) {
promptTokens = this.getTokenCountForMessage(titleMessage);
promptTokens += this.getTokenCountForMessage({ role: 'system', content: system });
}
if (!completionTokens) {
completionTokens = this.getTokenCountForMessage(response.content[0]);
}
await this.recordTokenUsage({
model,
promptTokens,
completionTokens,
context: 'title',
});
const text = response.content[0].text;
title = parseParamFromPrompt(text, 'title');
} catch (e) {
logger.error('[AnthropicClient] There was an issue generating the title', e);
}
};
await titleChatCompletion();
logger.debug('[AnthropicClient] Convo Title: ' + title);
return title;
}
}
module.exports = AnthropicClient;

View File

@@ -1,8 +1,12 @@
const crypto = require('crypto');
const fetch = require('node-fetch');
const { supportsBalanceCheck, Constants } = require('librechat-data-provider');
const { getConvo, getMessages, saveMessage, updateMessage, saveConvo } = require('~/models');
const { addSpaceIfNeeded, isEnabled } = require('~/server/utils');
const checkBalance = require('~/models/checkBalance');
const { getFiles } = require('~/models/File');
const TextStream = require('./TextStream');
const { getConvo, getMessages, saveMessage, updateMessage, saveConvo } = require('../../models');
const { addSpaceIfNeeded, isEnabled } = require('../../server/utils');
const checkBalance = require('../../models/checkBalance');
const { logger } = require('~/config');
class BaseClient {
constructor(apiKey, options = {}) {
@@ -14,13 +18,14 @@ class BaseClient {
month: 'long',
day: 'numeric',
});
this.fetch = this.fetch.bind(this);
}
setOptions() {
throw new Error('Method \'setOptions\' must be implemented.');
}
getCompletion() {
async getCompletion() {
throw new Error('Method \'getCompletion\' must be implemented.');
}
@@ -41,15 +46,30 @@ 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,
});
}
/**
* Makes an HTTP request and logs the process.
*
* @param {RequestInfo} url - The URL to make the request to. Can be a string or a Request object.
* @param {RequestInit} [init] - Optional init options for the request.
* @returns {Promise<Response>} - A promise that resolves to the response of the fetch request.
*/
async fetch(_url, init) {
let url = _url;
if (this.options.directEndpoint) {
url = this.options.reverseProxyUrl;
}
logger.debug(`Making request to ${url}`);
return await fetch(url, init);
}
getBuildMessagesOptions() {
@@ -62,7 +82,7 @@ class BaseClient {
}
async setMessageOptions(opts = {}) {
if (opts && typeof opts === 'object') {
if (opts && opts.replaceOptions) {
this.setOptions(opts);
}
@@ -72,7 +92,7 @@ class BaseClient {
const saveOptions = this.getSaveOptions();
this.abortController = opts.abortController ?? new AbortController();
const conversationId = opts.conversationId ?? crypto.randomUUID();
const parentMessageId = opts.parentMessageId ?? '00000000-0000-0000-0000-000000000000';
const parentMessageId = opts.parentMessageId ?? Constants.NO_PARENT;
const userMessageId = opts.overrideParentMessageId ?? crypto.randomUUID();
let responseMessageId = opts.responseMessageId ?? crypto.randomUUID();
let head = isEdited ? responseMessageId : parentMessageId;
@@ -194,14 +214,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 +298,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 +326,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 +355,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 +376,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 };
}
@@ -382,6 +391,14 @@ class BaseClient {
const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } =
await this.handleStartMethods(message, opts);
if (opts.progressCallback) {
opts.onProgress = opts.progressCallback.call(null, {
...(opts.progressOptions ?? {}),
parentMessageId: userMessage.messageId,
messageId: responseMessageId,
});
}
const { generation = '' } = opts;
// It's not necessary to push to currentMessages
@@ -417,14 +434,14 @@ class BaseClient {
// this only matters when buildMessages is utilizing the parentMessageId, and may vary on implementation
isEdited ? head : userMessage.messageId,
this.getBuildMessagesOptions(opts),
opts,
);
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);
@@ -434,7 +451,10 @@ class BaseClient {
await this.saveMessageToDatabase(userMessage, saveOptions, user);
}
if (isEnabled(process.env.CHECK_BALANCE)) {
if (
isEnabled(process.env.CHECK_BALANCE) &&
supportsBalanceCheck[this.options.endpointType ?? this.options.endpoint]
) {
await checkBalance({
req: this.options.req,
res: this.options.res,
@@ -442,13 +462,16 @@ class BaseClient {
user: this.user,
tokenType: 'prompt',
amount: promptTokens,
debug: this.options.debug,
model: this.modelOptions.model,
endpoint: this.options.endpoint,
endpointTokenConfig: this.options.endpointTokenConfig,
},
});
}
const completion = await this.sendCompletion(payload, opts);
this.abortController.requestCompleted = true;
const responseMessage = {
messageId: responseMessageId,
conversationId,
@@ -459,6 +482,9 @@ class BaseClient {
sender: this.sender,
text: addSpaceIfNeeded(generation) + completion,
promptTokens,
iconURL: this.options.iconURL,
endpoint: this.options.endpoint,
...(this.metadata ?? {}),
};
if (
@@ -481,9 +507,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 })) ?? [];
@@ -496,37 +520,56 @@ class BaseClient {
mapMethod = this.getMessageMapMethod();
}
const orderedMessages = this.constructor.getMessagesForConversation({
let _messages = this.constructor.getMessagesForConversation({
messages,
parentMessageId,
mapMethod,
});
_messages = await this.addPreviousAttachments(_messages);
if (!this.shouldSummarize) {
return orderedMessages;
return _messages;
}
// Find the latest message with a 'summary' property
for (let i = orderedMessages.length - 1; i >= 0; i--) {
if (orderedMessages[i]?.summary) {
this.previous_summary = orderedMessages[i];
for (let i = _messages.length - 1; i >= 0; i--) {
if (_messages[i]?.summary) {
this.previous_summary = _messages[i];
break;
}
}
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;
return _messages;
}
/**
* Save a message to the database.
* @param {TMessage} message
* @param {Partial<TConversation>} endpointOptions
* @param {string | null} user
*/
async saveMessageToDatabase(message, endpointOptions, user = null) {
await saveMessage({ ...message, user, unfinished: false, cancelled: false });
await saveMessage({
...message,
endpoint: this.options.endpoint,
unfinished: false,
user,
});
await saveConvo(user, {
conversationId: message.conversationId,
endpoint: this.options.endpoint,
endpointType: this.options.endpointType,
...endpointOptions,
});
}
@@ -548,15 +591,15 @@ class BaseClient {
*
* Each message object should have an 'id' or 'messageId' property and may have a 'parentMessageId' property.
* The 'parentMessageId' is the ID of the message that the current message is a reply to.
* If 'parentMessageId' is not present, null, or is '00000000-0000-0000-0000-000000000000',
* If 'parentMessageId' is not present, null, or is Constants.NO_PARENT,
* the message is considered a root message.
*
* @param {Object} options - The options for the function.
* @param {Array} options.messages - An array of message objects. Each object should have either an 'id' or 'messageId' property, and may have a 'parentMessageId' property.
* @param {TMessage[]} options.messages - An array of message objects. Each object should have either an 'id' or 'messageId' property, and may have a 'parentMessageId' property.
* @param {string} options.parentMessageId - The ID of the parent message to start the traversal from.
* @param {Function} [options.mapMethod] - An optional function to map over the ordered messages. If provided, it will be applied to each message in the resulting array.
* @param {boolean} [options.summary=false] - If set to true, the traversal modifies messages with 'summary' and 'summaryTokenCount' properties and stops at the message with a 'summary' property.
* @returns {Array} An array containing the messages in the order they should be displayed, starting with the most recent message with a 'summary' property if the 'summary' option is true, and ending with the message identified by 'parentMessageId'.
* @returns {TMessage[]} An array containing the messages in the order they should be displayed, starting with the most recent message with a 'summary' property if the 'summary' option is true, and ending with the message identified by 'parentMessageId'.
*/
static getMessagesForConversation({
messages,
@@ -603,9 +646,7 @@ class BaseClient {
}
currentMessageId =
message.parentMessageId === '00000000-0000-0000-0000-000000000000'
? null
: message.parentMessageId;
message.parentMessageId === Constants.NO_PARENT ? null : message.parentMessageId;
}
orderedMessages.reverse();
@@ -624,6 +665,11 @@ class BaseClient {
* An additional 3 tokens need to be added for assistant label priming after all messages have been counted.
* In our implementation, this is accounted for in the getMessagesWithinTokenLimit method.
*
* The content parts example was adapted from the following example:
* https://github.com/openai/openai-cookbook/pull/881/files
*
* Note: image token calculation is to be done elsewhere where we have access to the image metadata
*
* @param {Object} message
*/
getTokenCountForMessage(message) {
@@ -636,14 +682,34 @@ class BaseClient {
tokensPerName = -1;
}
const processValue = (value) => {
if (Array.isArray(value)) {
for (let item of value) {
if (!item || !item.type || item.type === 'image_url') {
continue;
}
const nestedValue = item[item.type];
if (!nestedValue) {
continue;
}
processValue(nestedValue);
}
} else {
numTokens += this.getTokenCount(value);
}
};
let numTokens = tokensPerMessage;
for (let [key, value] of Object.entries(message)) {
numTokens += this.getTokenCount(value);
processValue(value);
if (key === 'name') {
numTokens += tokensPerName;
}
}
return numTokens;
}
@@ -654,6 +720,54 @@ class BaseClient {
return await this.sendCompletion(payload, opts);
}
/**
*
* @param {TMessage[]} _messages
* @returns {Promise<TMessage[]>}
*/
async addPreviousAttachments(_messages) {
if (!this.options.resendFiles) {
return _messages;
}
/**
*
* @param {TMessage} message
*/
const processMessage = async (message) => {
if (!this.message_file_map) {
/** @type {Record<string, MongoFile[]> */
this.message_file_map = {};
}
const fileIds = message.files.map((file) => file.file_id);
const files = await getFiles({
file_id: { $in: fileIds },
});
await this.addImageURLs(message, files);
this.message_file_map[message.messageId] = files;
return message;
};
const promises = [];
for (const message of _messages) {
if (!message.files) {
promises.push(message);
continue;
}
promises.push(processMessage(message));
}
const messages = await Promise.all(promises);
this.checkVisionRequest(Object.values(this.message_file_map ?? {}).flat());
return messages;
}
}
module.exports = BaseClient;

View File

@@ -1,9 +1,19 @@
const crypto = require('crypto');
const Keyv = require('keyv');
const crypto = require('crypto');
const {
EModelEndpoint,
resolveHeaders,
CohereConstants,
mapModelToAzureConfig,
} = require('librechat-data-provider');
const { CohereClient } = require('cohere-ai');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source');
const { createCoherePayload } = require('./llm');
const { Agent, ProxyAgent } = require('undici');
const BaseClient = require('./BaseClient');
const { logger } = require('~/config');
const { extractBaseURL, constructAzureURL, genAzureChatCompletion } = require('~/utils');
const CHATGPT_MODEL = 'gpt-3.5-turbo';
const tokenizersCache = {};
@@ -140,11 +150,13 @@ class ChatGPTClient extends BaseClient {
return tokenizer;
}
async getCompletion(input, onProgress, abortController = null) {
/** @type {getCompletion} */
async getCompletion(input, onProgress, onTokenProgress, abortController = null) {
if (!abortController) {
abortController = new AbortController();
}
const modelOptions = { ...this.modelOptions };
let modelOptions = { ...this.modelOptions };
if (typeof onProgress === 'function') {
modelOptions.stream = true;
}
@@ -159,50 +171,176 @@ class ChatGPTClient extends BaseClient {
}
const { debug } = this.options;
const url = this.completionsUrl;
let baseURL = this.completionsUrl;
if (debug) {
console.debug();
console.debug(url);
console.debug(baseURL);
console.debug(modelOptions);
console.debug();
}
const opts = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(modelOptions),
dispatcher: new Agent({
bodyTimeout: 0,
headersTimeout: 0,
}),
};
if (this.apiKey && this.options.azure) {
opts.headers['api-key'] = this.apiKey;
if (this.isVisionModel) {
modelOptions.max_tokens = 4000;
}
/** @type {TAzureConfig | undefined} */
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];
const isAzure = this.azure || this.options.azure;
if (
(isAzure && this.isVisionModel && azureConfig) ||
(azureConfig && this.isVisionModel && this.options.endpoint === EModelEndpoint.azureOpenAI)
) {
const { modelGroupMap, groupMap } = azureConfig;
const {
azureOptions,
baseURL,
headers = {},
serverless,
} = mapModelToAzureConfig({
modelName: modelOptions.model,
modelGroupMap,
groupMap,
});
opts.headers = resolveHeaders(headers);
this.langchainProxy = extractBaseURL(baseURL);
this.apiKey = azureOptions.azureOpenAIApiKey;
const groupName = modelGroupMap[modelOptions.model].group;
this.options.addParams = azureConfig.groupMap[groupName].addParams;
this.options.dropParams = azureConfig.groupMap[groupName].dropParams;
// Note: `forcePrompt` not re-assigned as only chat models are vision models
this.azure = !serverless && azureOptions;
this.azureEndpoint =
!serverless && genAzureChatCompletion(this.azure, modelOptions.model, this);
}
if (this.options.headers) {
opts.headers = { ...opts.headers, ...this.options.headers };
}
if (isAzure) {
// Azure does not accept `model` in the body, so we need to remove it.
delete modelOptions.model;
baseURL = this.langchainProxy
? constructAzureURL({
baseURL: this.langchainProxy,
azureOptions: this.azure,
})
: this.azureEndpoint.split(/(?<!\/)\/(chat|completion)\//)[0];
if (this.options.forcePrompt) {
baseURL += '/completions';
} else {
baseURL += '/chat/completions';
}
opts.defaultQuery = { 'api-version': this.azure.azureOpenAIApiVersion };
opts.headers = { ...opts.headers, 'api-key': this.apiKey };
} else if (this.apiKey) {
opts.headers.Authorization = `Bearer ${this.apiKey}`;
}
if (process.env.OPENAI_ORGANIZATION) {
opts.headers['OpenAI-Organization'] = process.env.OPENAI_ORGANIZATION;
}
if (this.useOpenRouter) {
opts.headers['HTTP-Referer'] = 'https://librechat.ai';
opts.headers['X-Title'] = 'LibreChat';
}
if (this.options.headers) {
opts.headers = { ...opts.headers, ...this.options.headers };
}
if (this.options.proxy) {
opts.dispatcher = new ProxyAgent(this.options.proxy);
}
/* hacky fixes for Mistral AI API:
- Re-orders system message to the top of the messages payload, as not allowed anywhere else
- If there is only one message and it's a system message, change the role to user
*/
if (baseURL.includes('https://api.mistral.ai/v1') && modelOptions.messages) {
const { messages } = modelOptions;
const systemMessageIndex = messages.findIndex((msg) => msg.role === 'system');
if (systemMessageIndex > 0) {
const [systemMessage] = messages.splice(systemMessageIndex, 1);
messages.unshift(systemMessage);
}
modelOptions.messages = messages;
if (messages.length === 1 && messages[0].role === 'system') {
modelOptions.messages[0].role = 'user';
}
}
if (this.options.addParams && typeof this.options.addParams === 'object') {
modelOptions = {
...modelOptions,
...this.options.addParams,
};
logger.debug('[ChatGPTClient] chatCompletion: added params', {
addParams: this.options.addParams,
modelOptions,
});
}
if (this.options.dropParams && Array.isArray(this.options.dropParams)) {
this.options.dropParams.forEach((param) => {
delete modelOptions[param];
});
logger.debug('[ChatGPTClient] chatCompletion: dropped params', {
dropParams: this.options.dropParams,
modelOptions,
});
}
if (baseURL.startsWith(CohereConstants.API_URL)) {
const payload = createCoherePayload({ modelOptions });
return await this.cohereChatCompletion({ payload, onTokenProgress });
}
if (baseURL.includes('v1') && !baseURL.includes('/completions') && !this.isChatCompletion) {
baseURL = baseURL.split('v1')[0] + 'v1/completions';
} else if (
baseURL.includes('v1') &&
!baseURL.includes('/chat/completions') &&
this.isChatCompletion
) {
baseURL = baseURL.split('v1')[0] + 'v1/chat/completions';
}
const BASE_URL = new URL(baseURL);
if (opts.defaultQuery) {
Object.entries(opts.defaultQuery).forEach(([key, value]) => {
BASE_URL.searchParams.append(key, value);
});
delete opts.defaultQuery;
}
const completionsURL = BASE_URL.toString();
opts.body = JSON.stringify(modelOptions);
if (modelOptions.stream) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
try {
let done = false;
await fetchEventSource(url, {
await fetchEventSource(completionsURL, {
...opts,
signal: abortController.signal,
async onopen(response) {
@@ -230,7 +368,6 @@ class ChatGPTClient extends BaseClient {
// workaround for private API not sending [DONE] event
if (!done) {
onProgress('[DONE]');
abortController.abort();
resolve();
}
},
@@ -243,14 +380,13 @@ class ChatGPTClient extends BaseClient {
},
onmessage(message) {
if (debug) {
// console.debug(message);
console.debug(message);
}
if (!message.data || message.event === 'ping') {
return;
}
if (message.data === '[DONE]') {
onProgress('[DONE]');
abortController.abort();
resolve();
done = true;
return;
@@ -263,7 +399,7 @@ class ChatGPTClient extends BaseClient {
}
});
}
const response = await fetch(url, {
const response = await fetch(completionsURL, {
...opts,
signal: abortController.signal,
});
@@ -281,6 +417,43 @@ class ChatGPTClient extends BaseClient {
return response.json();
}
/** @type {cohereChatCompletion} */
async cohereChatCompletion({ payload, onTokenProgress }) {
const cohere = new CohereClient({
token: this.apiKey,
environment: this.completionsUrl,
});
if (!payload.stream) {
const chatResponse = await cohere.chat(payload);
return chatResponse.text;
}
const chatStream = await cohere.chatStream(payload);
let reply = '';
for await (const message of chatStream) {
if (!message) {
continue;
}
if (message.eventType === 'text-generation' && message.text) {
onTokenProgress(message.text);
reply += message.text;
}
/*
Cohere API Chinese Unicode character replacement hotfix.
Should be un-commented when the following issue is resolved:
https://github.com/cohere-ai/cohere-typescript/issues/151
else if (message.eventType === 'stream-end' && message.response) {
reply = message.response.text;
}
*/
}
return reply;
}
async generateTitle(userMessage, botMessage) {
const instructionsPayload = {
role: 'system',
@@ -548,7 +721,7 @@ ${botMessage.message}
if (isChatGptModel) {
return { prompt: [instructionsPayload, messagePayload], context };
}
return { prompt, context };
return { prompt, context, promptTokens: currentTokenCount };
}
getTokenCount(text) {

View File

@@ -1,23 +1,64 @@
const BaseClient = require('./BaseClient');
const { google } = require('googleapis');
const { Agent, ProxyAgent } = require('undici');
const { ChatVertexAI } = require('@langchain/google-vertexai');
const { ChatGoogleGenerativeAI } = require('@langchain/google-genai');
const { GoogleGenerativeAI: GenAI } = require('@google/generative-ai');
const { GoogleVertexAI } = require('@langchain/community/llms/googlevertexai');
const { ChatGoogleVertexAI } = require('langchain/chat_models/googlevertexai');
const { AIMessage, HumanMessage, SystemMessage } = require('langchain/schema');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const {
validateVisionModel,
getResponseSender,
endpointSettings,
EModelEndpoint,
VisionModes,
AuthKeys,
} = require('librechat-data-provider');
const { encodeAndFormat } = require('~/server/services/Files/images');
const { formatMessage, createContextHandlers } = require('./prompts');
const { getModelMaxTokens } = require('~/utils');
const BaseClient = require('./BaseClient');
const { logger } = require('~/config');
const loc = 'us-central1';
const publisher = 'google';
const endpointPrefix = `https://${loc}-aiplatform.googleapis.com`;
// const apiEndpoint = loc + '-aiplatform.googleapis.com';
const tokenizersCache = {};
const settings = endpointSettings[EModelEndpoint.google];
class GoogleClient extends BaseClient {
constructor(credentials, options = {}) {
super('apiKey', options);
this.client_email = credentials.client_email;
this.project_id = credentials.project_id;
this.private_key = credentials.private_key;
this.sender = 'PaLM2';
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;
}
this.setOptions(options);
}
/* Google/PaLM2 specific methods */
/* Google specific methods */
constructUrl() {
return `https://us-central1-aiplatform.googleapis.com/v1/projects/${this.project_id}/locations/us-central1/publishers/google/models/${this.modelOptions.model}:predict`;
return `${endpointPrefix}/v1/projects/${this.project_id}/locations/${loc}/publishers/${publisher}/models/${this.modelOptions.model}:serverStreamingPredict`;
}
async getClient() {
@@ -26,8 +67,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;
}
});
@@ -35,6 +75,22 @@ class GoogleClient extends BaseClient {
return jwtClient;
}
async getAccessToken() {
const scopes = ['https://www.googleapis.com/auth/cloud-platform'];
const jwtClient = new google.auth.JWT(this.client_email, null, this.private_key, scopes);
return new Promise((resolve, reject) => {
jwtClient.authorize((err, tokens) => {
if (err) {
logger.error('jwtClient failed to authorize', err);
reject(err);
} else {
resolve(tokens.access_token);
}
});
});
}
/* Required Client methods */
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
@@ -53,30 +109,47 @@ class GoogleClient extends BaseClient {
this.options = options;
}
this.options.examples = this.options.examples.filter(
(obj) => obj.input.content !== '' && obj.output.content !== '',
);
this.options.examples = (this.options.examples ?? [])
.filter((ex) => ex)
.filter((obj) => obj.input.content !== '' && obj.output.content !== '');
const modelOptions = this.options.modelOptions || {};
this.modelOptions = {
...modelOptions,
// set some good defaults (check for undefined in some cases because they may be 0)
model: modelOptions.model || 'chat-bison',
temperature: typeof modelOptions.temperature === 'undefined' ? 0.2 : modelOptions.temperature, // 0 - 1, 0.2 is recommended
topP: typeof modelOptions.topP === 'undefined' ? 0.95 : modelOptions.topP, // 0 - 1, default: 0.95
topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK, // 1-40, default: 40
model: modelOptions.model || settings.model.default,
temperature:
typeof modelOptions.temperature === 'undefined'
? settings.temperature.default
: modelOptions.temperature,
topP: typeof modelOptions.topP === 'undefined' ? settings.topP.default : modelOptions.topP,
topK: typeof modelOptions.topK === 'undefined' ? settings.topK.default : modelOptions.topK,
// stop: modelOptions.stop // no stop method for now
};
this.isChatModel = this.modelOptions.model.startsWith('chat-');
this.options.attachments?.then((attachments) => this.checkVisionRequest(attachments));
/** @type {boolean} Whether using a "GenerativeAI" Model */
this.isGenerativeModel = this.modelOptions.model.includes('gemini');
const { isGenerativeModel } = this;
this.isChatModel = !isGenerativeModel && this.modelOptions.model.includes('chat');
const { isChatModel } = this;
this.isTextModel = this.modelOptions.model.startsWith('text-');
this.isTextModel =
!isGenerativeModel && !isChatModel && /code|text/.test(this.modelOptions.model);
const { isTextModel } = this;
this.maxContextTokens = this.options.maxContextTokens || (isTextModel ? 8000 : 4096);
this.maxContextTokens =
this.options.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 || 1024;
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;
@@ -88,10 +161,18 @@ class GoogleClient extends BaseClient {
);
}
this.sender =
this.options.sender ??
getResponseSender({
model: this.modelOptions.model,
endpoint: EModelEndpoint.google,
modelLabel: this.options.modelLabel,
});
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.
@@ -99,8 +180,8 @@ class GoogleClient extends BaseClient {
this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('cl100k_base');
} else if (isTextModel) {
this.startToken = '<|im_start|>';
this.endToken = '<|im_end|>';
this.startToken = '||>';
this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, {
'<|im_start|>': 100264,
'<|im_end|>': 100265,
@@ -138,22 +219,180 @@ class GoogleClient extends BaseClient {
return this;
}
getMessageMapMethod() {
/**
*
* Checks if the model is a vision model based on request attachments and sets the appropriate options:
* @param {MongoFile[]} attachments
*/
checkVisionRequest(attachments) {
/* Validation vision request */
this.defaultVisionModel = this.options.visionModel ?? 'gemini-pro-vision';
const availableModels = this.options.modelsConfig?.[EModelEndpoint.google];
this.isVisionModel = validateVisionModel({ model: this.modelOptions.model, availableModels });
if (
attachments &&
attachments.some((file) => file?.type && file?.type?.includes('image')) &&
availableModels?.includes(this.defaultVisionModel) &&
!this.isVisionModel
) {
this.modelOptions.model = this.defaultVisionModel;
this.isVisionModel = true;
}
if (this.isVisionModel && !attachments && this.modelOptions.model.includes('gemini-pro')) {
this.modelOptions.model = 'gemini-pro';
this.isVisionModel = false;
}
}
formatMessages() {
return ((message) => ({
author: message?.author ?? (message.isCreatedByUser ? this.userLabel : this.modelLabel),
content: message?.content ?? message.text,
})).bind(this);
}
buildMessages(messages = []) {
const formattedMessages = messages.map(this.getMessageMapMethod());
/**
* Formats messages for generative AI
* @param {TMessage[]} messages
* @returns
*/
async formatGenerativeMessages(messages) {
const formattedMessages = [];
const attachments = await this.options.attachments;
const latestMessage = { ...messages[messages.length - 1] };
const files = await this.addImageURLs(latestMessage, attachments, VisionModes.generative);
this.options.attachments = files;
messages[messages.length - 1] = latestMessage;
for (const _message of messages) {
const role = _message.isCreatedByUser ? this.userLabel : this.modelLabel;
const parts = [];
parts.push({ text: _message.text });
if (!_message.image_urls?.length) {
formattedMessages.push({ role, parts });
continue;
}
for (const images of _message.image_urls) {
if (images.inlineData) {
parts.push({ inlineData: images.inlineData });
}
}
formattedMessages.push({ role, parts });
}
return formattedMessages;
}
/**
*
* Adds image URLs to the message object and returns the files
*
* @param {TMessage[]} messages
* @param {MongoFile[]} files
* @returns {Promise<MongoFile[]>}
*/
async addImageURLs(message, attachments, mode = '') {
const { files, image_urls } = await encodeAndFormat(
this.options.req,
attachments,
EModelEndpoint.google,
mode,
);
message.image_urls = image_urls.length ? image_urls : undefined;
return files;
}
/**
* Builds the augmented prompt for attachments
* TODO: Add File API Support
* @param {TMessage[]} messages
*/
async buildAugmentedPrompt(messages = []) {
const attachments = await this.options.attachments;
const latestMessage = { ...messages[messages.length - 1] };
this.contextHandlers = createContextHandlers(this.options.req, latestMessage.text);
if (this.contextHandlers) {
for (const file of attachments) {
if (file.embedded) {
this.contextHandlers?.processFile(file);
continue;
}
}
this.augmentedPrompt = await this.contextHandlers.createContext();
this.options.promptPrefix = this.augmentedPrompt + this.options.promptPrefix;
}
}
async buildVisionMessages(messages = [], parentMessageId) {
const attachments = await this.options.attachments;
const latestMessage = { ...messages[messages.length - 1] };
await this.buildAugmentedPrompt(messages);
const { prompt } = await this.buildMessagesPrompt(messages, parentMessageId);
const files = await this.addImageURLs(latestMessage, attachments);
this.options.attachments = files;
latestMessage.text = prompt;
const payload = {
instances: [
{
messages: [new HumanMessage(formatMessage({ message: latestMessage }))],
},
],
parameters: this.modelOptions,
};
return { prompt: payload };
}
/** @param {TMessage[]} [messages=[]] */
async buildGenerativeMessages(messages = []) {
this.userLabel = 'user';
this.modelLabel = 'model';
const promises = [];
promises.push(await this.formatGenerativeMessages(messages));
promises.push(this.buildAugmentedPrompt(messages));
const [formattedMessages] = await Promise.all(promises);
return { prompt: formattedMessages };
}
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)',
);
}
if (!this.project_id && this.modelOptions.model.includes('1.5')) {
return await this.buildGenerativeMessages(messages);
}
if (this.options.attachments && this.isGenerativeModel) {
return this.buildVisionMessages(messages, parentMessageId);
}
if (this.isTextModel) {
return this.buildMessagesPrompt(messages, parentMessageId);
}
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) {
@@ -164,34 +403,171 @@ class GoogleClient extends BaseClient {
payload.instances[0].examples = this.options.examples;
}
/* TO-DO: text model needs more context since it can't process an array of messages */
if (this.isTextModel) {
payload.instances = [
{
prompt: messages[messages.length - 1].content,
},
];
}
if (this.options.debug) {
console.debug('GoogleClient buildMessages');
console.dir(payload, { depth: null });
}
logger.debug('[GoogleClient] buildMessages', payload);
return { prompt: payload };
}
async getCompletion(payload, abortController = null) {
async buildMessagesPrompt(messages, parentMessageId) {
const orderedMessages = this.constructor.getMessagesForConversation({
messages,
parentMessageId,
});
logger.debug('[GoogleClient]', {
orderedMessages,
parentMessageId,
});
const formattedMessages = orderedMessages.map((message) => ({
author: message.isCreatedByUser ? this.userLabel : this.modelLabel,
content: message?.content ?? message.text,
}));
let lastAuthor = '';
let groupedMessages = [];
for (let message of formattedMessages) {
// If last author is not same as current author, add to new group
if (lastAuthor !== message.author) {
groupedMessages.push({
author: message.author,
content: [message.content],
});
lastAuthor = message.author;
// If same author, append content to the last group
} else {
groupedMessages[groupedMessages.length - 1].content.push(message.content);
}
}
let identityPrefix = '';
if (this.options.userLabel) {
identityPrefix = `\nHuman's name: ${this.options.userLabel}`;
}
if (this.options.modelLabel) {
identityPrefix = `${identityPrefix}\nYou are ${this.options.modelLabel}`;
}
let promptPrefix = (this.options.promptPrefix || '').trim();
if (promptPrefix) {
// If the prompt prefix doesn't end with the end token, add it.
if (!promptPrefix.endsWith(`${this.endToken}`)) {
promptPrefix = `${promptPrefix.trim()}${this.endToken}\n\n`;
}
promptPrefix = `\nContext:\n${promptPrefix}`;
}
if (identityPrefix) {
promptPrefix = `${identityPrefix}${promptPrefix}`;
}
// Prompt AI to respond, empty if last message was from AI
let isEdited = lastAuthor === this.modelLabel;
const promptSuffix = isEdited ? '' : `${promptPrefix}\n\n${this.modelLabel}:\n`;
let currentTokenCount = isEdited
? this.getTokenCount(promptPrefix)
: this.getTokenCount(promptSuffix);
let promptBody = '';
const maxTokenCount = this.maxPromptTokens;
const context = [];
// Iterate backwards through the messages, adding them to the prompt until we reach the max token count.
// Do this within a recursive async function so that it doesn't block the event loop for too long.
// Also, remove the next message when the message that puts us over the token limit is created by the user.
// Otherwise, remove only the exceeding message. This is due to Anthropic's strict payload rule to start with "Human:".
const nextMessage = {
remove: false,
tokenCount: 0,
messageString: '',
};
const buildPromptBody = async () => {
if (currentTokenCount < maxTokenCount && groupedMessages.length > 0) {
const message = groupedMessages.pop();
const isCreatedByUser = message.author === this.userLabel;
// Use promptPrefix if message is edited assistant'
const messagePrefix =
isCreatedByUser || !isEdited
? `\n\n${message.author}:`
: `${promptPrefix}\n\n${message.author}:`;
const messageString = `${messagePrefix}\n${message.content}${this.endToken}\n`;
let newPromptBody = `${messageString}${promptBody}`;
context.unshift(message);
const tokenCountForMessage = this.getTokenCount(messageString);
const newTokenCount = currentTokenCount + tokenCountForMessage;
if (!isCreatedByUser) {
nextMessage.messageString = messageString;
nextMessage.tokenCount = tokenCountForMessage;
}
if (newTokenCount > maxTokenCount) {
if (!promptBody) {
// This is the first message, so we can't add it. Just throw an error.
throw new Error(
`Prompt is too long. Max token count is ${maxTokenCount}, but prompt is ${newTokenCount} tokens long.`,
);
}
// Otherwise, ths message would put us over the token limit, so don't add it.
// if created by user, remove next message, otherwise remove only this message
if (isCreatedByUser) {
nextMessage.remove = true;
}
return false;
}
promptBody = newPromptBody;
currentTokenCount = newTokenCount;
// Switch off isEdited after using it for the first time
if (isEdited) {
isEdited = false;
}
// wait for next tick to avoid blocking the event loop
await new Promise((resolve) => setImmediate(resolve));
return buildPromptBody();
}
return true;
};
await buildPromptBody();
if (nextMessage.remove) {
promptBody = promptBody.replace(nextMessage.messageString, '');
currentTokenCount -= nextMessage.tokenCount;
context.shift();
}
let prompt = `${promptBody}${promptSuffix}`.trim();
// Add 2 tokens for metadata after all messages have been counted.
currentTokenCount += 2;
// Use up to `this.maxContextTokens` tokens (prompt + response), but try to leave `this.maxTokens` tokens for the response.
this.modelOptions.maxOutputTokens = Math.min(
this.maxContextTokens - currentTokenCount,
this.maxResponseTokens,
);
return { prompt, context };
}
async _getCompletion(payload, abortController = null) {
if (!abortController) {
abortController = new AbortController();
}
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',
@@ -208,51 +584,184 @@ 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) {
const model = clientOptions.modelName ?? clientOptions.model;
if (this.project_id && this.isTextModel) {
return new GoogleVertexAI(clientOptions);
} else if (this.project_id && this.isChatModel) {
return new ChatGoogleVertexAI(clientOptions);
} else if (this.project_id) {
return new ChatVertexAI(clientOptions);
} else if (model.includes('1.5')) {
return new GenAI(this.apiKey).getGenerativeModel(
{
...clientOptions,
model,
},
{ apiVersion: 'v1beta' },
);
}
return new ChatGoogleGenerativeAI({ ...clientOptions, apiKey: this.apiKey });
}
async getCompletion(_payload, options = {}) {
const { onProgress, abortController } = options;
const { parameters, instances } = _payload;
const { messages: _messages, context, examples: _examples } = instances?.[0] ?? {};
let examples;
let clientOptions = { ...parameters, maxRetries: 2 };
if (this.project_id) {
clientOptions['authOptions'] = {
credentials: {
...this.serviceKey,
},
projectId: this.project_id,
};
}
if (!parameters) {
clientOptions = { ...clientOptions, ...this.modelOptions };
}
if (this.isGenerativeModel && !this.project_id) {
clientOptions.modelName = clientOptions.model;
delete clientOptions.model;
}
if (_examples && _examples.length) {
examples = _examples
.map((ex) => {
const { input, output } = ex;
if (!input || !output) {
return undefined;
}
return {
input: new HumanMessage(input.content),
output: new AIMessage(output.content),
};
})
.filter((ex) => ex);
clientOptions.examples = examples;
}
const model = this.createLLM(clientOptions);
let reply = '';
const messages = this.isTextModel ? _payload.trim() : _messages;
if (!this.isVisionModel && context && messages?.length > 0) {
messages.unshift(new SystemMessage(context));
}
const modelName = clientOptions.modelName ?? clientOptions.model ?? '';
if (modelName?.includes('1.5') && !this.project_id) {
/** @type {GenerativeModel} */
const client = model;
const requestOptions = {
contents: _payload,
};
if (this.options?.promptPrefix?.length) {
requestOptions.systemInstruction = {
parts: [
{
text: this.options.promptPrefix,
},
],
};
}
const safetySettings = _payload.safetySettings;
requestOptions.safetySettings = safetySettings;
const delay = modelName.includes('flash') ? 8 : 14;
const result = await client.generateContentStream(requestOptions);
for await (const chunk of result.stream) {
const chunkText = chunk.text();
await this.generateTextStream(chunkText, onProgress, {
delay,
});
reply += chunkText;
}
return reply;
}
const safetySettings = _payload.safetySettings;
const stream = await model.stream(messages, {
signal: abortController.signal,
timeout: 7000,
safetySettings: safetySettings,
});
let delay = this.isGenerativeModel ? 12 : 8;
if (modelName.includes('flash')) {
delay = 5;
}
for await (const chunk of stream) {
const chunkText = chunk?.content ?? chunk;
await this.generateTextStream(chunkText, onProgress, {
delay,
});
reply += chunkText;
}
return reply;
}
getSaveOptions() {
return {
promptPrefix: this.options.promptPrefix,
modelLabel: this.options.modelLabel,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
...this.modelOptions,
};
}
getBuildMessagesOptions() {
// console.log('GoogleClient doesn\'t use getBuildMessagesOptions');
// logger.debug('GoogleClient doesn\'t use getBuildMessagesOptions');
}
async sendCompletion(payload, opts = {}) {
console.log('GoogleClient: sendcompletion', payload, opts);
const modelName = payload.parameters?.model;
if (modelName && modelName.toLowerCase().includes('gemini')) {
const safetySettings = [
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold:
process.env.GOOGLE_SAFETY_SEXUALLY_EXPLICIT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: process.env.GOOGLE_SAFETY_HATE_SPEECH || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: process.env.GOOGLE_SAFETY_HARASSMENT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold:
process.env.GOOGLE_SAFETY_DANGEROUS_CONTENT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
];
payload.safetySettings = safetySettings;
}
let reply = '';
let blocked = false;
try {
const result = await this.getCompletion(payload, opts.abortController);
blocked = result?.predictions?.[0]?.safetyAttributes?.blocked;
reply =
result?.predictions?.[0]?.candidates?.[0]?.content ||
result?.predictions?.[0]?.content ||
'';
if (blocked === true) {
reply = `Google blocked a proper response to your message:\n${JSON.stringify(
result.predictions[0].safetyAttributes,
)}${reply.length > 0 ? `\nAI Response:\n${reply}` : ''}`;
}
if (this.options.debug) {
console.debug('result');
console.debug(result);
}
} catch (err) {
console.error('Error: failed to send completion to Google');
console.error(err.message);
}
if (!blocked) {
await this.generateTextStream(reply, opts.onProgress, { delay: 0.5 });
}
reply = await this.getCompletion(payload, opts);
return reply.trim();
}

View File

@@ -0,0 +1,154 @@
const { z } = require('zod');
const axios = require('axios');
const { Ollama } = require('ollama');
const { deriveBaseURL } = require('~/utils');
const { logger } = require('~/config');
const ollamaPayloadSchema = z.object({
mirostat: z.number().optional(),
mirostat_eta: z.number().optional(),
mirostat_tau: z.number().optional(),
num_ctx: z.number().optional(),
repeat_last_n: z.number().optional(),
repeat_penalty: z.number().optional(),
temperature: z.number().optional(),
seed: z.number().nullable().optional(),
stop: z.array(z.string()).optional(),
tfs_z: z.number().optional(),
num_predict: z.number().optional(),
top_k: z.number().optional(),
top_p: z.number().optional(),
stream: z.optional(z.boolean()),
model: z.string(),
});
/**
* @param {string} imageUrl
* @returns {string}
* @throws {Error}
*/
const getValidBase64 = (imageUrl) => {
const parts = imageUrl.split(';base64,');
if (parts.length === 2) {
return parts[1];
} else {
logger.error('Invalid or no Base64 string found in URL.');
}
};
class OllamaClient {
constructor(options = {}) {
const host = deriveBaseURL(options.baseURL ?? 'http://localhost:11434');
/** @type {Ollama} */
this.client = new Ollama({ host });
}
/**
* Fetches Ollama models from the specified base API path.
* @param {string} baseURL
* @returns {Promise<string[]>} The Ollama models.
*/
static async fetchModels(baseURL) {
let models = [];
if (!baseURL) {
return models;
}
try {
const ollamaEndpoint = deriveBaseURL(baseURL);
/** @type {Promise<AxiosResponse<OllamaListResponse>>} */
const response = await axios.get(`${ollamaEndpoint}/api/tags`);
models = response.data.models.map((tag) => tag.name);
return models;
} catch (error) {
const logMessage =
'Failed to fetch models from Ollama API. If you are not using Ollama directly, and instead, through some aggregator or reverse proxy that handles fetching via OpenAI spec, ensure the name of the endpoint doesn\'t start with `ollama` (case-insensitive).';
logger.error(logMessage, error);
return [];
}
}
/**
* @param {ChatCompletionMessage[]} messages
* @returns {OllamaMessage[]}
*/
static formatOpenAIMessages(messages) {
const ollamaMessages = [];
for (const message of messages) {
if (typeof message.content === 'string') {
ollamaMessages.push({
role: message.role,
content: message.content,
});
continue;
}
let aggregatedText = '';
let imageUrls = [];
for (const content of message.content) {
if (content.type === 'text') {
aggregatedText += content.text + ' ';
} else if (content.type === 'image_url') {
imageUrls.push(getValidBase64(content.image_url.url));
}
}
const ollamaMessage = {
role: message.role,
content: aggregatedText.trim(),
};
if (imageUrls.length > 0) {
ollamaMessage.images = imageUrls;
}
ollamaMessages.push(ollamaMessage);
}
return ollamaMessages;
}
/***
* @param {Object} params
* @param {ChatCompletionPayload} params.payload
* @param {onTokenProgress} params.onProgress
* @param {AbortController} params.abortController
*/
async chatCompletion({ payload, onProgress, abortController = null }) {
let intermediateReply = '';
const parameters = ollamaPayloadSchema.parse(payload);
const messages = OllamaClient.formatOpenAIMessages(payload.messages);
if (parameters.stream) {
const stream = await this.client.chat({
messages,
...parameters,
});
for await (const chunk of stream) {
const token = chunk.message.content;
intermediateReply += token;
onProgress(token);
if (abortController.signal.aborted) {
stream.controller.abort();
break;
}
}
}
// TODO: regular completion
else {
// const generation = await this.client.generate(payload);
}
return intermediateReply;
}
catch(err) {
logger.error('[OllamaClient.chatCompletion]', err);
throw err;
}
}
module.exports = { OllamaClient, ollamaPayloadSchema };

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,15 @@ const { CallbackManager } = require('langchain/callbacks');
const { BufferMemory, ChatMessageHistory } = require('langchain/memory');
const { initializeCustomAgent, initializeFunctionsAgent } = require('./agents');
const { addImages, buildErrorInput, buildPromptPrefix } = require('./output_parsers');
const checkBalance = require('../../models/checkBalance');
const { processFileURL } = require('~/server/services/Files/process');
const { EModelEndpoint } = require('librechat-data-provider');
const { formatLangChainMessages } = require('./prompts');
const { isEnabled } = require('../../server/utils');
const checkBalance = require('~/models/checkBalance');
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 = {}) {
@@ -27,18 +31,10 @@ class PluginsClient extends OpenAIClient {
super.setOptions(options);
if (this.functionsAgent && this.agentOptions.model && !this.useOpenRouter) {
this.agentOptions.model = this.getFunctionModelName(this.agentOptions.model);
}
this.isGpt3 = this.modelOptions?.model?.includes('gpt-3');
if (this.options.reverseProxyUrl) {
this.langchainProxy = this.options.reverseProxyUrl.match(/.*v1/)?.[0];
!this.langchainProxy &&
console.warn(`The reverse proxy URL ${this.options.reverseProxyUrl} is not valid for Plugins.
The url must follow OpenAI specs, for example: https://localhost:8080/v1/chat/completions
If your reverse proxy is compatible to OpenAI specs in every other way, it may still work without plugins enabled.`);
this.langchainProxy = extractBaseURL(this.options.reverseProxyUrl);
}
}
@@ -46,8 +42,12 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
return {
chatGptLabel: this.options.chatGptLabel,
promptPrefix: this.options.promptPrefix,
tools: this.options.tools,
...this.modelOptions,
agentOptions: this.agentOptions,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
};
}
@@ -56,7 +56,9 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
}
getFunctionModelName(input) {
if (input.includes('gpt-3.5-turbo')) {
if (/-(?!0314)\d{4}/.test(input)) {
return input;
} else if (input.includes('gpt-3.5-turbo')) {
return 'gpt-3.5-turbo';
} else if (input.includes('gpt-4')) {
return 'gpt-4';
@@ -85,17 +87,15 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
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({
@@ -113,7 +113,8 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
signal: this.abortController.signal,
openAIApiKey: this.openAIApiKey,
conversationId: this.conversationId,
debug: this.options?.debug,
fileStrategy: this.options.req.app.locals.fileStrategy,
processFileURL,
message,
},
});
@@ -124,19 +125,16 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
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);
@@ -150,9 +148,11 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
signal,
pastMessages,
tools: this.tools,
currentDateString: this.currentDateString,
verbose: this.options.debug,
returnIntermediateSteps: true,
customName: this.options.chatGptLabel,
currentDateString: this.currentDateString,
customInstructions: this.options.promptPrefix,
callbackManager: CallbackManager.fromHandlers({
async handleAgentAction(action, runId) {
handleAction(action, runId, onAgentAction);
@@ -165,9 +165,7 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
}),
});
if (this.options.debug) {
console.debug('Loaded agent.');
}
logger.debug('[PluginsClient] Loaded agent.');
}
async executorCall(message, { signal, stream, onToolStart, onToolEnd }) {
@@ -183,12 +181,10 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
});
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 +207,10 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
]);
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 +222,11 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
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 +250,8 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
this.setOptions(opts);
return super.sendMessage(message, opts);
}
this.options.debug && console.log('Plugins sendMessage', message, opts);
logger.debug('[PluginsClient] sendMessage', { userMessageText: message, opts });
const {
user,
isEdited,
@@ -265,6 +265,14 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
onToolEnd,
} = await this.handleStartMethods(message, opts);
if (opts.progressCallback) {
opts.onProgress = opts.progressCallback.call(null, {
...(opts.progressOptions ?? {}),
parentMessageId: userMessage.messageId,
messageId: responseMessageId,
});
}
this.currentMessages.push(userMessage);
let {
@@ -281,10 +289,10 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
);
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);
}
@@ -305,11 +313,14 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
amount: promptTokens,
debug: this.options.debug,
model: this.modelOptions.model,
endpoint: EModelEndpoint.openAI,
},
});
}
const responseMessage = {
endpoint: EModelEndpoint.gptPlugins,
iconURL: this.options.iconURL,
messageId: responseMessageId,
conversationId,
parentMessageId: userMessage.messageId,
@@ -357,6 +368,7 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
const trimmedPartial = opts.getPartialText().replaceAll(':::plugin:::\n', '');
responseMessage.text =
trimmedPartial.length === 0 ? `${partialText}${this.result.output}` : partialText;
addImages(this.result.intermediateSteps, responseMessage);
await this.generateTextStream(this.result.output, opts.onProgress, { delay: 5 });
return await this.handleResponseMessage(responseMessage, saveOptions, user);
}
@@ -368,10 +380,7 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
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,
@@ -379,28 +388,20 @@ If your reverse proxy is compatible to OpenAI specs in every other way, it may s
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();

View File

@@ -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
}
}

View File

@@ -13,10 +13,18 @@ const initializeCustomAgent = async ({
tools,
model,
pastMessages,
customName,
customInstructions,
currentDateString,
...rest
}) => {
let prompt = CustomAgent.createPrompt(tools, { currentDateString, model: model.modelName });
if (customName) {
prompt = `You are "${customName}".\n${prompt}`;
}
if (customInstructions) {
prompt = `${prompt}\n${customInstructions}`;
}
const chatPrompt = ChatPromptTemplate.fromMessages([
new SystemMessagePromptTemplate(prompt),

View File

@@ -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);
// }
}

View File

@@ -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);
}
}

View File

@@ -10,6 +10,8 @@ const initializeFunctionsAgent = async ({
tools,
model,
pastMessages,
customName,
customInstructions,
currentDateString,
...rest
}) => {
@@ -24,7 +26,13 @@ const initializeFunctionsAgent = async ({
returnMessages: true,
});
const prefix = addToolDescriptions(`Current Date: ${currentDateString}\n${PREFIX}`, tools);
let prefix = addToolDescriptions(`Current Date: ${currentDateString}\n${PREFIX}`, tools);
if (customName) {
prefix = `You are "${customName}".\n${prefix}`;
}
if (customInstructions) {
prefix = `${prefix}\n${customInstructions}`;
}
return await initializeAgentExecutorWithOptions(tools, model, {
agentType: 'openai-functions',

View File

@@ -1,7 +1,9 @@
const { promptTokensEstimate } = require('openai-chat-tokens');
const checkBalance = require('../../../models/checkBalance');
const { isEnabled } = require('../../../server/utils');
const { formatFromLangChain } = require('../prompts');
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,
@@ -15,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 };
@@ -34,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)
@@ -55,11 +65,12 @@ const createStartHandler = ({
debug: manager.debug,
generations,
model,
endpoint: EModelEndpoint.openAI,
},
});
}
} 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 });

View File

@@ -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) });

View File

@@ -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') {

View File

@@ -0,0 +1,85 @@
const { CohereConstants } = require('librechat-data-provider');
const { titleInstruction } = require('../prompts/titlePrompts');
// Mapping OpenAI roles to Cohere roles
const roleMap = {
user: CohereConstants.ROLE_USER,
assistant: CohereConstants.ROLE_CHATBOT,
system: CohereConstants.ROLE_SYSTEM, // Recognize and map the system role explicitly
};
/**
* Adjusts an OpenAI ChatCompletionPayload to conform with Cohere's expected chat payload format.
* Now includes handling for "system" roles explicitly mentioned.
*
* @param {Object} options - Object containing the model options.
* @param {ChatCompletionPayload} options.modelOptions - The OpenAI model payload options.
* @returns {CohereChatStreamRequest} Cohere-compatible chat API payload.
*/
function createCoherePayload({ modelOptions }) {
/** @type {string | undefined} */
let preamble;
let latestUserMessageContent = '';
const {
stream,
stop,
top_p,
temperature,
frequency_penalty,
presence_penalty,
max_tokens,
messages,
model,
...rest
} = modelOptions;
// Filter out the latest user message and transform remaining messages to Cohere's chat_history format
let chatHistory = messages.reduce((acc, message, index, arr) => {
const isLastUserMessage = index === arr.length - 1 && message.role === 'user';
const messageContent =
typeof message.content === 'string'
? message.content
: message.content.map((part) => (part.type === 'text' ? part.text : '')).join(' ');
if (isLastUserMessage) {
latestUserMessageContent = messageContent;
} else {
acc.push({
role: roleMap[message.role] || CohereConstants.ROLE_USER,
message: messageContent,
});
}
return acc;
}, []);
if (
chatHistory.length === 1 &&
chatHistory[0].role === CohereConstants.ROLE_SYSTEM &&
!latestUserMessageContent.length
) {
const message = chatHistory[0].message;
latestUserMessageContent = message.includes(titleInstruction)
? CohereConstants.TITLE_MESSAGE
: '.';
preamble = message;
}
return {
message: latestUserMessageContent,
model: model,
chatHistory,
stream: stream ?? false,
temperature: temperature,
frequencyPenalty: frequency_penalty,
presencePenalty: presence_penalty,
maxTokens: max_tokens,
stopSequences: stop,
preamble,
p: top_p,
...rest,
};
}
module.exports = createCoherePayload;

View File

@@ -1,5 +1,28 @@
const { ChatOpenAI } = require('langchain/chat_models/openai');
const { sanitizeModelName, constructAzureURL } = require('~/utils');
const { isEnabled } = require('~/server/utils');
/**
* Creates a new instance of a language model (LLM) for chat interactions.
*
* @param {Object} options - The options for creating the LLM.
* @param {ModelOptions} options.modelOptions - The options specific to the model, including modelName, temperature, presence_penalty, frequency_penalty, and other model-related settings.
* @param {ConfigOptions} options.configOptions - Configuration options for the API requests, including proxy settings and custom headers.
* @param {Callbacks} options.callbacks - Callback functions for managing the lifecycle of the LLM, including token buffers, context, and initial message count.
* @param {boolean} [options.streaming=false] - Determines if the LLM should operate in streaming mode.
* @param {string} options.openAIApiKey - The API key for OpenAI, used for authentication.
* @param {AzureOptions} [options.azure={}] - Optional Azure-specific configurations. If provided, Azure configurations take precedence over OpenAI configurations.
*
* @returns {ChatOpenAI} An instance of the ChatOpenAI class, configured with the provided options.
*
* @example
* const llm = createLLM({
* modelOptions: { modelName: 'gpt-3.5-turbo', temperature: 0.2 },
* configOptions: { basePath: 'https://example.api/path' },
* callbacks: { onMessage: handleMessage },
* openAIApiKey: 'your-api-key'
* });
*/
function createLLM({
modelOptions,
configOptions,
@@ -13,22 +36,42 @@ function createLLM({
apiKey: openAIApiKey,
};
/** @type {AzureOptions} */
let azureOptions = {};
if (azure) {
const useModelName = isEnabled(process.env.AZURE_USE_MODEL_AS_DEPLOYMENT_NAME);
credentials = {};
configuration = {};
azureOptions = azure;
azureOptions.azureOpenAIApiDeploymentName = useModelName
? sanitizeModelName(modelOptions.modelName)
: azureOptions.azureOpenAIApiDeploymentName;
}
// console.debug('createLLM: configOptions');
// console.debug(configOptions);
if (azure && process.env.AZURE_OPENAI_DEFAULT_MODEL) {
modelOptions.modelName = process.env.AZURE_OPENAI_DEFAULT_MODEL;
}
if (azure && configOptions.basePath) {
const azureURL = constructAzureURL({
baseURL: configOptions.basePath,
azureOptions,
});
azureOptions.azureOpenAIBasePath = azureURL.split(
`/${azureOptions.azureOpenAIApiDeploymentName}`,
)[0];
}
return new ChatOpenAI(
{
streaming,
verbose: true,
credentials,
configuration,
...azure,
...azureOptions,
...modelOptions,
...credentials,
callbacks,
},
configOptions,

View File

@@ -1,7 +1,9 @@
const createLLM = require('./createLLM');
const RunManager = require('./RunManager');
const createCoherePayload = require('./createCoherePayload');
module.exports = {
createLLM,
RunManager,
createCoherePayload,
};

View File

@@ -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 };

View File

@@ -1,26 +1,71 @@
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.
*
* @function
* @module addImages
*
* @param {Array.<Object>} intermediateSteps - An array of objects, each containing an observation.
* @param {Object} responseMessage - An object containing the text property which might have image URLs.
*
* @property {string} intermediateSteps[].observation - The observation string which might contain an image markdown.
* @property {string} responseMessage.text - The text which might contain image URLs.
*
* @example
*
* const intermediateSteps = [
* { observation: '![desc](/images/test.png)' }
* ];
* const responseMessage = { text: 'Some text with ![desc](sandbox:/images/test.png)' };
*
* addImages(intermediateSteps, responseMessage);
*
* logger.debug(responseMessage.text);
* // Outputs: 'Some text with ![desc](/images/test.png)\n![desc](/images/test.png)'
*
* @returns {void}
*/
function addImages(intermediateSteps, responseMessage) {
if (!intermediateSteps || !responseMessage) {
return;
}
// Correct any erroneous URLs in the responseMessage.text first
intermediateSteps.forEach((step) => {
const { observation } = step;
if (!observation || !observation.includes('![')) {
return;
}
// Extract the image file path from the observation
const observedImagePath = observation.match(/\(\/images\/.*\.\w*\)/g)[0];
const match = observation.match(/\/images\/.*\.\w*/);
if (!match) {
return;
}
const essentialImagePath = match[0];
// Check if the responseMessage already includes the image file path
if (!responseMessage.text.includes(observedImagePath)) {
// If the image file path is not found, append the whole observation
responseMessage.text += '\n' + observation;
if (this.options.debug) {
console.debug('added image from intermediateSteps');
const regex = /!\[.*?\]\((.*?)\)/g;
let matchErroneous;
while ((matchErroneous = regex.exec(responseMessage.text)) !== null) {
if (matchErroneous[1] && !matchErroneous[1].startsWith('/images/')) {
responseMessage.text = responseMessage.text.replace(matchErroneous[1], essentialImagePath);
}
}
});
// Now, check if the responseMessage already includes the correct image file path and append if not
intermediateSteps.forEach((step) => {
const { observation } = step;
if (!observation || !observation.includes('![')) {
return;
}
const observedImagePath = observation.match(/!\[.*\]\([^)]*\)/g);
if (observedImagePath && !responseMessage.text.includes(observedImagePath[0])) {
responseMessage.text += '\n' + observation;
logger.debug('[addImages] added image from intermediateSteps:', observation);
}
});
}
module.exports = addImages;

View File

@@ -0,0 +1,84 @@
let addImages = require('./addImages');
describe('addImages', () => {
let intermediateSteps;
let responseMessage;
let options;
beforeEach(() => {
intermediateSteps = [];
responseMessage = { text: '' };
options = { debug: false };
this.options = options;
addImages = addImages.bind(this);
});
it('should handle null or undefined parameters', () => {
addImages(null, responseMessage);
expect(responseMessage.text).toBe('');
addImages(intermediateSteps, null);
expect(responseMessage.text).toBe('');
addImages(null, null);
expect(responseMessage.text).toBe('');
});
it('should append correct image markdown if not present in responseMessage', () => {
intermediateSteps.push({ observation: '![desc](/images/test.png)' });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe('\n![desc](/images/test.png)');
});
it('should not append image markdown if already present in responseMessage', () => {
responseMessage.text = '![desc](/images/test.png)';
intermediateSteps.push({ observation: '![desc](/images/test.png)' });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe('![desc](/images/test.png)');
});
it('should correct and append image markdown with erroneous URL', () => {
responseMessage.text = '![desc](sandbox:/images/test.png)';
intermediateSteps.push({ observation: '![desc](/images/test.png)' });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe('![desc](/images/test.png)');
});
it('should correct multiple erroneous URLs in responseMessage', () => {
responseMessage.text =
'![desc1](sandbox:/images/test1.png) ![desc2](version:/images/test2.png)';
intermediateSteps.push({ observation: '![desc1](/images/test1.png)' });
intermediateSteps.push({ observation: '![desc2](/images/test2.png)' });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe('![desc1](/images/test1.png) ![desc2](/images/test2.png)');
});
it('should not append non-image markdown observations', () => {
intermediateSteps.push({ observation: '[desc](/images/test.png)' });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe('');
});
it('should handle multiple observations', () => {
intermediateSteps.push({ observation: '![desc1](/images/test1.png)' });
intermediateSteps.push({ observation: '![desc2](/images/test2.png)' });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe('\n![desc1](/images/test1.png)\n![desc2](/images/test2.png)');
});
it('should not append if observation does not contain image markdown', () => {
intermediateSteps.push({ observation: 'This is a test observation without image markdown.' });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe('');
});
it('should append correctly from a real scenario', () => {
responseMessage.text =
'Here is the generated image based on your request. It depicts a surreal landscape filled with floating musical notes. The style is impressionistic, with vibrant sunset hues dominating the scene. At the center, there\'s a silhouette of a grand piano, adding a dreamy emotion to the overall image. This could serve as a unique and creative music album cover. Would you like to make any changes or generate another image?';
const originalText = responseMessage.text;
const imageMarkdown = '![generated image](/images/img-RnVWaYo2Yg4x3e0isICiMuf5.png)';
intermediateSteps.push({ observation: imageMarkdown });
addImages(intermediateSteps, responseMessage);
expect(responseMessage.text).toBe(`${originalText}\n${imageMarkdown}`);
});
});

View File

@@ -0,0 +1,159 @@
const axios = require('axios');
const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config');
const footer = `Use the context as your learned knowledge to better answer the user.
In your response, remember to follow these guidelines:
- If you don't know the answer, simply say that you don't know.
- If you are unsure how to answer, ask for clarification.
- Avoid mentioning that you obtained the information from the context.
Answer appropriately in the user's language.
`;
function createContextHandlers(req, userMessageContent) {
if (!process.env.RAG_API_URL) {
return;
}
const queryPromises = [];
const processedFiles = [];
const processedIds = new Set();
const jwtToken = req.headers.authorization.split(' ')[1];
const useFullContext = isEnabled(process.env.RAG_USE_FULL_CONTEXT);
const query = async (file) => {
if (useFullContext) {
return axios.get(`${process.env.RAG_API_URL}/documents/${file.file_id}/context`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
},
});
}
return axios.post(
`${process.env.RAG_API_URL}/query`,
{
file_id: file.file_id,
query: userMessageContent,
k: 4,
},
{
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
},
);
};
const processFile = async (file) => {
if (file.embedded && !processedIds.has(file.file_id)) {
try {
const promise = query(file);
queryPromises.push(promise);
processedFiles.push(file);
processedIds.add(file.file_id);
} catch (error) {
logger.error(`Error processing file ${file.filename}:`, error);
}
}
};
const createContext = async () => {
try {
if (!queryPromises.length || !processedFiles.length) {
return '';
}
const oneFile = processedFiles.length === 1;
const header = `The user has attached ${oneFile ? 'a' : processedFiles.length} file${
!oneFile ? 's' : ''
} to the conversation:`;
const files = `${
oneFile
? ''
: `
<files>`
}${processedFiles
.map(
(file) => `
<file>
<filename>${file.filename}</filename>
<type>${file.type}</type>
</file>`,
)
.join('')}${
oneFile
? ''
: `
</files>`
}`;
const resolvedQueries = await Promise.all(queryPromises);
const context = resolvedQueries
.map((queryResult, index) => {
const file = processedFiles[index];
let contextItems = queryResult.data;
const generateContext = (currentContext) =>
`
<file>
<filename>${file.filename}</filename>
<context>${currentContext}
</context>
</file>`;
if (useFullContext) {
return generateContext(`\n${contextItems}`);
}
contextItems = queryResult.data
.map((item) => {
const pageContent = item[0].page_content;
return `
<contextItem>
<![CDATA[${pageContent?.trim()}]]>
</contextItem>`;
})
.join('');
return generateContext(contextItems);
})
.join('');
if (useFullContext) {
const prompt = `${header}
${context}
${footer}`;
return prompt;
}
const prompt = `${header}
${files}
A semantic search was executed with the user's message as the query, retrieving the following context inside <context></context> XML tags.
<context>${context}
</context>
${footer}`;
return prompt;
} catch (error) {
logger.error('Error creating context:', error);
throw error;
}
};
return {
processFile,
createContext,
};
}
module.exports = createContextHandlers;

View File

@@ -0,0 +1,34 @@
/**
* Generates a prompt instructing the user to describe an image in detail, tailored to different types of visual content.
* @param {boolean} pluralized - Whether to pluralize the prompt for multiple images.
* @returns {string} - The generated vision prompt.
*/
const createVisionPrompt = (pluralized = false) => {
return `Please describe the image${
pluralized ? 's' : ''
} in detail, covering relevant aspects such as:
For photographs, illustrations, or artwork:
- The main subject(s) and their appearance, positioning, and actions
- The setting, background, and any notable objects or elements
- Colors, lighting, and overall mood or atmosphere
- Any interesting details, textures, or patterns
- The style, technique, or medium used (if discernible)
For screenshots or images containing text:
- The content and purpose of the text
- The layout, formatting, and organization of the information
- Any notable visual elements, such as logos, icons, or graphics
- The overall context or message conveyed by the screenshot
For graphs, charts, or data visualizations:
- The type of graph or chart (e.g., bar graph, line chart, pie chart)
- The variables being compared or analyzed
- Any trends, patterns, or outliers in the data
- The axis labels, scales, and units of measurement
- The title, legend, and any additional context provided
Be as specific and descriptive as possible while maintaining clarity and concision.`;
};
module.exports = createVisionPrompt;

View File

@@ -0,0 +1,42 @@
/**
* Formats an object to match the struct_val, list_val, string_val, float_val, and int_val format.
*
* @param {Object} obj - The object to be formatted.
* @returns {Object} The formatted object.
*
* Handles different types:
* - Arrays are wrapped in list_val and each element is processed.
* - Objects are recursively processed.
* - Strings are wrapped in string_val.
* - Numbers are wrapped in float_val or int_val depending on whether they are floating-point or integers.
*/
function formatGoogleInputs(obj) {
const formattedObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key];
// Handle arrays
if (Array.isArray(value)) {
formattedObj[key] = { list_val: value.map((item) => formatGoogleInputs(item)) };
}
// Handle objects
else if (typeof value === 'object' && value !== null) {
formattedObj[key] = formatGoogleInputs(value);
}
// Handle numbers
else if (typeof value === 'number') {
formattedObj[key] = Number.isInteger(value) ? { int_val: value } : { float_val: value };
}
// Handle other types (e.g., strings)
else {
formattedObj[key] = { string_val: [value] };
}
}
}
return { struct_val: formattedObj };
}
module.exports = formatGoogleInputs;

View File

@@ -0,0 +1,274 @@
const formatGoogleInputs = require('./formatGoogleInputs');
describe('formatGoogleInputs', () => {
it('formats message correctly', () => {
const input = {
messages: [
{
content: 'hi',
author: 'user',
},
],
context: 'context',
examples: [
{
input: {
author: 'user',
content: 'user input',
},
output: {
author: 'bot',
content: 'bot output',
},
},
],
parameters: {
temperature: 0.2,
topP: 0.8,
topK: 40,
maxOutputTokens: 1024,
},
};
const expectedOutput = {
struct_val: {
messages: {
list_val: [
{
struct_val: {
content: {
string_val: ['hi'],
},
author: {
string_val: ['user'],
},
},
},
],
},
context: {
string_val: ['context'],
},
examples: {
list_val: [
{
struct_val: {
input: {
struct_val: {
author: {
string_val: ['user'],
},
content: {
string_val: ['user input'],
},
},
},
output: {
struct_val: {
author: {
string_val: ['bot'],
},
content: {
string_val: ['bot output'],
},
},
},
},
},
],
},
parameters: {
struct_val: {
temperature: {
float_val: 0.2,
},
topP: {
float_val: 0.8,
},
topK: {
int_val: 40,
},
maxOutputTokens: {
int_val: 1024,
},
},
},
},
};
const result = formatGoogleInputs(input);
expect(JSON.stringify(result)).toEqual(JSON.stringify(expectedOutput));
});
it('formats real payload parts', () => {
const input = {
instances: [
{
context: 'context',
examples: [
{
input: {
author: 'user',
content: 'user input',
},
output: {
author: 'bot',
content: 'user output',
},
},
],
messages: [
{
author: 'user',
content: 'hi',
},
],
},
],
parameters: {
candidateCount: 1,
maxOutputTokens: 1024,
temperature: 0.2,
topP: 0.8,
topK: 40,
},
};
const expectedOutput = {
struct_val: {
instances: {
list_val: [
{
struct_val: {
context: { string_val: ['context'] },
examples: {
list_val: [
{
struct_val: {
input: {
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['user input'] },
},
},
output: {
struct_val: {
author: { string_val: ['bot'] },
content: { string_val: ['user output'] },
},
},
},
},
],
},
messages: {
list_val: [
{
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['hi'] },
},
},
],
},
},
},
],
},
parameters: {
struct_val: {
candidateCount: { int_val: 1 },
maxOutputTokens: { int_val: 1024 },
temperature: { float_val: 0.2 },
topP: { float_val: 0.8 },
topK: { int_val: 40 },
},
},
},
};
const result = formatGoogleInputs(input);
expect(JSON.stringify(result)).toEqual(JSON.stringify(expectedOutput));
});
it('helps create valid payload parts', () => {
const instances = {
context: 'context',
examples: [
{
input: {
author: 'user',
content: 'user input',
},
output: {
author: 'bot',
content: 'user output',
},
},
],
messages: [
{
author: 'user',
content: 'hi',
},
],
};
const expectedInstances = {
struct_val: {
context: { string_val: ['context'] },
examples: {
list_val: [
{
struct_val: {
input: {
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['user input'] },
},
},
output: {
struct_val: {
author: { string_val: ['bot'] },
content: { string_val: ['user output'] },
},
},
},
},
],
},
messages: {
list_val: [
{
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['hi'] },
},
},
],
},
},
};
const parameters = {
candidateCount: 1,
maxOutputTokens: 1024,
temperature: 0.2,
topP: 0.8,
topK: 40,
};
const expectedParameters = {
struct_val: {
candidateCount: { int_val: 1 },
maxOutputTokens: { int_val: 1024 },
temperature: { float_val: 0.2 },
topP: { float_val: 0.8 },
topK: { int_val: 40 },
},
};
const instancesResult = formatGoogleInputs(instances);
const parametersResult = formatGoogleInputs(parameters);
expect(JSON.stringify(instancesResult)).toEqual(JSON.stringify(expectedInstances));
expect(JSON.stringify(parametersResult)).toEqual(JSON.stringify(expectedParameters));
});
});

View File

@@ -1,5 +1,28 @@
const { EModelEndpoint } = require('librechat-data-provider');
const { HumanMessage, AIMessage, SystemMessage } = require('langchain/schema');
/**
* Formats a message to OpenAI Vision API payload format.
*
* @param {Object} params - The parameters for formatting.
* @param {Object} params.message - The message object to format.
* @param {string} [params.message.role] - The role of the message sender (must be 'user').
* @param {string} [params.message.content] - The text content of the message.
* @param {EModelEndpoint} [params.endpoint] - Identifier for specific endpoint handling
* @param {Array<string>} [params.image_urls] - The image_urls to attach to the message.
* @returns {(Object)} - The formatted message.
*/
const formatVisionMessage = ({ message, image_urls, endpoint }) => {
if (endpoint === EModelEndpoint.anthropic) {
message.content = [...image_urls, { type: 'text', text: message.content }];
return message;
}
message.content = [{ type: 'text', text: message.content }, ...image_urls];
return message;
};
/**
* Formats a message to OpenAI payload format based on the provided options.
*
@@ -10,12 +33,14 @@ const { HumanMessage, AIMessage, SystemMessage } = require('langchain/schema');
* @param {string} [params.message.sender] - The sender of the message.
* @param {string} [params.message.text] - The text content of the message.
* @param {string} [params.message.content] - The content of the message.
* @param {Array<string>} [params.message.image_urls] - The image_urls attached to the message for Vision API.
* @param {string} [params.userName] - The name of the user.
* @param {string} [params.assistantName] - The name of the assistant.
* @param {string} [params.endpoint] - Identifier for specific endpoint handling
* @param {boolean} [params.langChain=false] - Whether to return a LangChain message object.
* @returns {(Object|HumanMessage|AIMessage|SystemMessage)} - The formatted message.
*/
const formatMessage = ({ message, userName, assistantName, langChain = false }) => {
const formatMessage = ({ message, userName, assistantName, endpoint, langChain = false }) => {
let { role: _role, _name, sender, text, content: _content, lc_id } = message;
if (lc_id && lc_id[2] && !langChain) {
const roleMapping = {
@@ -32,6 +57,15 @@ const formatMessage = ({ message, userName, assistantName, langChain = false })
content,
};
const { image_urls } = message;
if (Array.isArray(image_urls) && image_urls.length > 0 && role === 'user') {
return formatVisionMessage({
message: formattedMessage,
image_urls: message.image_urls,
endpoint,
});
}
if (_name) {
formattedMessage.name = _name;
}

View File

@@ -1,5 +1,6 @@
const { formatMessage, formatLangChainMessages, formatFromLangChain } = require('./formatMessages');
const { Constants } = require('librechat-data-provider');
const { HumanMessage, AIMessage, SystemMessage } = require('langchain/schema');
const { formatMessage, formatLangChainMessages, formatFromLangChain } = require('./formatMessages');
describe('formatMessage', () => {
it('formats user message', () => {
@@ -54,7 +55,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,
@@ -62,7 +62,7 @@ describe('formatMessage', () => {
isCreatedByUser: true,
isEdited: false,
model: null,
parentMessageId: '00000000-0000-0000-0000-000000000000',
parentMessageId: Constants.NO_PARENT,
sender: 'User',
text: 'hi',
tokenCount: 5,

View File

@@ -4,6 +4,8 @@ const handleInputs = require('./handleInputs');
const instructions = require('./instructions');
const titlePrompts = require('./titlePrompts');
const truncateText = require('./truncateText');
const createVisionPrompt = require('./createVisionPrompt');
const createContextHandlers = require('./createContextHandlers');
module.exports = {
...formatMessages,
@@ -11,5 +13,7 @@ module.exports = {
...handleInputs,
...instructions,
...titlePrompts,
truncateText,
...truncateText,
createVisionPrompt,
createContextHandlers,
};

View File

@@ -27,7 +27,96 @@ ${convo}`,
return titlePrompt;
};
const titleInstruction =
'a concise, 5-word-or-less title for the conversation, using its same language, with no punctuation. Apply title case conventions appropriate for the language. Never directly mention the language name or the word "title"';
const titleFunctionPrompt = `In this environment you have access to a set of tools you can use to generate the conversation title.
You may call them like this:
<function_calls>
<invoke>
<tool_name>$TOOL_NAME</tool_name>
<parameters>
<$PARAMETER_NAME>$PARAMETER_VALUE</$PARAMETER_NAME>
...
</parameters>
</invoke>
</function_calls>
Here are the tools available:
<tools>
<tool_description>
<tool_name>submit_title</tool_name>
<description>
Submit a brief title in the conversation's language, following the parameter description closely.
</description>
<parameters>
<parameter>
<name>title</name>
<type>string</type>
<description>${titleInstruction}</description>
</parameter>
</parameters>
</tool_description>
</tools>`;
const genTranslationPrompt = (
translationPrompt,
) => `In this environment you have access to a set of tools you can use to translate text.
You may call them like this:
<function_calls>
<invoke>
<tool_name>$TOOL_NAME</tool_name>
<parameters>
<$PARAMETER_NAME>$PARAMETER_VALUE</$PARAMETER_NAME>
...
</parameters>
</invoke>
</function_calls>
Here are the tools available:
<tools>
<tool_description>
<tool_name>submit_translation</tool_name>
<description>
Submit a translation in the target language, following the parameter description and its language closely.
</description>
<parameters>
<parameter>
<name>translation</name>
<type>string</type>
<description>${translationPrompt}
ONLY include the generated translation without quotations, nor its related key</description>
</parameter>
</parameters>
</tool_description>
</tools>`;
/**
* Parses specified parameter from the provided prompt.
* @param {string} prompt - The prompt containing the desired parameter.
* @param {string} paramName - The name of the parameter to extract.
* @returns {string} The parsed parameter's value or a default value if not found.
*/
function parseParamFromPrompt(prompt, paramName) {
const paramRegex = new RegExp(`<${paramName}>([\\s\\S]+?)</${paramName}>`);
const paramMatch = prompt.match(paramRegex);
if (paramMatch && paramMatch[1]) {
return paramMatch[1].trim();
}
if (prompt && prompt.length) {
return `NO TOOL INVOCATION: ${prompt}`;
}
return `No ${paramName} provided`;
}
module.exports = {
langPrompt,
titleInstruction,
createTitlePrompt,
titleFunctionPrompt,
parseParamFromPrompt,
genTranslationPrompt,
};

View File

@@ -1,10 +1,40 @@
const MAX_CHAR = 255;
function truncateText(text) {
if (text.length > MAX_CHAR) {
return `${text.slice(0, MAX_CHAR)}... [text truncated for brevity]`;
/**
* Truncates a given text to a specified maximum length, appending ellipsis and a notification
* if the original text exceeds the maximum length.
*
* @param {string} text - The text to be truncated.
* @param {number} [maxLength=MAX_CHAR] - The maximum length of the text after truncation. Defaults to MAX_CHAR.
* @returns {string} The truncated text if the original text length exceeds maxLength, otherwise returns the original text.
*/
function truncateText(text, maxLength = MAX_CHAR) {
if (text.length > maxLength) {
return `${text.slice(0, maxLength)}... [text truncated for brevity]`;
}
return text;
}
module.exports = truncateText;
/**
* Truncates a given text to a specified maximum length by showing the first half and the last half of the text,
* separated by ellipsis. This method ensures the output does not exceed the maximum length, including the addition
* of ellipsis and notification if the original text exceeds the maximum length.
*
* @param {string} text - The text to be truncated.
* @param {number} [maxLength=MAX_CHAR] - The maximum length of the output text after truncation. Defaults to MAX_CHAR.
* @returns {string} The truncated text showing the first half and the last half, or the original text if it does not exceed maxLength.
*/
function smartTruncateText(text, maxLength = MAX_CHAR) {
const ellipsis = '...';
const notification = ' [text truncated for brevity]';
const halfMaxLength = Math.floor((maxLength - ellipsis.length - notification.length) / 2);
if (text.length > maxLength) {
const startLastHalf = text.length - halfMaxLength;
return `${text.slice(0, halfMaxLength)}${ellipsis}${text.slice(startLastHalf)}${notification}`;
}
return text;
}
module.exports = { truncateText, smartTruncateText };

View File

@@ -1,19 +1,34 @@
const { Constants } = require('librechat-data-provider');
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 {
@@ -293,7 +308,7 @@ describe('BaseClient', () => {
const unorderedMessages = [
{ id: '3', parentMessageId: '2', text: 'Message 3' },
{ id: '2', parentMessageId: '1', text: 'Message 2' },
{ id: '1', parentMessageId: '00000000-0000-0000-0000-000000000000', text: 'Message 1' },
{ id: '1', parentMessageId: Constants.NO_PARENT, text: 'Message 1' },
];
it('should return ordered messages based on parentMessageId', () => {
@@ -302,7 +317,7 @@ describe('BaseClient', () => {
parentMessageId: '3',
});
expect(result).toEqual([
{ id: '1', parentMessageId: '00000000-0000-0000-0000-000000000000', text: 'Message 1' },
{ id: '1', parentMessageId: Constants.NO_PARENT, text: 'Message 1' },
{ id: '2', parentMessageId: '1', text: 'Message 2' },
{ id: '3', parentMessageId: '2', text: 'Message 3' },
]);
@@ -529,9 +544,9 @@ describe('BaseClient', () => {
);
});
test('setOptions is called with the correct arguments', async () => {
test('setOptions is called with the correct arguments only when replaceOptions is set to true', async () => {
TestClient.setOptions = jest.fn();
const opts = { conversationId: '123', parentMessageId: '456' };
const opts = { conversationId: '123', parentMessageId: '456', replaceOptions: true };
await TestClient.sendMessage('Hello, world!', opts);
expect(TestClient.setOptions).toHaveBeenCalledWith(opts);
TestClient.setOptions.mockClear();

View File

@@ -40,9 +40,9 @@ class FakeClient extends BaseClient {
};
}
this.maxContextTokens = getModelMaxTokens(this.modelOptions.model) ?? 4097;
this.maxContextTokens =
this.options.maxContextTokens ?? getModelMaxTokens(this.modelOptions.model) ?? 4097;
}
getCompletion() {}
buildMessages() {}
getTokenCount(str) {
return str.length;
@@ -86,6 +86,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,

View File

@@ -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,15 +142,39 @@ describe('OpenAIClient', () => {
{ role: 'assistant', sender: 'Assistant', text: 'Hi', messageId: '2' },
];
const defaultOptions = {
// debug: true,
req: {},
openaiApiKey: 'new-api-key',
modelOptions: {
model,
temperature: 0.7,
},
};
const defaultAzureOptions = {
azureOpenAIApiInstanceName: 'your-instance-name',
azureOpenAIApiDeploymentName: 'your-deployment-name',
azureOpenAIApiVersion: '2020-07-01-preview',
};
let originalWarn;
beforeAll(() => {
originalWarn = console.warn;
console.warn = jest.fn();
});
afterAll(() => {
console.warn = originalWarn;
});
beforeEach(() => {
const options = {
// debug: true,
openaiApiKey: 'new-api-key',
modelOptions: {
model,
temperature: 0.7,
},
};
console.warn.mockClear();
});
beforeEach(() => {
const options = { ...defaultOptions };
client = new OpenAIClient('test-api-key', options);
client2 = new OpenAIClient('test-api-key', options);
client.summarizeMessages = jest.fn().mockResolvedValue({
@@ -32,6 +186,7 @@ describe('OpenAIClient', () => {
.fn()
.mockResolvedValue({ prompt: messages.map((m) => m.text).join('\n') });
client.constructor.freeAndResetAllEncoders();
client.getMessages = jest.fn().mockResolvedValue([]);
});
describe('setOptions', () => {
@@ -86,7 +241,97 @@ describe('OpenAIClient', () => {
client.setOptions({ reverseProxyUrl: 'https://example.com/completions' });
expect(client.completionsUrl).toBe('https://example.com/completions');
expect(client.langchainProxy).toBeUndefined();
expect(client.langchainProxy).toBe('https://example.com/completions');
});
});
describe('setOptions with Simplified Azure Integration', () => {
afterEach(() => {
delete process.env.AZURE_OPENAI_DEFAULT_MODEL;
delete process.env.AZURE_USE_MODEL_AS_DEPLOYMENT_NAME;
});
const azureOpenAIApiInstanceName = 'test-instance';
const azureOpenAIApiDeploymentName = 'test-deployment';
const azureOpenAIApiVersion = '2020-07-01-preview';
const createOptions = (model) => ({
modelOptions: { model },
azure: {
azureOpenAIApiInstanceName,
azureOpenAIApiDeploymentName,
azureOpenAIApiVersion,
},
});
it('should set model from AZURE_OPENAI_DEFAULT_MODEL when Azure is enabled', () => {
process.env.AZURE_OPENAI_DEFAULT_MODEL = 'gpt-4-azure';
const options = createOptions('test');
client.azure = options.azure;
client.setOptions(options);
expect(client.modelOptions.model).toBe('gpt-4-azure');
});
it('should not change model if Azure is not enabled', () => {
process.env.AZURE_OPENAI_DEFAULT_MODEL = 'gpt-4-azure';
const originalModel = 'test';
client.azure = false;
client.setOptions(createOptions('test'));
expect(client.modelOptions.model).toBe(originalModel);
});
it('should not change model if AZURE_OPENAI_DEFAULT_MODEL is not set and model is passed', () => {
const originalModel = 'GROK-LLM';
const options = createOptions(originalModel);
client.azure = options.azure;
client.setOptions(options);
expect(client.modelOptions.model).toBe(originalModel);
});
it('should change model if AZURE_OPENAI_DEFAULT_MODEL is set and model is passed', () => {
process.env.AZURE_OPENAI_DEFAULT_MODEL = 'gpt-4-azure';
const originalModel = 'GROK-LLM';
const options = createOptions(originalModel);
client.azure = options.azure;
client.setOptions(options);
expect(client.modelOptions.model).toBe(process.env.AZURE_OPENAI_DEFAULT_MODEL);
});
it('should include model in deployment name if AZURE_USE_MODEL_AS_DEPLOYMENT_NAME is set', () => {
process.env.AZURE_USE_MODEL_AS_DEPLOYMENT_NAME = 'true';
const model = 'gpt-4-azure';
const AzureClient = new OpenAIClient('test-api-key', createOptions(model));
const expectedValue = `https://${azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${model}/chat/completions?api-version=${azureOpenAIApiVersion}`;
expect(AzureClient.modelOptions.model).toBe(model);
expect(AzureClient.azureEndpoint).toBe(expectedValue);
});
it('should include model in deployment name if AZURE_USE_MODEL_AS_DEPLOYMENT_NAME and default model is set', () => {
const defaultModel = 'gpt-4-azure';
process.env.AZURE_USE_MODEL_AS_DEPLOYMENT_NAME = 'true';
process.env.AZURE_OPENAI_DEFAULT_MODEL = defaultModel;
const model = 'gpt-4-this-is-a-test-model-name';
const AzureClient = new OpenAIClient('test-api-key', createOptions(model));
const expectedValue = `https://${azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${model}/chat/completions?api-version=${azureOpenAIApiVersion}`;
expect(AzureClient.modelOptions.model).toBe(defaultModel);
expect(AzureClient.azureEndpoint).toBe(expectedValue);
});
it('should not include model in deployment name if AZURE_USE_MODEL_AS_DEPLOYMENT_NAME is not set', () => {
const model = 'gpt-4-azure';
const AzureClient = new OpenAIClient('test-api-key', createOptions(model));
const expectedValue = `https://${azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${azureOpenAIApiDeploymentName}/chat/completions?api-version=${azureOpenAIApiVersion}`;
expect(AzureClient.modelOptions.model).toBe(model);
expect(AzureClient.azureEndpoint).toBe(expectedValue);
});
});
@@ -309,5 +554,151 @@ describe('OpenAIClient', () => {
expect(totalTokens).toBe(testCase.expected);
});
});
const vision_request = [
{
role: 'user',
content: [
{
type: 'text',
text: 'describe what is in this image?',
},
{
type: 'image_url',
image_url: {
url: 'https://venturebeat.com/wp-content/uploads/2019/03/openai-1.png',
detail: 'high',
},
},
],
},
];
const expectedTokens = 14;
const visionModel = 'gpt-4-vision-preview';
it(`should return ${expectedTokens} tokens for model ${visionModel} (Vision Request)`, () => {
client.modelOptions.model = visionModel;
client.selectTokenizer();
// 3 tokens for assistant label
let totalTokens = 3;
for (let message of vision_request) {
totalTokens += client.getTokenCountForMessage(message);
}
expect(totalTokens).toBe(expectedTokens);
});
});
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);
});
});
describe('checkVisionRequest functionality', () => {
let client;
const attachments = [{ type: 'image/png' }];
beforeEach(() => {
client = new OpenAIClient('test-api-key', {
endpoint: 'ollama',
modelOptions: {
model: 'initial-model',
},
modelsConfig: {
ollama: ['initial-model', 'llava', 'other-model'],
},
});
client.defaultVisionModel = 'non-valid-default-model';
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should set "llava" as the model if it is the first valid model when default validation fails', () => {
client.checkVisionRequest(attachments);
expect(client.modelOptions.model).toBe('llava');
expect(client.isVisionModel).toBeTruthy();
expect(client.modelOptions.stop).toBeUndefined();
});
});
});

View File

@@ -1,9 +1,10 @@
const crypto = require('crypto');
const { Constants } = require('librechat-data-provider');
const { HumanChatMessage, AIChatMessage } = require('langchain/schema');
const PluginsClient = require('../PluginsClient');
const crypto = require('crypto');
jest.mock('../../../lib/db/connectDb');
jest.mock('../../../models/Conversation', () => {
jest.mock('~/lib/db/connectDb');
jest.mock('~/models/Conversation', () => {
return function () {
return {
save: jest.fn(),
@@ -12,6 +13,12 @@ jest.mock('../../../models/Conversation', () => {
};
});
const defaultAzureOptions = {
azureOpenAIApiInstanceName: 'your-instance-name',
azureOpenAIApiDeploymentName: 'your-deployment-name',
azureOpenAIApiVersion: '2020-07-01-preview',
};
describe('PluginsClient', () => {
let TestAgent;
let options = {
@@ -60,7 +67,7 @@ describe('PluginsClient', () => {
TestAgent.setOptions(opts);
}
const conversationId = opts.conversationId || crypto.randomUUID();
const parentMessageId = opts.parentMessageId || '00000000-0000-0000-0000-000000000000';
const parentMessageId = opts.parentMessageId || Constants.NO_PARENT;
const userMessageId = opts.overrideParentMessageId || crypto.randomUUID();
this.pastMessages = await TestAgent.loadHistory(
conversationId,
@@ -144,4 +151,73 @@ describe('PluginsClient', () => {
expect(chatMessages[0].text).toEqual(userMessage);
});
});
describe('getFunctionModelName', () => {
let client;
beforeEach(() => {
client = new PluginsClient('dummy_api_key');
});
test('should return the input when it includes a dash followed by four digits', () => {
expect(client.getFunctionModelName('-1234')).toBe('-1234');
expect(client.getFunctionModelName('gpt-4-5678-preview')).toBe('gpt-4-5678-preview');
});
test('should return the input for all function-capable models (`0613` models and above)', () => {
expect(client.getFunctionModelName('gpt-4-0613')).toBe('gpt-4-0613');
expect(client.getFunctionModelName('gpt-4-32k-0613')).toBe('gpt-4-32k-0613');
expect(client.getFunctionModelName('gpt-3.5-turbo-0613')).toBe('gpt-3.5-turbo-0613');
expect(client.getFunctionModelName('gpt-3.5-turbo-16k-0613')).toBe('gpt-3.5-turbo-16k-0613');
expect(client.getFunctionModelName('gpt-3.5-turbo-1106')).toBe('gpt-3.5-turbo-1106');
expect(client.getFunctionModelName('gpt-4-1106-preview')).toBe('gpt-4-1106-preview');
expect(client.getFunctionModelName('gpt-4-1106')).toBe('gpt-4-1106');
});
test('should return the corresponding model if input is non-function capable (`0314` models)', () => {
expect(client.getFunctionModelName('gpt-4-0314')).toBe('gpt-4');
expect(client.getFunctionModelName('gpt-4-32k-0314')).toBe('gpt-4');
expect(client.getFunctionModelName('gpt-3.5-turbo-0314')).toBe('gpt-3.5-turbo');
expect(client.getFunctionModelName('gpt-3.5-turbo-16k-0314')).toBe('gpt-3.5-turbo');
});
test('should return "gpt-3.5-turbo" when the input includes "gpt-3.5-turbo"', () => {
expect(client.getFunctionModelName('test gpt-3.5-turbo model')).toBe('gpt-3.5-turbo');
});
test('should return "gpt-4" when the input includes "gpt-4"', () => {
expect(client.getFunctionModelName('testing gpt-4')).toBe('gpt-4');
});
test('should return "gpt-3.5-turbo" for input that does not meet any specific condition', () => {
expect(client.getFunctionModelName('random string')).toBe('gpt-3.5-turbo');
expect(client.getFunctionModelName('')).toBe('gpt-3.5-turbo');
});
});
describe('Azure OpenAI tests specific to Plugins', () => {
// TODO: add more tests for Azure OpenAI integration with Plugins
// let client;
// beforeEach(() => {
// client = new PluginsClient('dummy_api_key');
// });
test('should not call getFunctionModelName when azure options are set', () => {
const spy = jest.spyOn(PluginsClient.prototype, 'getFunctionModelName');
const model = 'gpt-4-turbo';
// note, without the azure change in PR #1766, `getFunctionModelName` is called twice
const testClient = new PluginsClient('dummy_api_key', {
agentOptions: {
model,
agent: 'functions',
},
azure: defaultAzureOptions,
});
expect(spy).not.toHaveBeenCalled();
expect(testClient.agentOptions.model).toBe(model);
spy.mockRestore();
});
});
});

View File

@@ -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;

View File

@@ -0,0 +1,98 @@
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
static DEFAULT_API_VERSION = '2023-11-01';
static DEFAULT_QUERY_TYPE = 'simple';
static DEFAULT_TOP = 5;
// Helper function for initializing properties
_initializeField(field, envVar, defaultValue) {
return field || process.env[envVar] || defaultValue;
}
constructor(fields = {}) {
super();
this.name = 'azure-ai-search';
this.description =
'Use the \'azure-ai-search\' tool to retrieve search results relevant to your input';
// Initialize properties using helper function
this.serviceEndpoint = this._initializeField(
fields.AZURE_AI_SEARCH_SERVICE_ENDPOINT,
'AZURE_AI_SEARCH_SERVICE_ENDPOINT',
);
this.indexName = this._initializeField(
fields.AZURE_AI_SEARCH_INDEX_NAME,
'AZURE_AI_SEARCH_INDEX_NAME',
);
this.apiKey = this._initializeField(fields.AZURE_AI_SEARCH_API_KEY, 'AZURE_AI_SEARCH_API_KEY');
this.apiVersion = this._initializeField(
fields.AZURE_AI_SEARCH_API_VERSION,
'AZURE_AI_SEARCH_API_VERSION',
AzureAISearch.DEFAULT_API_VERSION,
);
this.queryType = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE,
'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE',
AzureAISearch.DEFAULT_QUERY_TYPE,
);
this.top = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_TOP,
'AZURE_AI_SEARCH_SEARCH_OPTION_TOP',
AzureAISearch.DEFAULT_TOP,
);
this.select = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_SELECT,
'AZURE_AI_SEARCH_SEARCH_OPTION_SELECT',
);
// Check for required fields
if (!this.serviceEndpoint || !this.indexName || !this.apiKey) {
throw new Error(
'Missing AZURE_AI_SEARCH_SERVICE_ENDPOINT, AZURE_AI_SEARCH_INDEX_NAME, or AZURE_AI_SEARCH_API_KEY environment variable.',
);
}
// Create SearchClient
this.client = new SearchClient(
this.serviceEndpoint,
this.indexName,
new AzureKeyCredential(this.apiKey),
{ apiVersion: this.apiVersion },
);
// Define schema
this.schema = z.object({
query: z.string().describe('Search word or phrase to Azure AI Search'),
});
}
// Improved error handling and logging
async _call(data) {
const { query } = data;
try {
const searchOption = {
queryType: this.queryType,
top: this.top,
};
if (this.select) {
searchOption.select = this.select.split(',');
}
const searchResults = await this.client.search(query, searchOption);
const resultDocuments = [];
for await (const result of searchResults.results) {
resultDocuments.push(result.document);
}
return JSON.stringify(resultDocuments);
} catch (error) {
logger.error('Azure AI Search request failed', error);
return 'There was an error with Azure AI Search.';
}
}
}
module.exports = AzureAISearch;

View File

@@ -1,111 +0,0 @@
const { Tool } = require('langchain/tools');
const { SearchClient, AzureKeyCredential } = require('@azure/search-documents');
class AzureCognitiveSearch extends Tool {
constructor(fields = {}) {
super();
this.serviceEndpoint =
fields.AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT || this.getServiceEndpoint();
this.indexName = fields.AZURE_COGNITIVE_SEARCH_INDEX_NAME || this.getIndexName();
this.apiKey = fields.AZURE_COGNITIVE_SEARCH_API_KEY || this.getApiKey();
this.apiVersion = fields.AZURE_COGNITIVE_SEARCH_API_VERSION || this.getApiVersion();
this.queryType = fields.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE || this.getQueryType();
this.top = fields.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP || this.getTop();
this.select = fields.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_SELECT || this.getSelect();
this.client = new SearchClient(
this.serviceEndpoint,
this.indexName,
new AzureKeyCredential(this.apiKey),
{
apiVersion: this.apiVersion,
},
);
}
/**
* The name of the tool.
* @type {string}
*/
name = 'azure-cognitive-search';
/**
* A description for the agent to use
* @type {string}
*/
description =
'Use the \'azure-cognitive-search\' tool to retrieve search results relevant to your input';
getServiceEndpoint() {
const serviceEndpoint = process.env.AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT || '';
if (!serviceEndpoint) {
throw new Error('Missing AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT environment variable.');
}
return serviceEndpoint;
}
getIndexName() {
const indexName = process.env.AZURE_COGNITIVE_SEARCH_INDEX_NAME || '';
if (!indexName) {
throw new Error('Missing AZURE_COGNITIVE_SEARCH_INDEX_NAME environment variable.');
}
return indexName;
}
getApiKey() {
const apiKey = process.env.AZURE_COGNITIVE_SEARCH_API_KEY || '';
if (!apiKey) {
throw new Error('Missing AZURE_COGNITIVE_SEARCH_API_KEY environment variable.');
}
return apiKey;
}
getApiVersion() {
return process.env.AZURE_COGNITIVE_SEARCH_API_VERSION || '2020-06-30';
}
getQueryType() {
return process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE || 'simple';
}
getTop() {
if (process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP) {
return Number(process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP);
} else {
return 5;
}
}
getSelect() {
if (process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_SELECT) {
return process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_SELECT.split(',');
} else {
return null;
}
}
async _call(query) {
try {
const searchOption = {
queryType: this.queryType,
top: this.top,
};
if (this.select) {
searchOption.select = this.select;
}
const searchResults = await this.client.search(query, searchOption);
const resultDocuments = [];
for await (const result of searchResults.results) {
resultDocuments.push(result.document);
}
return JSON.stringify(resultDocuments);
} catch (error) {
console.error(`Azure Cognitive Search request failed: ${error}`);
return 'There was an error with Azure Cognitive Search.';
}
}
}
module.exports = AzureCognitiveSearch;

View File

@@ -1,52 +0,0 @@
const { Tool } = require('langchain/tools');
const WebSocket = require('ws');
const { promisify } = require('util');
const fs = require('fs');
class CodeInterpreter extends Tool {
constructor() {
super();
this.name = 'code-interpreter';
this.description = `If there is plotting or any image related tasks, save the result as .png file.
No need show the image or plot. USE print(variable_name) if you need output.You can run python codes with this plugin.You have to use print function in python code to get any result from this plugin.
This does not support user input. Even if the code has input() function, change it to an appropriate value.
You can show the user the code with input() functions. But the code passed to the plug-in should not contain input().
You should provide properly formatted code to this plugin. If the code is executed successfully, the stdout will be returned to you. You have to print that to the user, and if the user had
asked for an explanation, you have to provide one. If the output is "Error From here" or any other error message,
tell the user "Python Engine Failed" and continue with whatever you are supposed to do.`;
// Create a promisified version of fs.unlink
this.unlinkAsync = promisify(fs.unlink);
}
async _call(input) {
const websocket = new WebSocket('ws://localhost:3380'); // Update with your WebSocket server URL
// Wait until the WebSocket connection is open
await new Promise((resolve) => {
websocket.onopen = resolve;
});
// Send the Python code to the server
websocket.send(input);
// Wait for the result from the server
const result = await new Promise((resolve) => {
websocket.onmessage = (event) => {
resolve(event.data);
};
// Handle WebSocket connection closed
websocket.onclose = () => {
resolve('Python Engine Failed');
};
});
// Close the WebSocket connection
websocket.close();
return result;
}
}
module.exports = CodeInterpreter;

View File

@@ -1,41 +1,43 @@
// From https://platform.openai.com/docs/api-reference/images/create
// To use this tool, you must pass in a configured OpenAIApi object.
const fs = require('fs');
const OpenAI = require('openai');
// const { genAzureEndpoint } = require('../../../utils/genAzureEndpoints');
const { Tool } = require('langchain/tools');
const saveImageFromUrl = require('./saveImageFromUrl');
const path = require('path');
const OpenAI = require('openai');
const { v4: uuidv4 } = require('uuid');
const { Tool } = require('langchain/tools');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { FileContext } = require('librechat-data-provider');
const { getImageBasename } = require('~/server/services/Files/images');
const extractBaseURL = require('~/utils/extractBaseURL');
const { logger } = require('~/config');
class OpenAICreateImage extends Tool {
constructor(fields = {}) {
super();
let apiKey = fields.DALLE_API_KEY || this.getApiKey();
// let azureKey = fields.AZURE_API_KEY || process.env.AZURE_API_KEY;
let config = { apiKey };
this.userId = fields.userId;
this.fileStrategy = fields.fileStrategy;
if (fields.processFileURL) {
this.processFileURL = fields.processFileURL.bind(this);
}
let apiKey = fields.DALLE2_API_KEY ?? fields.DALLE_API_KEY ?? this.getApiKey();
const config = { apiKey };
if (process.env.DALLE_REVERSE_PROXY) {
config.baseURL = extractBaseURL(process.env.DALLE_REVERSE_PROXY);
}
if (process.env.DALLE2_AZURE_API_VERSION && process.env.DALLE2_BASEURL) {
config.baseURL = process.env.DALLE2_BASEURL;
config.defaultQuery = { 'api-version': process.env.DALLE2_AZURE_API_VERSION };
config.defaultHeaders = {
'api-key': process.env.DALLE2_API_KEY,
'Content-Type': 'application/json',
};
config.apiKey = process.env.DALLE2_API_KEY;
}
if (process.env.PROXY) {
config.httpAgent = new HttpsProxyAgent(process.env.PROXY);
}
// if (azureKey) {
// apiKey = azureKey;
// const azureConfig = {
// apiKey,
// azureOpenAIApiInstanceName: process.env.AZURE_OPENAI_API_INSTANCE_NAME || fields.azureOpenAIApiInstanceName,
// azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME || fields.azureOpenAIApiDeploymentName,
// azureOpenAIApiVersion: process.env.AZURE_OPENAI_API_VERSION || fields.azureOpenAIApiVersion
// };
// config = {
// apiKey,
// basePath: genAzureEndpoint({
// ...azureConfig,
// }),
// baseOptions: {
// headers: { 'api-key': apiKey },
// params: {
// 'api-version': azureConfig.azureOpenAIApiVersion // this might change. I got the current value from the sample code at https://oai.azure.com/portal/chat
// }
// }
// };
// }
this.openai = new OpenAI(config);
this.name = 'dall-e';
this.description = `You can generate images with 'dall-e'. This tool is exclusively for visual content.
@@ -45,10 +47,24 @@ Guidelines:
- It's best to follow this format for image creation. Come up with the optional inputs yourself if none are given:
"Subject: [subject], Style: [style], Color: [color], Details: [details], Emotion: [emotion]"
- Generate images only once per human query unless explicitly requested by the user`;
this.description_for_model =
process.env.DALLE2_SYSTEM_PROMPT ??
`// Whenever a description of an image is given, generate prompts (following these rules), and use dalle to create the image. If the user does not ask for a specific number of images, default to creating 2 prompts to send to dalle that are written to be as diverse as possible. All prompts sent to dalle must abide by the following policies:
// 1. Prompts must be in English. Translate to English if needed.
// 2. One image per function call. Create only 1 image per request unless explicitly told to generate more than 1 image.
// 3. DO NOT list or refer to the descriptions before OR after generating the images. They should ONLY ever be written out ONCE, in the \`"prompts"\` field of the request. You do not need to ask for permission to generate, just do it!
// 4. Always mention the image type (photo, oil painting, watercolor painting, illustration, cartoon, drawing, vector, render, etc.) at the beginning of the caption. Unless the captions suggests otherwise, make one of the images a photo.
// 5. Diversify depictions of ALL images with people to always include always DESCENT and GENDER for EACH person using direct terms. Adjust only human descriptions.
// - EXPLICITLY specify these attributes, not abstractly reference them. The attributes should be specified in a minimal way and should directly describe their physical form.
// - Your choices should be grounded in reality. For example, all of a given OCCUPATION should not be the same gender or race. Additionally, focus on creating diverse, inclusive, and exploratory scenes via the properties you choose during rewrites. Make choices that may be insightful or unique sometimes.
// - Use "various" or "diverse" ONLY IF the description refers to groups of more than 3 people. Do not change the number of people requested in the original description.
// - Don't alter memes, fictional character origins, or unseen people. Maintain the original prompt's intent and prioritize quality.
// The prompt must intricately describe every part of the image in concrete, objective detail. THINK about what the end goal of the description is, and extrapolate that to what would make satisfying images.
// All descriptions sent to dalle should be a paragraph of text that is extremely descriptive and detailed. Each should be more than 3 sentences long.`;
}
getApiKey() {
const apiKey = process.env.DALLE_API_KEY || '';
const apiKey = process.env.DALLE2_API_KEY ?? process.env.DALLE_API_KEY ?? '';
if (!apiKey) {
throw new Error('Missing DALLE_API_KEY environment variable.');
}
@@ -58,26 +74,30 @@ Guidelines:
replaceUnwantedChars(inputString) {
return inputString
.replace(/\r\n|\r|\n/g, ' ')
.replace('"', '')
.replace(/"/g, '')
.trim();
}
getMarkdownImageUrl(imageName) {
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
wrapInMarkdown(imageUrl) {
return `![generated image](${imageUrl})`;
}
async _call(input) {
const resp = await this.openai.images.generate({
prompt: this.replaceUnwantedChars(input),
// TODO: Future idea -- could we ask an LLM to extract these arguments from an input that might contain them?
n: 1,
// size: '1024x1024'
size: '512x512',
});
let resp;
try {
resp = await this.openai.images.generate({
prompt: this.replaceUnwantedChars(input),
// TODO: Future idea -- could we ask an LLM to extract these arguments from an input that might contain them?
n: 1,
// size: '1024x1024'
size: '512x512',
});
} catch (error) {
logger.error('[DALL-E] Problem generating the image:', error);
return `Something went wrong when trying to generate the image. The DALL-E API may be unavailable:
Error Message: ${error.message}`;
}
const theImageUrl = resp.data[0].url;
@@ -85,32 +105,35 @@ 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);
const imageExt = path.extname(imageBasename);
if (match) {
imageName = match[0];
console.log(imageName); // Output: img-lgCf7ppcbhqQrz6a5ear6FOb.png
} else {
console.log('No image name found in the string.');
}
const extension = imageExt.startsWith('.') ? imageExt.slice(1) : imageExt;
const imageName = `img-${uuidv4()}.${extension}`;
this.outputPath = path.resolve(__dirname, '..', '..', '..', '..', 'client', 'public', 'images');
const appRoot = path.resolve(__dirname, '..', '..', '..', '..', 'client');
this.relativeImageUrl = path.relative(appRoot, this.outputPath);
// Check if directory exists, if not create it
if (!fs.existsSync(this.outputPath)) {
fs.mkdirSync(this.outputPath, { recursive: true });
}
logger.debug('[DALL-E-2]', {
imageName,
imageBasename,
imageExt,
extension,
theImageUrl,
data: resp.data[0],
});
try {
await saveImageFromUrl(theImageUrl, this.outputPath, imageName);
this.result = this.getMarkdownImageUrl(imageName);
const result = await this.processFileURL({
fileStrategy: this.fileStrategy,
userId: this.userId,
URL: theImageUrl,
fileName: imageName,
basePath: 'images',
context: FileContext.image_generation,
});
this.result = this.wrapInMarkdown(result.filepath);
} catch (error) {
console.error('Error while saving the image:', error);
this.result = theImageUrl;
logger.error('Error while saving the image:', error);
this.result = `Failed to save the image locally. ${error.message}`;
}
return this.result;

View File

@@ -1,120 +0,0 @@
const { Tool } = require('langchain/tools');
const { google } = require('googleapis');
/**
* Represents a tool that allows an agent to use the Google Custom Search API.
* @extends Tool
*/
class GoogleSearchAPI extends Tool {
constructor(fields = {}) {
super();
this.cx = fields.GOOGLE_CSE_ID || this.getCx();
this.apiKey = fields.GOOGLE_API_KEY || this.getApiKey();
this.customSearch = undefined;
}
/**
* The name of the tool.
* @type {string}
*/
name = 'google';
/**
* A description for the agent to use
* @type {string}
*/
description =
'Use the \'google\' tool to retrieve internet search results relevant to your input. The results will return links and snippets of text from the webpages';
description_for_model =
'Use the \'google\' tool to retrieve internet search results relevant to your input. The results will return links and snippets of text from the webpages';
getCx() {
const cx = process.env.GOOGLE_CSE_ID || '';
if (!cx) {
throw new Error('Missing GOOGLE_CSE_ID environment variable.');
}
return cx;
}
getApiKey() {
const apiKey = process.env.GOOGLE_API_KEY || '';
if (!apiKey) {
throw new Error('Missing GOOGLE_API_KEY environment variable.');
}
return apiKey;
}
getCustomSearch() {
if (!this.customSearch) {
const version = 'v1';
this.customSearch = google.customsearch(version);
}
return this.customSearch;
}
resultsToReadableFormat(results) {
let output = 'Results:\n';
results.forEach((resultObj, index) => {
output += `Title: ${resultObj.title}\n`;
output += `Link: ${resultObj.link}\n`;
if (resultObj.snippet) {
output += `Snippet: ${resultObj.snippet}\n`;
}
if (index < results.length - 1) {
output += '\n';
}
});
return output;
}
/**
* Calls the tool with the provided input and returns a promise that resolves with a response from the Google Custom Search API.
* @param {string} input - The input to provide to the API.
* @returns {Promise<String>} A promise that resolves with a response from the Google Custom Search API.
*/
async _call(input) {
try {
const metadataResults = [];
const response = await this.getCustomSearch().cse.list({
q: input,
cx: this.cx,
auth: this.apiKey,
num: 5, // Limit the number of results to 5
});
// return response.data;
// console.log(response.data);
if (!response.data.items || response.data.items.length === 0) {
return this.resultsToReadableFormat([
{ title: 'No good Google Search Result was found', link: '' },
]);
}
// const results = response.items.slice(0, numResults);
const results = response.data.items;
for (const result of results) {
const metadataResult = {
title: result.title || '',
link: result.link || '',
};
if (result.snippet) {
metadataResult.snippet = result.snippet;
}
metadataResults.push(metadataResult);
}
return this.resultsToReadableFormat(metadataResults);
} catch (error) {
console.log(`Error searching Google: ${error}`);
// throw error;
return 'There was an error searching Google.';
}
}
}
module.exports = GoogleSearchAPI;

View File

@@ -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('<html')) {
return 'This tool is not designed to browse web pages. Only use it for API calls.';
}
return text.slice(0, this.maxOutputLength);
} catch (error) {
console.log(error);
return `${error}`;
}
}
}
module.exports = HttpRequestTool;

View File

@@ -1,9 +1,10 @@
// Generates image using stable diffusion webui's api (automatic1111)
const fs = require('fs');
const { Tool } = require('langchain/tools');
const path = require('path');
const axios = require('axios');
const sharp = require('sharp');
const { Tool } = require('langchain/tools');
const { logger } = require('~/config');
class StableDiffusionAPI extends Tool {
constructor(fields) {
@@ -81,7 +82,7 @@ Guidelines:
.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;
}

View File

@@ -1,6 +1,7 @@
/* eslint-disable no-useless-escape */
const axios = require('axios');
const { Tool } = require('langchain/tools');
const { logger } = require('~/config');
class WolframAlphaAPI extends Tool {
constructor(fields) {
@@ -38,7 +39,7 @@ General guidelines:
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;
}
}
@@ -68,11 +69,10 @@ General guidelines:
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.';
}
}

View File

@@ -1,11 +1,12 @@
require('dotenv').config();
const { z } = require('zod');
const fs = require('fs');
const yaml = require('js-yaml');
const { z } = require('zod');
const path = require('path');
const { DynamicStructuredTool } = require('langchain/tools');
const yaml = require('js-yaml');
const { createOpenAPIChain } = require('langchain/chains');
const { DynamicStructuredTool } = require('langchain/tools');
const { ChatPromptTemplate, HumanMessagePromptTemplate } = require('langchain/prompts');
const { logger } = require('~/config');
function addLinePrefix(text, prefix = '// ') {
return text
@@ -52,7 +53,7 @@ async function readSpecFile(filePath) {
}
return yaml.load(fileContents);
} catch (e) {
console.error(e);
logger.error('[readSpecFile] error', e);
return false;
}
}
@@ -83,54 +84,51 @@ async function getSpec(url) {
return ValidSpecPath.parse(url);
}
async function createOpenAPIPlugin({ data, llm, user, message, memory, signal, verbose = false }) {
async function createOpenAPIPlugin({ data, llm, user, message, memory, signal }) {
let spec;
try {
spec = await getSpec(data.api.url, verbose);
spec = await getSpec(data.api.url);
} catch (error) {
verbose && console.debug('getSpec error', error);
logger.error('[createOpenAPIPlugin] getSpec error', error);
return null;
}
if (!spec) {
verbose && console.debug('No spec found');
logger.warn('[createOpenAPIPlugin] No spec found');
return null;
}
const headers = {};
const { auth, name_for_model, description_for_model, description_for_human } = data;
if (auth && AuthDefinition.parse(auth)) {
verbose && console.debug('auth detected', auth);
logger.debug('[createOpenAPIPlugin] auth detected', auth);
const { openai } = auth.verification_tokens;
if (AuthBearer.parse(auth)) {
headers.authorization = `Bearer ${openai}`;
verbose && console.debug('added auth bearer', headers);
logger.debug('[createOpenAPIPlugin] added auth bearer', headers);
}
}
const chainOptions = {
llm,
verbose,
};
const chainOptions = { llm };
if (data.headers && data.headers['librechat_user_id']) {
verbose && console.debug('id detected', headers);
logger.debug('[createOpenAPIPlugin] id detected', headers);
headers[data.headers['librechat_user_id']] = user;
}
if (Object.keys(headers).length > 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` : '';
}

View File

@@ -1,39 +1,44 @@
const GoogleSearchAPI = require('./GoogleSearch');
const HttpRequestTool = require('./HttpRequestTool');
const AIPluginTool = require('./AIPluginTool');
const OpenAICreateImage = require('./DALL-E');
const StructuredSD = require('./structured/StableDiffusion');
const StableDiffusionAPI = require('./StableDiffusion');
const availableTools = require('./manifest.json');
// Basic Tools
const CodeBrew = require('./CodeBrew');
const WolframAlphaAPI = require('./Wolfram');
const StructuredWolfram = require('./structured/Wolfram');
const AzureAiSearch = require('./AzureAiSearch');
const OpenAICreateImage = require('./DALL-E');
const StableDiffusionAPI = require('./StableDiffusion');
const SelfReflectionTool = require('./SelfReflection');
const AzureCognitiveSearch = require('./AzureCognitiveSearch');
const StructuredACS = require('./structured/AzureCognitiveSearch');
// Structured Tools
const DALLE3 = require('./structured/DALLE3');
const ChatTool = require('./structured/ChatTool');
const E2BTools = require('./structured/E2BTools');
const CodeSherpa = require('./structured/CodeSherpa');
const StructuredSD = require('./structured/StableDiffusion');
const StructuredACS = require('./structured/AzureAISearch');
const CodeSherpaTools = require('./structured/CodeSherpaTools');
const availableTools = require('./manifest.json');
const CodeInterpreter = require('./CodeInterpreter');
const CodeBrew = require('./CodeBrew');
const GoogleSearchAPI = require('./structured/GoogleSearch');
const StructuredWolfram = require('./structured/Wolfram');
const TavilySearchResults = require('./structured/TavilySearchResults');
const TraversaalSearch = require('./structured/TraversaalSearch');
module.exports = {
availableTools,
// Basic Tools
CodeBrew,
AzureAiSearch,
GoogleSearchAPI,
HttpRequestTool,
AIPluginTool,
WolframAlphaAPI,
OpenAICreateImage,
StableDiffusionAPI,
StructuredSD,
WolframAlphaAPI,
StructuredWolfram,
SelfReflectionTool,
AzureCognitiveSearch,
StructuredACS,
E2BTools,
// Structured Tools
DALLE3,
ChatTool,
E2BTools,
CodeSherpa,
StructuredSD,
StructuredACS,
CodeSherpaTools,
CodeInterpreter,
CodeBrew,
StructuredWolfram,
TavilySearchResults,
TraversaalSearch,
};

View File

@@ -1,4 +1,17 @@
[
{
"name": "Traversaal",
"pluginKey": "traversaal_search",
"description": "Traversaal is a robust search API tailored for LLM Agents. Get an API key here: https://api.traversaal.ai",
"icon": "https://traversaal.ai/favicon.ico",
"authConfig": [
{
"authField": "TRAVERSAAL_API_KEY",
"label": "Traversaal API Key",
"description": "Get your API key here: <a href=\"https://api.traversaal.ai\" target=\"_blank\">https://api.traversaal.ai</a>"
}
]
},
{
"name": "Google",
"pluginKey": "google",
@@ -11,7 +24,7 @@
"description": "This is your Google Custom Search Engine ID. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>."
},
{
"authField": "GOOGLE_API_KEY",
"authField": "GOOGLE_SEARCH_API_KEY",
"label": "Google API Key",
"description": "This is your Google Custom Search API Key. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>."
}
@@ -47,7 +60,7 @@
"name": "CodeSherpa",
"pluginKey": "codesherpa_tools",
"description": "[Experimental] A REPL for your chat. Requires https://github.com/iamgreggarcia/codesherpa",
"icon": "https://github.com/iamgreggarcia/codesherpa/blob/main/localserver/_logo.png",
"icon": "https://raw.githubusercontent.com/iamgreggarcia/codesherpa/main/localserver/_logo.png",
"authConfig": [
{
"authField": "CODESHERPA_SERVER_URL",
@@ -89,12 +102,38 @@
"icon": "https://i.imgur.com/u2TzXzH.png",
"authConfig": [
{
"authField": "DALLE_API_KEY",
"authField": "DALLE2_API_KEY||DALLE_API_KEY",
"label": "OpenAI API Key",
"description": "You can use DALL-E with your API Key from OpenAI."
}
]
},
{
"name": "DALL-E-3",
"pluginKey": "dalle",
"description": "[DALL-E-3] Create realistic images and art from a description in natural language",
"icon": "https://i.imgur.com/u2TzXzH.png",
"authConfig": [
{
"authField": "DALLE3_API_KEY||DALLE_API_KEY",
"label": "OpenAI API Key",
"description": "You can use DALL-E with your API Key from OpenAI."
}
]
},
{
"name": "Tavily Search",
"pluginKey": "tavily_search_results_json",
"description": "Tavily Search is a robust search API tailored for LLM Agents. It seamlessly integrates with diverse data sources to ensure a superior, relevant search experience.",
"icon": "https://tavily.com/favicon.ico",
"authConfig": [
{
"authField": "TAVILY_API_KEY",
"label": "Tavily API Key",
"description": "Get your API key here: https://app.tavily.com/"
}
]
},
{
"name": "Calculator",
"pluginKey": "calculator",
@@ -130,38 +169,25 @@
]
},
{
"name": "Azure Cognitive Search",
"pluginKey": "azure-cognitive-search",
"description": "Use Azure Cognitive Search to find information",
"name": "Azure AI Search",
"pluginKey": "azure-ai-search",
"description": "Use Azure AI Search to find information",
"icon": "https://i.imgur.com/E7crPze.png",
"authConfig": [
{
"authField": "AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT",
"label": "Azur Cognitive Search Endpoint",
"description": "You need to provide your Endpoint for Azure Cognitive Search."
"authField": "AZURE_AI_SEARCH_SERVICE_ENDPOINT",
"label": "Azure AI Search Endpoint",
"description": "You need to provide your Endpoint for Azure AI Search."
},
{
"authField": "AZURE_COGNITIVE_SEARCH_INDEX_NAME",
"label": "Azur Cognitive Search Index Name",
"description": "You need to provide your Index Name for Azure Cognitive Search."
"authField": "AZURE_AI_SEARCH_INDEX_NAME",
"label": "Azure AI Search Index Name",
"description": "You need to provide your Index Name for Azure AI Search."
},
{
"authField": "AZURE_COGNITIVE_SEARCH_API_KEY",
"label": "Azur Cognitive Search API Key",
"description": "You need to provideq your API Key for Azure Cognitive Search."
}
]
},
{
"name": "Code Interpreter",
"pluginKey": "codeinterpreter",
"description": "[Experimental] Analyze files and run code online with ease. Requires dockerized python server in /pyserver/",
"icon": "/assets/code.png",
"authConfig": [
{
"authField": "OPENAI_API_KEY",
"label": "OpenAI API Key",
"description": "Gets Code from Open AI API"
"authField": "AZURE_AI_SEARCH_API_KEY",
"label": "Azure AI Search API Key",
"description": "You need to provideq your API Key for Azure AI Search."
}
]
},

View File

@@ -1,39 +0,0 @@
const axios = require('axios');
const fs = require('fs');
const path = require('path');
async function saveImageFromUrl(url, outputPath, outputFilename) {
try {
// Fetch the image from the URL
const response = await axios({
url,
responseType: 'stream',
});
// Check if the output directory exists, if not, create it
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}
// Ensure the output filename has a '.png' extension
const filenameWithPngExt = outputFilename.endsWith('.png')
? outputFilename
: `${outputFilename}.png`;
// Create a writable stream for the output path
const outputFilePath = path.join(outputPath, filenameWithPngExt);
const writer = fs.createWriteStream(outputFilePath);
// Pipe the response data to the output file
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
} catch (error) {
console.error('Error while saving the image:', error);
}
}
module.exports = saveImageFromUrl;

View File

@@ -0,0 +1,104 @@
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
static DEFAULT_API_VERSION = '2023-11-01';
static DEFAULT_QUERY_TYPE = 'simple';
static DEFAULT_TOP = 5;
// Helper function for initializing properties
_initializeField(field, envVar, defaultValue) {
return field || process.env[envVar] || defaultValue;
}
constructor(fields = {}) {
super();
this.name = 'azure-ai-search';
this.description =
'Use the \'azure-ai-search\' tool to retrieve search results relevant to your input';
/* Used to initialize the Tool without necessary variables. */
this.override = fields.override ?? false;
// Define schema
this.schema = z.object({
query: z.string().describe('Search word or phrase to Azure AI Search'),
});
// Initialize properties using helper function
this.serviceEndpoint = this._initializeField(
fields.AZURE_AI_SEARCH_SERVICE_ENDPOINT,
'AZURE_AI_SEARCH_SERVICE_ENDPOINT',
);
this.indexName = this._initializeField(
fields.AZURE_AI_SEARCH_INDEX_NAME,
'AZURE_AI_SEARCH_INDEX_NAME',
);
this.apiKey = this._initializeField(fields.AZURE_AI_SEARCH_API_KEY, 'AZURE_AI_SEARCH_API_KEY');
this.apiVersion = this._initializeField(
fields.AZURE_AI_SEARCH_API_VERSION,
'AZURE_AI_SEARCH_API_VERSION',
AzureAISearch.DEFAULT_API_VERSION,
);
this.queryType = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE,
'AZURE_AI_SEARCH_SEARCH_OPTION_QUERY_TYPE',
AzureAISearch.DEFAULT_QUERY_TYPE,
);
this.top = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_TOP,
'AZURE_AI_SEARCH_SEARCH_OPTION_TOP',
AzureAISearch.DEFAULT_TOP,
);
this.select = this._initializeField(
fields.AZURE_AI_SEARCH_SEARCH_OPTION_SELECT,
'AZURE_AI_SEARCH_SEARCH_OPTION_SELECT',
);
// Check for required fields
if (!this.override && (!this.serviceEndpoint || !this.indexName || !this.apiKey)) {
throw new Error(
'Missing AZURE_AI_SEARCH_SERVICE_ENDPOINT, AZURE_AI_SEARCH_INDEX_NAME, or AZURE_AI_SEARCH_API_KEY environment variable.',
);
}
if (this.override) {
return;
}
// Create SearchClient
this.client = new SearchClient(
this.serviceEndpoint,
this.indexName,
new AzureKeyCredential(this.apiKey),
{ apiVersion: this.apiVersion },
);
}
// Improved error handling and logging
async _call(data) {
const { query } = data;
try {
const searchOption = {
queryType: this.queryType,
top: this.top,
};
if (this.select) {
searchOption.select = this.select.split(',');
}
const searchResults = await this.client.search(query, searchOption);
const resultDocuments = [];
for await (const result of searchResults.results) {
resultDocuments.push(result.document);
}
return JSON.stringify(resultDocuments);
} catch (error) {
logger.error('Azure AI Search request failed', error);
return 'There was an error with Azure AI Search.';
}
}
}
module.exports = AzureAISearch;

View File

@@ -1,116 +0,0 @@
const { StructuredTool } = require('langchain/tools');
const { z } = require('zod');
const { SearchClient, AzureKeyCredential } = require('@azure/search-documents');
class AzureCognitiveSearch extends StructuredTool {
constructor(fields = {}) {
super();
this.serviceEndpoint =
fields.AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT || this.getServiceEndpoint();
this.indexName = fields.AZURE_COGNITIVE_SEARCH_INDEX_NAME || this.getIndexName();
this.apiKey = fields.AZURE_COGNITIVE_SEARCH_API_KEY || this.getApiKey();
this.apiVersion = fields.AZURE_COGNITIVE_SEARCH_API_VERSION || this.getApiVersion();
this.queryType = fields.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE || this.getQueryType();
this.top = fields.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP || this.getTop();
this.select = fields.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_SELECT || this.getSelect();
this.client = new SearchClient(
this.serviceEndpoint,
this.indexName,
new AzureKeyCredential(this.apiKey),
{
apiVersion: this.apiVersion,
},
);
this.schema = z.object({
query: z.string().describe('Search word or phrase to Azure Cognitive Search'),
});
}
/**
* The name of the tool.
* @type {string}
*/
name = 'azure-cognitive-search';
/**
* A description for the agent to use
* @type {string}
*/
description =
'Use the \'azure-cognitive-search\' tool to retrieve search results relevant to your input';
getServiceEndpoint() {
const serviceEndpoint = process.env.AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT || '';
if (!serviceEndpoint) {
throw new Error('Missing AZURE_COGNITIVE_SEARCH_SERVICE_ENDPOINT environment variable.');
}
return serviceEndpoint;
}
getIndexName() {
const indexName = process.env.AZURE_COGNITIVE_SEARCH_INDEX_NAME || '';
if (!indexName) {
throw new Error('Missing AZURE_COGNITIVE_SEARCH_INDEX_NAME environment variable.');
}
return indexName;
}
getApiKey() {
const apiKey = process.env.AZURE_COGNITIVE_SEARCH_API_KEY || '';
if (!apiKey) {
throw new Error('Missing AZURE_COGNITIVE_SEARCH_API_KEY environment variable.');
}
return apiKey;
}
getApiVersion() {
return process.env.AZURE_COGNITIVE_SEARCH_API_VERSION || '2020-06-30';
}
getQueryType() {
return process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_QUERY_TYPE || 'simple';
}
getTop() {
if (process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP) {
return Number(process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_TOP);
} else {
return 5;
}
}
getSelect() {
if (process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_SELECT) {
return process.env.AZURE_COGNITIVE_SEARCH_SEARCH_OPTION_SELECT.split(',');
} else {
return null;
}
}
async _call(data) {
const { query } = data;
try {
const searchOption = {
queryType: this.queryType,
top: this.top,
};
if (this.select) {
searchOption.select = this.select;
}
const searchResults = await this.client.search(query, searchOption);
const resultDocuments = [];
for await (const result of searchResults.results) {
resultDocuments.push(result.document);
}
return JSON.stringify(resultDocuments);
} catch (error) {
console.error(`Azure Cognitive Search request failed: ${error}`);
return 'There was an error with Azure Cognitive Search.';
}
}
}
module.exports = AzureCognitiveSearch;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,182 @@
const { z } = require('zod');
const path = require('path');
const OpenAI = require('openai');
const { v4: uuidv4 } = require('uuid');
const { Tool } = require('langchain/tools');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { FileContext } = require('librechat-data-provider');
const { getImageBasename } = require('~/server/services/Files/images');
const extractBaseURL = require('~/utils/extractBaseURL');
const { logger } = require('~/config');
class DALLE3 extends Tool {
constructor(fields = {}) {
super();
/** @type {boolean} Used to initialize the Tool without necessary variables. */
this.override = fields.override ?? false;
/** @type {boolean} Necessary for output to contain all image metadata. */
this.returnMetadata = fields.returnMetadata ?? false;
this.userId = fields.userId;
this.fileStrategy = fields.fileStrategy;
if (fields.processFileURL) {
/** @type {processFileURL} Necessary for output to contain all image metadata. */
this.processFileURL = fields.processFileURL.bind(this);
}
let apiKey = fields.DALLE3_API_KEY ?? fields.DALLE_API_KEY ?? this.getApiKey();
const config = { apiKey };
if (process.env.DALLE_REVERSE_PROXY) {
config.baseURL = extractBaseURL(process.env.DALLE_REVERSE_PROXY);
}
if (process.env.DALLE3_AZURE_API_VERSION && process.env.DALLE3_BASEURL) {
config.baseURL = process.env.DALLE3_BASEURL;
config.defaultQuery = { 'api-version': process.env.DALLE3_AZURE_API_VERSION };
config.defaultHeaders = {
'api-key': process.env.DALLE3_API_KEY,
'Content-Type': 'application/json',
};
config.apiKey = process.env.DALLE3_API_KEY;
}
if (process.env.PROXY) {
config.httpAgent = new HttpsProxyAgent(process.env.PROXY);
}
/** @type {OpenAI} */
this.openai = new OpenAI(config);
this.name = 'dalle';
this.description = `Use DALLE to create images from text descriptions.
- It requires prompts to be in English, detailed, and to specify image type and human features for diversity.
- Create only one image, without repeating or listing descriptions outside the "prompts" field.
- Maintains the original intent of the description, with parameters for image style, quality, and size to tailor the output.`;
this.description_for_model =
process.env.DALLE3_SYSTEM_PROMPT ??
`// Whenever a description of an image is given, generate prompts (following these rules), and use dalle to create the image. If the user does not ask for a specific number of images, default to creating 2 prompts to send to dalle that are written to be as diverse as possible. All prompts sent to dalle must abide by the following policies:
// 1. Prompts must be in English. Translate to English if needed.
// 2. One image per function call. Create only 1 image per request unless explicitly told to generate more than 1 image.
// 3. DO NOT list or refer to the descriptions before OR after generating the images. They should ONLY ever be written out ONCE, in the \`"prompts"\` field of the request. You do not need to ask for permission to generate, just do it!
// 4. Always mention the image type (photo, oil painting, watercolor painting, illustration, cartoon, drawing, vector, render, etc.) at the beginning of the caption. Unless the captions suggests otherwise, make one of the images a photo.
// 5. Diversify depictions of ALL images with people to always include always DESCENT and GENDER for EACH person using direct terms. Adjust only human descriptions.
// - EXPLICITLY specify these attributes, not abstractly reference them. The attributes should be specified in a minimal way and should directly describe their physical form.
// - Your choices should be grounded in reality. For example, all of a given OCCUPATION should not be the same gender or race. Additionally, focus on creating diverse, inclusive, and exploratory scenes via the properties you choose during rewrites. Make choices that may be insightful or unique sometimes.
// - Use "various" or "diverse" ONLY IF the description refers to groups of more than 3 people. Do not change the number of people requested in the original description.
// - Don't alter memes, fictional character origins, or unseen people. Maintain the original prompt's intent and prioritize quality.
// The prompt must intricately describe every part of the image in concrete, objective detail. THINK about what the end goal of the description is, and extrapolate that to what would make satisfying images.
// All descriptions sent to dalle should be a paragraph of text that is extremely descriptive and detailed. Each should be more than 3 sentences long.
// - The "vivid" style is HIGHLY preferred, but "natural" is also supported.`;
this.schema = z.object({
prompt: z
.string()
.max(4000)
.describe(
'A text description of the desired image, following the rules, up to 4000 characters.',
),
style: z
.enum(['vivid', 'natural'])
.describe(
'Must be one of `vivid` or `natural`. `vivid` generates hyper-real and dramatic images, `natural` produces more natural, less hyper-real looking images',
),
quality: z
.enum(['hd', 'standard'])
.describe('The quality of the generated image. Only `hd` and `standard` are supported.'),
size: z
.enum(['1024x1024', '1792x1024', '1024x1792'])
.describe(
'The size of the requested image. Use 1024x1024 (square) as the default, 1792x1024 if the user requests a wide image, and 1024x1792 for full-body portraits. Always include this parameter in the request.',
),
});
}
getApiKey() {
const apiKey = process.env.DALLE3_API_KEY ?? process.env.DALLE_API_KEY ?? '';
if (!apiKey && !this.override) {
throw new Error('Missing DALLE_API_KEY environment variable.');
}
return apiKey;
}
replaceUnwantedChars(inputString) {
return inputString
.replace(/\r\n|\r|\n/g, ' ')
.replace(/"/g, '')
.trim();
}
wrapInMarkdown(imageUrl) {
return `![generated image](${imageUrl})`;
}
async _call(data) {
const { prompt, quality = 'standard', size = '1024x1024', style = 'vivid' } = data;
if (!prompt) {
throw new Error('Missing required field: prompt');
}
let resp;
try {
resp = await this.openai.images.generate({
model: 'dall-e-3',
quality,
style,
size,
prompt: this.replaceUnwantedChars(prompt),
n: 1,
});
} catch (error) {
logger.error('[DALL-E-3] Problem generating the image:', error);
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 be unavailable';
}
const theImageUrl = resp.data[0].url;
if (!theImageUrl) {
return 'No image URL returned from OpenAI API. There may be a problem with the API or your configuration.';
}
const imageBasename = getImageBasename(theImageUrl);
const imageExt = path.extname(imageBasename);
const extension = imageExt.startsWith('.') ? imageExt.slice(1) : imageExt;
const imageName = `img-${uuidv4()}.${extension}`;
logger.debug('[DALL-E-3]', {
imageName,
imageBasename,
imageExt,
extension,
theImageUrl,
data: resp.data[0],
});
try {
const result = await this.processFileURL({
fileStrategy: this.fileStrategy,
userId: this.userId,
URL: theImageUrl,
fileName: imageName,
basePath: 'images',
context: FileContext.image_generation,
});
if (this.returnMetadata) {
this.result = result;
} else {
this.result = this.wrapInMarkdown(result.filepath);
}
} catch (error) {
logger.error('Error while saving the image:', error);
this.result = `Failed to save the image locally. ${error.message}`;
}
return this.result;
}
}
module.exports = DALLE3;

View File

@@ -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`,

View File

@@ -0,0 +1,65 @@
const { z } = require('zod');
const { Tool } = require('@langchain/core/tools');
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
class GoogleSearchResults extends Tool {
static lc_name() {
return 'GoogleSearchResults';
}
constructor(fields = {}) {
super(fields);
this.envVarApiKey = 'GOOGLE_SEARCH_API_KEY';
this.envVarSearchEngineId = 'GOOGLE_CSE_ID';
this.override = fields.override ?? false;
this.apiKey = fields.apiKey ?? getEnvironmentVariable(this.envVarApiKey);
this.searchEngineId =
fields.searchEngineId ?? getEnvironmentVariable(this.envVarSearchEngineId);
this.kwargs = fields?.kwargs ?? {};
this.name = 'google';
this.description =
'A search engine optimized for comprehensive, accurate, and trusted results. Useful for when you need to answer questions about current events.';
this.schema = z.object({
query: z.string().min(1).describe('The search query string.'),
max_results: z
.number()
.min(1)
.max(10)
.optional()
.describe('The maximum number of search results to return. Defaults to 10.'),
// Note: Google API has its own parameters for search customization, adjust as needed.
});
}
async _call(input) {
const validationResult = this.schema.safeParse(input);
if (!validationResult.success) {
throw new Error(`Validation failed: ${JSON.stringify(validationResult.error.issues)}`);
}
const { query, max_results = 5 } = validationResult.data;
const response = await fetch(
`https://www.googleapis.com/customsearch/v1?key=${this.apiKey}&cx=${
this.searchEngineId
}&q=${encodeURIComponent(query)}&num=${max_results}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
);
const json = await response.json();
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}: ${json.error.message}`);
}
return JSON.stringify(json);
}
}
module.exports = GoogleSearchResults;

View File

@@ -1,14 +1,31 @@
// 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 { v4: uuidv4 } = require('uuid');
const { StructuredTool } = require('langchain/tools');
const { FileContext } = require('librechat-data-provider');
const paths = require('~/config/paths');
const { logger } = require('~/config');
class StableDiffusionAPI extends StructuredTool {
constructor(fields) {
super();
/** @type {string} User ID */
this.userId = fields.userId;
/** @type {Express.Request | undefined} Express Request object, only provided by ToolService */
this.req = fields.req;
/** @type {boolean} Used to initialize the Tool without necessary variables. */
this.override = fields.override ?? false;
/** @type {boolean} Necessary for output to contain all image metadata. */
this.returnMetadata = fields.returnMetadata ?? false;
if (fields.uploadImageBuffer) {
/** @type {uploadImageBuffer} Necessary for output to contain all image metadata. */
this.uploadImageBuffer = fields.uploadImageBuffer.bind(this);
}
this.name = 'stable-diffusion';
this.url = fields.SD_WEBUI_URL || this.getServerURL();
this.description_for_model = `// Generate images and visuals using text.
@@ -43,7 +60,7 @@ class StableDiffusionAPI extends StructuredTool {
getMarkdownImageUrl(imageName) {
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.join(this.relativePath, this.userId, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
@@ -51,7 +68,7 @@ class StableDiffusionAPI extends StructuredTool {
getServerURL() {
const url = process.env.SD_WEBUI_URL || '';
if (!url) {
if (!url && !this.override) {
throw new Error('Missing SD_WEBUI_URL environment variable.');
}
return url;
@@ -63,52 +80,78 @@ class StableDiffusionAPI extends StructuredTool {
const payload = {
prompt,
negative_prompt,
sampler_index: 'DPM++ 2M Karras',
cfg_scale: 4.5,
steps: 22,
width: 1024,
height: 1024,
};
const response = await axios.post(`${url}/sdapi/v1/txt2img`, payload);
const image = response.data.images[0];
const pngPayload = { image: `data:image/png;base64,${image}` };
const response2 = await axios.post(`${url}/sdapi/v1/png-info`, pngPayload);
const info = response2.data.info;
let generationResponse;
try {
generationResponse = await axios.post(`${url}/sdapi/v1/txt2img`, payload);
} catch (error) {
logger.error('[StableDiffusion] Error while generating image:', error);
return 'Error making API request.';
}
const image = generationResponse.data.images[0];
// Generate unique name
const imageName = `${Date.now()}.png`;
this.outputPath = path.resolve(
__dirname,
'..',
'..',
'..',
'..',
'..',
'client',
'public',
'images',
);
const appRoot = path.resolve(__dirname, '..', '..', '..', '..', '..', 'client');
this.relativeImageUrl = path.relative(appRoot, this.outputPath);
/** @type {{ height: number, width: number, seed: number, infotexts: string[] }} */
let info = {};
try {
info = JSON.parse(generationResponse.data.info);
} catch (error) {
logger.error('[StableDiffusion] Error while getting image metadata:', error);
}
// Check if directory exists, if not create it
if (!fs.existsSync(this.outputPath)) {
fs.mkdirSync(this.outputPath, { recursive: true });
const file_id = uuidv4();
const imageName = `${file_id}.png`;
const { imageOutput: imageOutputPath, clientPath } = paths;
const filepath = path.join(imageOutputPath, this.userId, imageName);
this.relativePath = path.relative(clientPath, imageOutputPath);
if (!fs.existsSync(path.join(imageOutputPath, this.userId))) {
fs.mkdirSync(path.join(imageOutputPath, this.userId), { recursive: true });
}
try {
const buffer = Buffer.from(image.split(',', 1)[0], 'base64');
if (this.returnMetadata && this.uploadImageBuffer && this.req) {
const file = await this.uploadImageBuffer({
req: this.req,
context: FileContext.image_generation,
resize: false,
metadata: {
buffer,
height: info.height,
width: info.width,
bytes: Buffer.byteLength(buffer),
filename: imageName,
type: 'image/png',
file_id,
},
});
const generationInfo = info.infotexts[0].split('\n').pop();
return {
...file,
prompt,
metadata: {
negative_prompt,
seed: info.seed,
info: generationInfo,
},
};
}
await sharp(buffer)
.withMetadata({
iptcpng: {
parameters: info,
parameters: info.infotexts[0],
},
})
.toFile(this.outputPath + '/' + imageName);
.toFile(filepath);
this.result = this.getMarkdownImageUrl(imageName);
} catch (error) {
console.error('Error while saving the image:', error);
// this.result = theImageUrl;
logger.error('[StableDiffusion] Error while saving the image:', error);
}
return this.result;

View File

@@ -0,0 +1,92 @@
const { z } = require('zod');
const { Tool } = require('@langchain/core/tools');
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
class TavilySearchResults extends Tool {
static lc_name() {
return 'TavilySearchResults';
}
constructor(fields = {}) {
super(fields);
this.envVar = 'TAVILY_API_KEY';
/* Used to initialize the Tool without necessary variables. */
this.override = fields.override ?? false;
this.apiKey = fields.apiKey ?? this.getApiKey();
this.kwargs = fields?.kwargs ?? {};
this.name = 'tavily_search_results_json';
this.description =
'A search engine optimized for comprehensive, accurate, and trusted results. Useful for when you need to answer questions about current events.';
this.schema = z.object({
query: z.string().min(1).describe('The search query string.'),
max_results: z
.number()
.min(1)
.max(10)
.optional()
.describe('The maximum number of search results to return. Defaults to 5.'),
search_depth: z
.enum(['basic', 'advanced'])
.optional()
.describe(
'The depth of the search, affecting result quality and response time (`basic` or `advanced`). Default is basic for quick results and advanced for indepth high quality results but longer response time. Advanced calls equals 2 requests.',
),
include_images: z
.boolean()
.optional()
.describe(
'Whether to include a list of query-related images in the response. Default is False.',
),
include_answer: z
.boolean()
.optional()
.describe('Whether to include answers in the search results. Default is False.'),
// include_raw_content: z.boolean().optional().describe('Whether to include raw content in the search results. Default is False.'),
// include_domains: z.array(z.string()).optional().describe('A list of domains to specifically include in the search results.'),
// exclude_domains: z.array(z.string()).optional().describe('A list of domains to specifically exclude from the search results.'),
});
}
getApiKey() {
const apiKey = getEnvironmentVariable(this.envVar);
if (!apiKey && !this.override) {
throw new Error(`Missing ${this.envVar} environment variable.`);
}
return apiKey;
}
async _call(input) {
const validationResult = this.schema.safeParse(input);
if (!validationResult.success) {
throw new Error(`Validation failed: ${JSON.stringify(validationResult.error.issues)}`);
}
const { query, ...rest } = validationResult.data;
const requestBody = {
api_key: this.apiKey,
query,
...rest,
...this.kwargs,
};
const response = await fetch('https://api.tavily.com/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
const json = await response.json();
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}: ${json.error}`);
}
return JSON.stringify(json);
}
}
module.exports = TavilySearchResults;

View File

@@ -0,0 +1,89 @@
const { z } = require('zod');
const { Tool } = require('@langchain/core/tools');
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
const { logger } = require('~/config');
/**
* Tool for the Traversaal AI search API, Ares.
*/
class TraversaalSearch extends Tool {
static lc_name() {
return 'TraversaalSearch';
}
constructor(fields) {
super(fields);
this.name = 'traversaal_search';
this.description = `An AI search engine optimized for comprehensive, accurate, and trusted results.
Useful for when you need to answer questions about current events. Input should be a search query.`;
this.description_for_model =
'\'Please create a specific sentence for the AI to understand and use as a query to search the web based on the user\'s request. For example, "Find information about the highest mountains in the world." or "Show me the latest news articles about climate change and its impact on polar ice caps."\'';
this.schema = z.object({
query: z
.string()
.describe(
'A properly written sentence to be interpreted by an AI to search the web according to the user\'s request.',
),
});
this.apiKey = fields?.TRAVERSAAL_API_KEY ?? this.getApiKey();
}
getApiKey() {
const apiKey = getEnvironmentVariable('TRAVERSAAL_API_KEY');
if (!apiKey && this.override) {
throw new Error(
'No Traversaal API key found. Either set an environment variable named "TRAVERSAAL_API_KEY" or pass an API key as "apiKey".',
);
}
return apiKey;
}
// eslint-disable-next-line no-unused-vars
async _call({ query }, _runManager) {
const body = {
query: [query],
};
try {
const response = await fetch('https://api-ares.traversaal.ai/live/predict', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': this.apiKey,
},
body: JSON.stringify({ ...body }),
});
const json = await response.json();
if (!response.ok) {
throw new Error(
`Request failed with status code ${response.status}: ${json.error ?? json.message}`,
);
}
if (!json.data) {
throw new Error('Could not parse Traversaal API results. Please try again.');
}
const baseText = json.data?.response_text ?? '';
const sources = json.data?.web_url;
const noResponse = 'No response found in Traversaal API results';
if (!baseText && !sources) {
return noResponse;
}
const sourcesText = sources?.length ? '\n\nSources:\n - ' + sources.join('\n - ') : '';
const result = baseText + sourcesText;
if (!result) {
return noResponse;
}
return result;
} catch (error) {
logger.error('Traversaal API request failed', error);
return `Traversaal API request failed: ${error.message}`;
}
}
}
module.exports = TraversaalSearch;

View File

@@ -1,11 +1,15 @@
/* 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) {
super();
/* Used to initialize the Tool without necessary variables. */
this.override = fields.override ?? false;
this.name = 'wolfram';
this.apiKey = fields.WOLFRAM_APP_ID || this.getAppId();
this.description_for_model = `// Access dynamic computation and curated data from WolframAlpha and Wolfram Cloud.
@@ -47,14 +51,14 @@ 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;
}
}
getAppId() {
const appId = process.env.WOLFRAM_APP_ID || '';
if (!appId) {
if (!appId && !this.override) {
throw new Error('Missing WOLFRAM_APP_ID environment variable.');
}
return appId;
@@ -78,11 +82,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.';
}
}

View File

@@ -0,0 +1,212 @@
const OpenAI = require('openai');
const DALLE3 = require('../DALLE3');
const { logger } = require('~/config');
jest.mock('openai');
const processFileURL = 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: {
generate,
},
}));
jest.mock('fs', () => {
return {
existsSync: jest.fn(),
mkdirSync: jest.fn(),
};
});
jest.mock('path', () => {
return {
resolve: jest.fn(),
join: jest.fn(),
relative: jest.fn(),
extname: jest.fn().mockImplementation((filename) => {
return filename.slice(filename.lastIndexOf('.'));
}),
};
});
describe('DALLE3', () => {
let originalEnv;
let dalle; // Keep this declaration if you need to use dalle in other tests
const mockApiKey = 'mock_api_key';
beforeAll(() => {
// Save the original process.env
originalEnv = { ...process.env };
});
beforeEach(() => {
// Reset the process.env before each test
jest.resetModules();
process.env = { ...originalEnv, DALLE_API_KEY: mockApiKey };
// Instantiate DALLE3 for tests that do not depend on DALLE3_SYSTEM_PROMPT
dalle = new DALLE3({ processFileURL });
});
afterEach(() => {
jest.clearAllMocks();
// Restore the original process.env after each test
process.env = originalEnv;
});
it('should throw an error if all potential API keys are missing', () => {
delete process.env.DALLE3_API_KEY;
delete process.env.DALLE_API_KEY;
expect(() => new DALLE3()).toThrow('Missing DALLE_API_KEY environment variable.');
});
it('should replace unwanted characters in input string', () => {
const input = 'This is a test\nstring with "quotes" and new lines.';
const expectedOutput = 'This is a test string with quotes and new lines.';
expect(dalle.replaceUnwantedChars(input)).toBe(expectedOutput);
});
it('should generate markdown image URL correctly', () => {
const imageName = 'test.png';
const markdownImage = dalle.wrapInMarkdown(imageName);
expect(markdownImage).toBe('![generated image](test.png)');
});
it('should call OpenAI API with correct parameters', async () => {
const mockData = {
prompt: 'A test prompt',
quality: 'standard',
size: '1024x1024',
style: 'vivid',
};
const mockResponse = {
data: [
{
url: 'http://example.com/img-test.png',
},
],
};
generate.mockResolvedValue(mockResponse);
processFileURL.mockResolvedValue({
filepath: 'http://example.com/img-test.png',
});
const result = await dalle._call(mockData);
expect(generate).toHaveBeenCalledWith({
model: 'dall-e-3',
quality: mockData.quality,
style: mockData.style,
size: mockData.size,
prompt: mockData.prompt,
n: 1,
});
expect(result).toContain('![generated image]');
});
it('should use the system prompt if provided', () => {
process.env.DALLE3_SYSTEM_PROMPT = 'System prompt for testing';
jest.resetModules(); // This will ensure the module is fresh and will read the new env var
const DALLE3 = require('../DALLE3'); // Re-require after setting the env var
const dalleWithSystemPrompt = new DALLE3();
expect(dalleWithSystemPrompt.description_for_model).toBe('System prompt for testing');
});
it('should not use the system prompt if not provided', async () => {
delete process.env.DALLE3_SYSTEM_PROMPT;
const dalleWithoutSystemPrompt = new DALLE3();
expect(dalleWithoutSystemPrompt.description_for_model).not.toBe('System prompt for testing');
});
it('should throw an error if prompt is missing', async () => {
const mockData = {
quality: 'standard',
size: '1024x1024',
style: 'vivid',
};
await expect(dalle._call(mockData)).rejects.toThrow('Missing required field: prompt');
});
it('should log appropriate debug values', async () => {
const mockData = {
prompt: 'A test prompt',
};
const mockResponse = {
data: [
{
url: 'http://example.com/invalid-url',
},
],
};
generate.mockResolvedValue(mockResponse);
await dalle._call(mockData);
expect(logger.debug).toHaveBeenCalledWith('[DALL-E-3]', {
data: { url: 'http://example.com/invalid-url' },
theImageUrl: 'http://example.com/invalid-url',
extension: expect.any(String),
imageBasename: expect.any(String),
imageExt: expect.any(String),
imageName: expect.any(String),
});
});
it('should log an error and return the image URL if there is an error saving the image', async () => {
const mockData = {
prompt: 'A test prompt',
};
const mockResponse = {
data: [
{
url: 'http://example.com/img-test.png',
},
],
};
const error = new Error('Error while saving the image');
generate.mockResolvedValue(mockResponse);
processFileURL.mockRejectedValue(error);
const result = await dalle._call(mockData);
expect(logger.error).toHaveBeenCalledWith('Error while saving the image:', error);
expect(result).toBe('Failed to save the image locally. Error while saving the image');
});
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);
processFileURL.mockRejectedValue(error);
const result = await dalle._call(mockData);
expect(logger.error).toHaveBeenCalledWith('Error while saving the image:', error);
expect(result).toContain('Failed to save the image');
});
});

View File

@@ -0,0 +1,31 @@
const OpenAI = require('openai');
const { logger } = require('~/config');
/**
* Handles errors that may occur when making requests to OpenAI's API.
* It checks the instance of the error and prints a specific warning message
* to the console depending on the type of error encountered.
* It then calls an optional error callback function with the error object.
*
* @param {Error} err - The error object thrown by OpenAI API.
* @param {Function} errorCallback - A callback function that is called with the error object.
* @param {string} [context='stream'] - A string providing context where the error occurred, defaults to 'stream'.
*/
async function handleOpenAIErrors(err, errorCallback, context = 'stream') {
if (err instanceof OpenAI.APIError && err?.message?.includes('abort')) {
logger.warn(`[OpenAIClient.chatCompletion][${context}] Aborted Message`);
}
if (err instanceof OpenAI.OpenAIError && err?.message?.includes('missing finish_reason')) {
logger.warn(`[OpenAIClient.chatCompletion][${context}] Missing finish_reason`);
} else if (err instanceof OpenAI.APIError) {
logger.warn(`[OpenAIClient.chatCompletion][${context}] API error`);
} else {
logger.warn(`[OpenAIClient.chatCompletion][${context}] Unhandled error type`);
}
if (errorCallback) {
errorCallback(err);
}
}
module.exports = handleOpenAIErrors;

View File

@@ -1,30 +1,32 @@
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,
CodeInterpreter,
AIPluginTool,
// Basic Tools
CodeBrew,
AzureAISearch,
GoogleSearchAPI,
WolframAlphaAPI,
StructuredWolfram,
HttpRequestTool,
OpenAICreateImage,
StableDiffusionAPI,
StructuredSD,
AzureCognitiveSearch,
StructuredACS,
// Structured Tools
DALLE3,
E2BTools,
CodeSherpa,
StructuredSD,
StructuredACS,
CodeSherpaTools,
CodeBrew,
TraversaalSearch,
StructuredWolfram,
TavilySearchResults,
} = 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;
@@ -32,6 +34,14 @@ const getOpenAIKey = async (options, user) => {
return openAIApiKey || (await getUserPluginAuthValue(user, 'OPENAI_API_KEY'));
};
/**
* Validates the availability and authentication of tools for a user based on environment variables or user-specific plugin authentication values.
* Tools without required authentication or with valid authentication are considered valid.
*
* @param {Object} user The user object for whom to validate tool access.
* @param {Array<string>} tools An array of tool identifiers to validate. Defaults to an empty array.
* @returns {Promise<Array<string>>} A promise that resolves to an array of valid tool identifiers.
*/
const validateTools = async (user, tools = []) => {
try {
const validToolsSet = new Set(tools);
@@ -39,16 +49,34 @@ const validateTools = async (user, tools = []) => {
validToolsSet.has(tool.pluginKey),
);
/**
* Validates the credentials for a given auth field or set of alternate auth fields for a tool.
* If valid admin or user authentication is found, the function returns early. Otherwise, it removes the tool from the set of valid tools.
*
* @param {string} authField The authentication field or fields (separated by "||" for alternates) to validate.
* @param {string} toolName The identifier of the tool being validated.
*/
const validateCredentials = async (authField, toolName) => {
const adminAuth = process.env[authField];
if (adminAuth && adminAuth.length > 0) {
return;
const fields = authField.split('||');
for (const field of fields) {
const adminAuth = process.env[field];
if (adminAuth && adminAuth.length > 0) {
return;
}
let userAuth = null;
try {
userAuth = await getUserPluginAuthValue(user, field);
} catch (err) {
if (field === fields[fields.length - 1] && !userAuth) {
throw err;
}
}
if (userAuth && userAuth.length > 0) {
return;
}
}
const userAuth = await getUserPluginAuthValue(user, authField);
if (userAuth && userAuth.length > 0) {
return;
}
validToolsSet.delete(toolName);
};
@@ -64,24 +92,59 @@ const validateTools = async (user, tools = []) => {
return Array.from(validToolsSet.values());
} catch (err) {
console.log('There was a problem validating tools', err);
throw new Error(err);
logger.error('[validateTools] There was a problem validating tools', err);
throw new Error('There was a problem validating tools');
}
};
const loadToolWithAuth = async (user, authFields, ToolConstructor, options = {}) => {
/**
* Initializes a tool with authentication values for the given user, supporting alternate authentication fields.
* Authentication fields can have alternates separated by "||", and the first defined variable will be used.
*
* @param {string} userId The user ID for which the tool is being loaded.
* @param {Array<string>} authFields Array of strings representing the authentication fields. Supports alternate fields delimited by "||".
* @param {typeof import('langchain/tools').Tool} ToolConstructor The constructor function for the tool to be initialized.
* @param {Object} options Optional parameters to be passed to the tool constructor alongside authentication values.
* @returns {Function} An Async function that, when called, asynchronously initializes and returns an instance of the tool with authentication.
*/
const loadToolWithAuth = (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);
/**
* Finds the first non-empty value for the given authentication field, supporting alternate fields.
* @param {string[]} fields Array of strings representing the authentication fields. Supports alternate fields delimited by "||".
* @returns {Promise<{ authField: string, authValue: string} | null>} An object containing the authentication field and value, or null if not found.
*/
const findAuthValue = async (fields) => {
for (const field of fields) {
let value = process.env[field];
if (value) {
return { authField: field, authValue: value };
}
try {
value = await getUserPluginAuthValue(userId, field);
} catch (err) {
if (field === fields[fields.length - 1] && !value) {
throw err;
}
}
if (value) {
return { authField: field, authValue: value };
}
}
return null;
};
for (let authField of authFields) {
const fields = authField.split('||');
const result = await findAuthValue(fields);
if (result) {
authValues[result.authField] = result.authValue;
}
authValues[authField] = authValue;
}
return new ToolConstructor({ ...options, ...authValues });
return new ToolConstructor({ ...options, ...authValues, userId });
};
};
@@ -92,16 +155,18 @@ const loadTools = async ({
returnMap = false,
tools = [],
options = {},
skipSpecs = false,
}) => {
const toolConstructors = {
tavily_search_results_json: TavilySearchResults,
calculator: Calculator,
codeinterpreter: CodeInterpreter,
google: GoogleSearchAPI,
wolfram: functions ? StructuredWolfram : WolframAlphaAPI,
'dall-e': OpenAICreateImage,
'stable-diffusion': functions ? StructuredSD : StableDiffusionAPI,
'azure-cognitive-search': functions ? StructuredACS : AzureCognitiveSearch,
'azure-ai-search': functions ? StructuredACS : AzureAISearch,
CodeBrew: CodeBrew,
traversaal_search: TraversaalSearch,
};
const openAIApiKey = await getOpenAIKey(options, user);
@@ -162,25 +227,28 @@ 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 = {};
if (functions) {
toolConstructors.dalle = DALLE3;
toolConstructors.codesherpa = CodeSherpa;
}
const imageGenOptions = {
req: options.req,
fileStrategy: options.fileStrategy,
processFileURL: options.processFileURL,
returnMetadata: options.returnMetadata,
uploadImageBuffer: options.uploadImageBuffer,
};
const toolOptions = {
serpapi: { location: 'Austin,Texas,United States', hl: 'en', gl: 'us' },
dalle: imageGenOptions,
'dall-e': imageGenOptions,
'stable-diffusion': imageGenOptions,
};
const toolAuthFields = {};
@@ -203,7 +271,7 @@ const loadTools = async ({
if (toolConstructors[tool]) {
const options = toolOptions[tool] || {};
const toolInstance = await loadToolWithAuth(
const toolInstance = loadToolWithAuth(
user,
toolAuthFields[tool],
toolConstructors[tool],
@@ -219,7 +287,7 @@ const loadTools = async ({
}
let specs = null;
if (functions && remainingTools.length > 0) {
if (functions && remainingTools.length > 0 && skipSpecs !== true) {
specs = await loadSpecs({
llm: model,
user,
@@ -246,6 +314,9 @@ const loadTools = async ({
let result = [];
for (const tool of tools) {
const validTool = requestedTools[tool];
if (!validTool) {
continue;
}
const plugin = await validTool();
if (Array.isArray(plugin)) {
@@ -259,6 +330,7 @@ const loadTools = async ({
};
module.exports = {
loadToolWithAuth,
validateTools,
loadTools,
};

View File

@@ -4,26 +4,33 @@ const mockUser = {
findByIdAndDelete: jest.fn(),
};
var mockPluginService = {
const mockPluginService = {
updateUserPluginAuth: jest.fn(),
deleteUserPluginAuth: jest.fn(),
getUserPluginAuthValue: jest.fn(),
};
jest.mock('../../../../models/User', () => {
jest.mock('~/models/User', () => {
return function () {
return mockUser;
};
});
jest.mock('../../../../server/services/PluginService', () => mockPluginService);
jest.mock('~/server/services/PluginService', () => mockPluginService);
const User = require('../../../../models/User');
const { validateTools, loadTools } = require('./');
const PluginService = require('../../../../server/services/PluginService');
const { BaseChatModel } = require('langchain/chat_models/openai');
const { Calculator } = require('langchain/tools/calculator');
const { availableTools, OpenAICreateImage, GoogleSearchAPI, StructuredSD } = require('../');
const { BaseChatModel } = require('langchain/chat_models/openai');
const User = require('~/models/User');
const PluginService = require('~/server/services/PluginService');
const { validateTools, loadTools, loadToolWithAuth } = require('./handleTools');
const {
availableTools,
OpenAICreateImage,
GoogleSearchAPI,
StructuredSD,
WolframAlphaAPI,
} = require('../');
describe('Tool Handlers', () => {
let fakeUser;
@@ -44,7 +51,10 @@ describe('Tool Handlers', () => {
});
mockPluginService.updateUserPluginAuth.mockImplementation(
(userId, authField, _pluginKey, credential) => {
userAuthValues[`${userId}-${authField}`] = credential;
const fields = authField.split('||');
fields.forEach((field) => {
userAuthValues[`${userId}-${field}`] = credential;
});
},
);
@@ -53,6 +63,7 @@ describe('Tool Handlers', () => {
username: 'fakeuser',
email: 'fakeuser@example.com',
emailVerified: false,
// file deepcode ignore NoHardcodedPasswords/test: fake value
password: 'fakepassword123',
avatar: '',
provider: 'local',
@@ -133,6 +144,18 @@ describe('Tool Handlers', () => {
loadTool2 = toolFunctions[sampleTools[1]];
loadTool3 = toolFunctions[sampleTools[2]];
});
let originalEnv;
beforeEach(() => {
originalEnv = process.env;
process.env = { ...originalEnv };
});
afterEach(() => {
process.env = originalEnv;
});
it('returns the expected load functions for requested tools', async () => {
expect(loadTool1).toBeDefined();
expect(loadTool2).toBeDefined();
@@ -149,6 +172,86 @@ describe('Tool Handlers', () => {
expect(authTool).toBeInstanceOf(ToolClass);
expect(tool).toBeInstanceOf(ToolClass2);
});
it('should initialize an authenticated tool with primary auth field', async () => {
process.env.DALLE2_API_KEY = 'mocked_api_key';
const initToolFunction = loadToolWithAuth(
'userId',
['DALLE2_API_KEY||DALLE_API_KEY'],
ToolClass,
);
const authTool = await initToolFunction();
expect(authTool).toBeInstanceOf(ToolClass);
expect(mockPluginService.getUserPluginAuthValue).not.toHaveBeenCalled();
});
it('should initialize an authenticated tool with alternate auth field when primary is missing', async () => {
delete process.env.DALLE2_API_KEY; // Ensure the primary key is not set
process.env.DALLE_API_KEY = 'mocked_alternate_api_key';
const initToolFunction = loadToolWithAuth(
'userId',
['DALLE2_API_KEY||DALLE_API_KEY'],
ToolClass,
);
const authTool = await initToolFunction();
expect(authTool).toBeInstanceOf(ToolClass);
expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledTimes(1);
expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledWith(
'userId',
'DALLE2_API_KEY',
);
});
it('should fallback to getUserPluginAuthValue when env vars are missing', async () => {
mockPluginService.updateUserPluginAuth('userId', 'DALLE_API_KEY', 'dalle', 'mocked_api_key');
const initToolFunction = loadToolWithAuth(
'userId',
['DALLE2_API_KEY||DALLE_API_KEY'],
ToolClass,
);
const authTool = await initToolFunction();
expect(authTool).toBeInstanceOf(ToolClass);
expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledTimes(2);
});
it('should initialize an authenticated tool with singular auth field', async () => {
process.env.WOLFRAM_APP_ID = 'mocked_app_id';
const initToolFunction = loadToolWithAuth('userId', ['WOLFRAM_APP_ID'], WolframAlphaAPI);
const authTool = await initToolFunction();
expect(authTool).toBeInstanceOf(WolframAlphaAPI);
expect(mockPluginService.getUserPluginAuthValue).not.toHaveBeenCalled();
});
it('should initialize an authenticated tool when env var is set', async () => {
process.env.WOLFRAM_APP_ID = 'mocked_app_id';
const initToolFunction = loadToolWithAuth('userId', ['WOLFRAM_APP_ID'], WolframAlphaAPI);
const authTool = await initToolFunction();
expect(authTool).toBeInstanceOf(WolframAlphaAPI);
expect(mockPluginService.getUserPluginAuthValue).not.toHaveBeenCalledWith(
'userId',
'WOLFRAM_APP_ID',
);
});
it('should fallback to getUserPluginAuthValue when singular env var is missing', async () => {
delete process.env.WOLFRAM_APP_ID; // Ensure the environment variable is not set
mockPluginService.getUserPluginAuthValue.mockResolvedValue('mocked_user_auth_value');
const initToolFunction = loadToolWithAuth('userId', ['WOLFRAM_APP_ID'], WolframAlphaAPI);
const authTool = await initToolFunction();
expect(authTool).toBeInstanceOf(WolframAlphaAPI);
expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledTimes(1);
expect(mockPluginService.getUserPluginAuthValue).toHaveBeenCalledWith(
'userId',
'WOLFRAM_APP_ID',
);
});
it('should throw an error for an unauthenticated tool', async () => {
try {
await loadTool2();

View File

@@ -1,6 +1,8 @@
const { validateTools, loadTools } = require('./handleTools');
const handleOpenAIErrors = require('./handleOpenAIErrors');
module.exports = {
handleOpenAIErrors,
validateTools,
loadTools,
};

View File

@@ -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;
}

View File

@@ -1,17 +1,49 @@
const { getUserPluginAuthValue } = require('../../../../server/services/PluginService');
const { getUserPluginAuthValue } = require('~/server/services/PluginService');
const { availableTools } = require('../');
const { logger } = require('~/config');
const loadToolSuite = async ({ pluginKey, tools, user, options }) => {
/**
* Loads a suite of tools with authentication values for a given user, supporting alternate authentication fields.
* Authentication fields can have alternates separated by "||", and the first defined variable will be used.
*
* @param {Object} params Parameters for loading the tool suite.
* @param {string} params.pluginKey Key identifying the plugin whose tools are to be loaded.
* @param {Array<Function>} params.tools Array of tool constructor functions.
* @param {Object} params.user User object for whom the tools are being loaded.
* @param {Object} [params.options={}] Optional parameters to be passed to each tool constructor.
* @returns {Promise<Array>} A promise that resolves to an array of instantiated tools.
*/
const loadToolSuite = async ({ pluginKey, tools, user, options = {} }) => {
const authConfig = availableTools.find((tool) => tool.pluginKey === pluginKey).authConfig;
const suite = [];
const authValues = {};
for (const auth of authConfig) {
let authValue = process.env[auth.authField];
if (!authValue) {
authValue = await getUserPluginAuthValue(user, auth.authField);
const findAuthValue = async (authField) => {
const fields = authField.split('||');
for (const field of fields) {
let value = process.env[field];
if (value) {
return value;
}
try {
value = await getUserPluginAuthValue(user, field);
if (value) {
return value;
}
} catch (err) {
logger.error(`Error fetching plugin auth value for ${field}: ${err.message}`);
}
}
return null;
};
for (const auth of authConfig) {
const authValue = await findAuthValue(auth.authField);
if (authValue !== null) {
authValues[auth.authField] = authValue;
} else {
logger.warn(`[loadToolSuite] No auth value found for ${auth.authField}`);
}
authValues[auth.authField] = authValue;
}
for (const tool of tools) {

View File

@@ -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;
};

View File

@@ -1,6 +1,9 @@
const Session = require('../models/Session');
const { ViolationTypes } = require('librechat-data-provider');
const { isEnabled, math, removePorts } = require('~/server/utils');
const getLogStores = require('./getLogStores');
const { isEnabled, math, removePorts } = require('../server/utils');
const Session = require('~/models/Session');
const { logger } = require('~/config');
const { BAN_VIOLATIONS, BAN_INTERVAL } = process.env ?? {};
const interval = math(BAN_INTERVAL, 20);
@@ -46,18 +49,25 @@ const banViolation = async (req, res, errorMessage) => {
await Session.deleteAllUserSessions(user_id);
res.clearCookie('refreshToken');
const banLogs = getLogStores('ban');
const duration = banLogs.opts.ttl;
const banLogs = getLogStores(ViolationTypes.BAN);
const duration = errorMessage.duration || banLogs.opts.ttl;
if (duration <= 0) {
return;
}
req.ip = removePorts(req);
console.log(`[BAN] Banning user ${user_id} @ ${req.ip} for ${duration / 1000 / 60} minutes`);
logger.info(
`[BAN] Banning user ${user_id} ${req.ip ? `@ ${req.ip} ` : ''}for ${
duration / 1000 / 60
} minutes`,
);
const expiresAt = Date.now() + duration;
await banLogs.set(user_id, { type, violation_count, duration, expiresAt });
await banLogs.set(req.ip, { type, user_id, violation_count, duration, expiresAt });
if (req.ip) {
await banLogs.set(req.ip, { type, user_id, violation_count, duration, expiresAt });
}
errorMessage.ban = true;
errorMessage.ban_duration = duration;

Some files were not shown because too many files have changed in this diff Show More