Compare commits

...

290 Commits

Author SHA1 Message Date
Danny Avila
94764c9c2a Release v0.5.6 (#723) 2023-07-28 13:51:41 -04:00
Fuegovic
e9c981c202 feat: add version number in UI (#704)
* feat: add version number in UI

* feat: add version number in UI

* feat: add version number in footer

* Update Footer.tsx

More concise, cleaner message

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-07-28 13:48:02 -04:00
Danny Avila
bec1d245bd fix(Eng.tsx): change 'New Chat' to 'New chat' for consistency with other translations 2023-07-28 13:42:39 -04:00
Danny Avila
131cb6cddb chore: remove no longer needed route for nonexistant method 2023-07-28 13:42:39 -04:00
Danny Avila
a2b6e9a6a8 fix(meilisearch): results will now properly paginate 2023-07-28 13:42:39 -04:00
Danny Avila
428fd5bed8 chore(mongoMeili.js): update console log messages for indexing in Meilisearch 2023-07-28 13:42:39 -04:00
Danny Avila
9cacf76c10 refactor(mongoMeili.js): remove console.log statement for document not indexed 2023-07-28 13:42:39 -04:00
Danny Avila
7b8036a369 fix(anthropic.js, gptPlugins.js, openAI.js): add error handling to abortMessage function calls 2023-07-28 13:42:39 -04:00
Danny Avila
d56817850c chore(convos.js): comment out console.log statement for debugging deletion source 2023-07-28 13:42:39 -04:00
Danny Avila
f88a0685f7 fix(db/indexSync.js): update import paths for Conversation and Message models
feat(db/indexSync.js): add synchronization logic between MongoDB collection and MeiliSearch index
fix(models/plugins/mongoMeili.js): update createMeiliMongooseModel function to remove unused parameters and add documentation for syncWithMeili method
2023-07-28 13:42:39 -04:00
Marco Beretta
ae51e6153f docs: Improved ngrok installation and enhanced user_auth_system.md (#721)
* Update ngrok.md

* Update user_auth_system.md

* Update user_auth_system.md

* Update user_auth_system.md
2023-07-28 13:40:47 -04:00
Danny Avila
745eef2eb0 feat: build dev images on changes to api/client or manually (#720) 2023-07-27 19:00:21 -04:00
Danny Avila
1f8520cdad wip: testing leaner docker strategy and deployment compose file (#719)
* nginx setup

* chore(dev-images.yml): update workflow trigger to push events on main branch
chore(dev-images.yml): remove building and pushing of librechat-dev-client image
chore(nginx.conf): comment out SSL configuration in nginx.conf
chore(deploy-compose.yml): uncomment api build configuration in deploy-compose.yml
chore(deploy-compose.yml): update client build configuration in deploy-compose.yml
2023-07-27 18:52:10 -04:00
Danny Avila
dae2805d27 chore(dev-images.yml): rename workflow to "Docker Dev Images Build" (#717)
chore(deploy-compose.yml): change port mapping from 9000:3080 to 3080:3080
2023-07-27 17:05:35 -04:00
Danny Avila
ba2e95db04 Update Dockerfile.multi 2023-07-27 16:50:33 -04:00
Danny Avila
2a6e000217 wip: testing dev image workflows and deployment setup (#716)
* chore(deploy-compose.yml): update API and client image references to use latest versions from ghcr.io
feat(deploy-compose.yml): add NODE_ENV environment variable with value 'production' for API service

* chore(dev-images.yml): tag and push latest images to container registry
chore(dev-images.yml): tag and push latest client image to container registry
chore(dev-images.yml): tag and push latest dev image to container registry
fix(Dockerfile.multi): fix CMD command to properly set NODE_ENV variable
2023-07-27 16:48:41 -04:00
Danny Avila
32281d1b8d wip: testing container workflows and deployment images (#715)
* feat: add Dockerfile.multi for building API, Client, and Data Provider

feat: add nginx.conf for client-side routing in Nginx

feat: add deploy-compose.yml for deploying the application with Docker Compose

chore: update version in deploy-compose.yml to 3.8

chore: remove unused configuration in docs/dev/deploy-compose.yml

* chore(Dockerfile.multi): Remove data-provider build stage
chore(deploy-compose.yml): Add NODE_ENV=production environment variable

* chore(Dockerfile.multi): add environment variable NODE_OPTIONS with value "--max-old-space-size=776"
feat(Dockerfile.multi): copy client build output to api build stage

* chore(Dockerfile.multi): update NODE_OPTIONS to increase max-old-space-size to 2048
chore(deploy-compose.yml): remove NODE_ENV=production environment variable

* feat(dev-images.yml): add GitHub Actions workflow for Docker multi-stage build on push to main branch
2023-07-27 16:24:06 -04:00
Danny Avila
369b1f4eba chore: remove data-provider and use npm package instead (#713)
* chore: remove data-provider, install npm package

* chore: replace monorepo package with npm package: librechat-data-provider

* chore: remove data-provider scripts

* chore: remove data-provider from .eslintrc.js
2023-07-27 14:49:47 -04:00
Danny Avila
777d64088b feat: stop-backend.js and update.js linux support (#712)
* chore(dependabot.yml): update target-branch from "develop" to "dev" for npm package updates in /api, /client, and root directory

* feat: stop-backend.js and update.js linux support (#701)

* feat: stop-backend.js and update.js linux support

* feat: update.js sudo support

* chore(helpers.js): add deleteNodeModules function
feat(packages.js): add script to delete node_modules and install dependencies
refactor(update.js): remove unnecessary imports and use deleteNodeModules function
feat(package.json): add update:linux script to update with sudo

* chore(package.json): rename 'update:linux'  script to 'update:sudo'

* refactor(update.js): simplify downCommand and buildCommand by removing redundant use of sudo command, add sudo to single docker command

---------

Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
2023-07-27 11:11:56 -04:00
Danny Avila
d59a3f20cb chore: linting 2023-07-27 10:32:23 -04:00
Danny Avila
8959576d75 fix(Settings/General): fix clear convos bug where active convo would still appear after clearing convos 2023-07-27 10:32:23 -04:00
Danny Avila
dd8bc39001 refactor(Nav/Conversation): reorganize imports and fix import paths 2023-07-27 10:32:23 -04:00
Danny Avila
4898f7489b chore(jest.config.cjs): linting 2023-07-27 10:32:23 -04:00
Danny Avila
c9c77d6fdf chore(eslint): add 'import' plugin to eslint configuration
chore(prettier): add 'prettier-plugin-tailwindcss' plugin to prettier configuration
chore(package.json): update eslint-plugin-import to version 2.27.5
2023-07-27 10:32:23 -04:00
Fuegovic
4dc86c4c18 feat: add more plugins to the Plugin store (#709) 2023-07-27 08:10:22 -04:00
Marco Beretta
6ae807c404 Update free_ai_apis.md (#707) 2023-07-27 08:08:53 -04:00
Marco Beretta
b5353e2640 Organize the getting started menu (#708)
* Create installation.md

* Delete installation.md

* Create installation.md

* Update installation.md

* Update README.md

* Update mkdocs.yml

* Update apis_and_tokens.md

* Update README.md

* Delete installation.md

* Update README.md

* Update mkdocs.yml
2023-07-27 08:05:49 -04:00
Danny Avila
f4f1199a55 feat: docker-compose deployment file (#706)
* feat(deploy-compose.yml): add docker-compose file for development deployment

A new docker-compose file has been added for development deployment. This file defines the services required for running the application in a development environment. The services include a client service running nginx, an api service running the LibreChat application, a mongodb service for the database, and a meilisearch service for search functionality.

The client service is configured to use the latest version of the nginx image, with port 3080 mapped to port 80. It also mounts the nginx.conf file and the client's node_modules directory.

The api service is named LibreChat and is built from the librechat image. It exposes port 9000 and depends on the mongodb service. It also mounts the api directory, the .env files, and the client's node_modules directory.

The mongodb service is named chat-mongodb and uses the mongo image. It exposes port 27018 and mounts the data-node directory for data storage

* chore(deploy-compose.yml): update env_file path to ../../.env

* chore(deploy-compose.yml): update image name to librechat_deploy
chore(deploy-compose.yml): update build context to ../../

* chore(deploy-compose.yml): update image and comment out build section

The image for the service has been updated to `ghcr.io/danny-avila/librechat:latest`. The build section has been commented out as it is no longer needed.

* refactor(nginx.conf): reformat nginx.conf for better readability and maintainability

* chore(nginx.conf): add worker_connections configuration to events block
chore(nginx.conf): add listen configuration to server block

* chore(deploy-compose.yml): update nginx container ports configuration
feat(deploy-compose.yml): add support for HTTPS by exposing port 443

* docs(dev/README.md): add instructions for deploying with deploy-compose.yml

* docs(dev/README.md): update instructions for deploying with deploy-compose.yml
2023-07-26 12:57:26 -04:00
Danny Avila
b6028a3434 Update breaking_changes.md 2023-07-26 08:48:12 -04:00
Danny Avila
abef8c02c1 Update breaking_changes.md 2023-07-26 08:44:51 -04:00
Danny Avila
19af2b06ce feat: utitlize lean queries, remove migration script, index createdAt timestamps (#698)
* feat(mongoDb): utitlize lean queries and index createdAt timestamps for cosmosDB support

* fix: remove unnecessary lean() method from deleteMany calls

* fix: remove unnecessary lean() method from deleteMany calls

* fix: remove lean() from queries that need hydration

* chore(migrateDb.js): remove unused migration script
fix(Preset.js): return lean documents when retrieving presets
refactor(index.js): remove migration script from server initialization
refactor(convos.js): remove toObject() when sending conversation object
refactor(presets.js): remove toObject() when sending presets object
2023-07-25 19:27:55 -04:00
Marco Beretta
2f7658e39f Italian-localization-support-for-Nav-components-and-small-fix (#697) 2023-07-25 18:29:32 -04:00
Raí
d3138c79fc Language files: Spanish translation and Portuguese corrections for PR. (#694)
* Add files via upload

* Add files via upload
2023-07-24 16:22:14 -04:00
Abner Chou
1e49b7ecb1 Support localization for Nav components (#688)
* init localization

* Update defaul to en

* Fix merge issue and import path.

* Set default to en

* Change jsx to tsx

* Update the password max length string.

* Remove languageContext as using the recoil instead.

* Add localization to component endpoints pages

* Revert default to en after testing.

* Update LoginForm.tsx

* Fix translation.

* Make lint happy

* Merge (#1)

* Create deploy.yml

* Add localization support for endpoint pages components  (#667)

* init localization

* Update defaul to en

* Fix merge issue and import path.

* Set default to en

* Change jsx to tsx

* Update the password max length string.

* Remove languageContext as using the recoil instead.

* Add localization to component endpoints pages

* Revert default to en after testing.

* Update LoginForm.tsx

* Fix translation.

* Make lint happy

* Add a restart to melisearch in docker-compose.yml (#684)

* Oauth fixes for Cognito (#686)

* Add a restart to melisearch in docker-compose.yml

* Oauth fixes for Cognito

* Use the username or email for full name from oath if not provided

---------

Co-authored-by: Donavan <snark@hey.com>

* Italian localization support for endpoint (#687)

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Donavan Stanley <donavan.stanley@gmail.com>
Co-authored-by: Donavan <snark@hey.com>
Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>

* Translate Nav pages

* Fix npm test

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Donavan Stanley <donavan.stanley@gmail.com>
Co-authored-by: Donavan <snark@hey.com>
Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
2023-07-24 08:33:08 -04:00
Danny Avila
3b865fbc59 Update mkdocs.yml 2023-07-23 20:34:41 -04:00
Danny Avila
afd894553c docs: add installation guide for free AI APIs (ChimeraGPT) (#692)
* docs: add installation guide for free AI APIs (chimeraGPT)

* docs: Update free_ai_apis.md with screenshots
2023-07-23 20:30:58 -04:00
Daniel Avila
df485e5bfe chore(.env.example): comment out OPENAI_MODELS and PLUGIN_MODELS
The OPENAI_MODELS and PLUGIN_MODELS variables are being commented out in the .env.example file. This is done to prefer fetching api/models as the default behavior
2023-07-23 16:51:42 -07:00
Daniel Avila
77252bafc1 chore(.prettierrc.js): update tabWidth to 2 and remove commented out code 2023-07-23 16:51:42 -07:00
Daniel Avila
bd1d5e991d feat(endpoints): fetch v1/api/models for model selection when no default models are set 2023-07-23 16:51:42 -07:00
Danny Avila
18c4883ae0 refactor(PluginsClient.js): simplify getFunctionModelName logic using if-else statements
refactor(PluginsClient.js): improve readability by extracting observedImagePath variable
fix(PluginsClient.js): check if responseMessage already includes observedImagePath before appending observation
2023-07-23 16:51:42 -07:00
Youngwook Kim
197307d514 fix(OpenAIClient): resolve null pointer exception in tokenizer management (#689) 2023-07-23 11:59:11 -04:00
Marco Beretta
130356654c Italian localization support for endpoint (#687) 2023-07-22 20:12:48 -04:00
Donavan Stanley
8f9f09698b Oauth fixes for Cognito (#686)
* Add a restart to melisearch in docker-compose.yml

* Oauth fixes for Cognito

* Use the username or email for full name from oath if not provided

---------

Co-authored-by: Donavan <snark@hey.com>
2023-07-22 20:12:15 -04:00
Donavan Stanley
5da833e066 Add a restart to melisearch in docker-compose.yml (#684) 2023-07-22 15:10:07 -04:00
Abner Chou
b64273957a Add localization support for endpoint pages components (#667)
* init localization

* Update defaul to en

* Fix merge issue and import path.

* Set default to en

* Change jsx to tsx

* Update the password max length string.

* Remove languageContext as using the recoil instead.

* Add localization to component endpoints pages

* Revert default to en after testing.

* Update LoginForm.tsx

* Fix translation.

* Make lint happy
2023-07-22 15:09:45 -04:00
Danny Avila
4148c6d219 Create deploy.yml 2023-07-22 13:49:49 -04:00
Danny Avila
e9d68e3bef Update build.yml 2023-07-22 13:35:00 -04:00
Danny Avila
bbe690cc4b Update build.yml 2023-07-22 13:29:48 -04:00
Danny Avila
a1ad471d87 Update build.yml 2023-07-22 13:21:30 -04:00
Danny Avila
c319d709f3 Create build.yml 2023-07-22 11:31:56 -04:00
Danny Avila
6943f1c2c7 refactor: improve passport strategy handling in async/await manner to prevent race conditions upon importing modules (#682) 2023-07-22 10:29:17 -04:00
Danny Avila
e38483a8b9 feat(config/update.js): add support for updating with single-compose file (#680) 2023-07-21 21:51:35 -04:00
Danny Avila
2a2e6d9991 docs(dev): update README.md with instructions for using single-compose.yml (#676)
feat(single-compose.yml): add single-compose.yml for building leaner app container without meilisearch and mongodb services
- This is useful for deploying on Google, Azure, etc., as a single, leaner container.
- Instructions for running the container are added to the README.md file.
- The container requires a MongoDB Atlas connection string for the `MONGO_URI` environment variable.
- Remote Meilisearch may also be possible, but is not tested.
2023-07-21 20:11:05 -04:00
Danny Avila
deb1472aa5 chore: add update script for assuring clean installations (#673) 2023-07-21 16:44:59 -04:00
Danny Avila
8aa58ea240 chore(api): update langchain dependency to version 0.0.114 (#669) 2023-07-21 00:14:54 -04:00
Daniel Avila
3a112a344d fix(Content.jsx): remove 'z-index: 1;' from currentContent variable
fix(Content.jsx): exclude rehypePlugins if isIFrame is true
fix(Content.jsx): update content rendering to use currentContent variable
2023-07-20 19:40:31 -07:00
Daniel Avila
712be248be chore: bump app version to 0.5.5
chore: bump @waylaidwanderer/chatgpt-api to 1.37.2
2023-07-20 19:40:31 -07:00
fuegovic
530f9d303f feat: Bing Image Creator 2023-07-20 19:40:31 -07:00
Fuegovic
ad29d25396 docs: updates (#662)
* docs: updates

* docs: updates

* Update Settings.jsx
2023-07-19 08:35:41 -07:00
Danny Avila
1ef53a41f0 chore(Settings.jsx): update placeholder text for promptPrefix/system message (#656) 2023-07-16 13:22:36 -04:00
Fuegovic
0246f164b0 docs: add "chatgpt_plugins_openapi.md" to readme.md and mkdocs (#655)
* docs: add chatgpt_plugins_openapi.md to toc

* docs: add chatgpt_plugins_openapi.md to mkdocs toc
2023-07-16 13:14:07 -04:00
Danny Avila
514f625b8f feat: ChatGPT Plugins/OpenAPI specs for Plugins Endpoint (#620)
* wip: proof of concept for openapi chain

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

* feat(Plugins): use ChatGPT Plugins/OpenAPI specs (first pass)

* chore(manifest.json): update pluginKey for "Browser" tool to "web-browser"
chore(handleTools.js): update customConstructor key for "web-browser" tool

* fix(handleSubmit.js): set unfinished property to false for all endpoints

* fix(handlers.js): remove unnecessary capitalizeWords function and use action.tool directly
refactor(endpoints.js): rename availableTools to tools and transform it into a map

* feat(endpoints): add plugins selector to endpoints file
refactor(CodeBlock.tsx): refactor to typescript
refactor(Plugin.tsx): use recoil Map for plugin name and refactor to typescript
chore(Message.jsx): linting
chore(PluginsOptions/index.jsx): remove comment/linting
chore(svg): export Clipboard and CheckMark components from SVG index and refactor to typescript

* fix(OpenAPIPlugin.js): rename readYamlFile function to readSpecFile
fix(OpenAPIPlugin.js): handle JSON files in readSpecFile function
fix(OpenAPIPlugin.js): handle JSON URLs in getSpec function
fix(OpenAPIPlugin.js): handle JSON variables in createOpenAPIPlugin function
fix(OpenAPIPlugin.js): add description for variables in createOpenAPIPlugin function
fix(OpenAPIPlugin.js): add optional flag for is_user_authenticated and has_user_authentication in ManifestDefinition
fix(loadSpecs.js): add optional flag for is_user_authenticated and has_user_authentication in ManifestDefinition
fix(Plugin.tsx): remove unnecessary callback parameter in getPluginName function
fix(getDefaultConversation.js): fix browser console error: handle null value for lastConversationSetup in getDefaultConversation function

* feat(api): add new tools

Add Ai PDF tool for super-fast, interactive chats with PDFs of any size, complete with page references for fact checking.
Add VoxScript tool for searching through YouTube transcripts, financial data sources, Google Search results, and more.
Add WebPilot tool for browsing and QA of webpages, PDFs, and data. Generate articles from one or more URLs.

feat(api): update OpenAPIPlugin.js

- Add support for bearer token authorization in the OpenAPIPlugin.
- Add support for custom headers in the OpenAPIPlugin.

fix(api): fix loadTools.js

- Pass the user parameter to the loadSpecs function.

* feat(PluginsClient.js): import findMessageContent function from utils
feat(PluginsClient.js): add message parameter to options object in initializeCustomAgent function
feat(PluginsClient.js): add content to errorMessage if message content is found
feat(PluginsClient.js): break out of loop if message content is found
feat(PluginsClient.js): add delay option with value of 8 to generateTextStream function
feat(PluginsClient.js): add support for process.env.PORT environment variable in app.listen function
feat(askyourpdf.json): add askyourpdf plugin configuration
feat(metar.json): add metar plugin configuration
feat(askyourpdf.yaml): add askyourpdf plugin OpenAPI specification
feat(OpenAPIPlugin.js): add message parameter to createOpenAPIPlugin function
feat(OpenAPIPlugin.js): add description_for_model to chain run message
feat(addOpenAPISpecs.js): remove verbose option from loadSpecs function call

fix(loadSpecs.js): add 'message' parameter to the loadSpecs function
feat(findMessageContent.js): add utility function to find message content in JSON objects

* fix(PluginStoreDialog.tsx): update z-index value for the dialog container

The z-index value for the dialog container was updated to "102" to ensure it appears above other elements on the page.

* chore(web_pilot.json): add "params" field with "user_has_request" parameter set to true

* chore(eslintrc.js): update eslint rules
fix(Login.tsx): add missing semicolon after import statement

* fix(package-lock.json): update langchain dependency to version ^0.0.105

* fix(OpenAPIPlugin.js): change header key from 'id' to 'librechat_user_id' for consistency and clarity

feat(plugins): add documentation for using official ChatGPT Plugins with OpenAPI specs

This commit adds a new file `chatgpt_plugins_openapi.md` to the `docs/features/plugins` directory. The file provides detailed information on how to use official ChatGPT Plugins with OpenAPI specifications. It explains the components of a plugin, including the Plugin Manifest file and the OpenAPI spec. It also covers the process of adding a plugin, editing manifest files, and customizing OpenAPI spec files. Additionally, the commit includes disclaimers about the limitations and compatibility of plugins with LibreChat. The documentation also clarifies that the use of ChatGPT Plugins with LibreChat does not violate OpenAI's Terms of Service.

The purpose of this commit is to provide comprehensive documentation for developers who want to integrate ChatGPT Plugins into their projects using OpenAPI specs. It aims to guide them through the process of adding and configuring plugins, as well as addressing potential issues and

chore(introduction.md): update link to ChatGPT Plugins documentation
docs(introduction.md): clarify the purpose of the plugins endpoint and its capabilities

* fix(OpenAPIPlugin.js): update SUFFIX variable to provide a clearer description
docs(chatgpt_plugins_openapi.md): update information about adding plugins via url on the frontend

* feat(PluginsClient.js): sendIntermediateMessage on successful Agent load
fix(PluginsClient.js, server/index.js, gptPlugins.js): linting fixes
docs(chatgpt_plugins_openapi.md): update links and add additional information

* Update chatgpt_plugins_openapi.md

* chore: rebuild package-lock file

* chore: format/lint all files with new rules

* chore: format all files

* chore(README.md): update AI model selection list

The AI model selection list in the README.md file has been updated to reflect the current options available. The "Anthropic" model has been added as an alternative name for the "Claude" model.

* fix(Plugin.tsx): type issue

* feat(tools): add new tool WebPilot

feat(tools): remove tool Weather Report

feat(tools): add new tool Prompt Perfect

feat(tools): add new tool Scholarly Graph Link

* feat(OpenAPIPlugin.js): add getSpec and readSpecFile functions
feat(OpenAPIPlugin.spec.js): add tests for readSpecFile, getSpec, and createOpenAPIPlugin functions

* chore(agent-demo-1.js): remove unused code and dependencies
chore(agent-demo-2.js): remove unused code and dependencies
chore(demo.js): remove unused code and dependencies

* feat(addOpenAPISpecs): add function to transform OpenAPI specs into desired format
feat(addOpenAPISpecs.spec): add tests for transformSpec function
fix(loadSpecs): remove debugging code

* feat(loadSpecs.spec.js): add unit tests for ManifestDefinition, validateJson, and loadSpecs functions

* fix: package file resolution bug

* chore: move scholarly_graph_link manifest to 'has-issues'

* refactor(client/hooks): convert to TS and export from index

* Update introduction.md

* Update chatgpt_plugins_openapi.md
2023-07-16 12:19:47 -04:00
Danny Avila
39ac8d3858 fix: typo when including proxy for langchain (#653)
* fix(PluginsClient.js): change reverseProxyUrl variable to options.reverseProxyUrl

* chore(.prettierrc.js): comment out tabWidth option in Prettier configuration
2023-07-15 12:19:23 -04:00
Danny Avila
15987abe0a style(Nav): improve Nav transition for open/close (#652)
* Revert "Animated sidebar (#649)"

This reverts commit dd19323280.

* in progress

* style(Nav): improve transition for Nav
2023-07-15 10:43:15 -04:00
Anirudh
dd19323280 Animated sidebar (#649)
* Initial Commit

* Add transition
2023-07-15 08:26:18 -04:00
Fuegovic
af47a68632 fix: sharpness in Bing Chat icon (#648)
* Fix sharpness in Bing Chat icon

* Fix sharpness in Bing Chat icon

* Fix sharpness in Bing Chat icon
2023-07-15 08:25:11 -04:00
Danny Avila
9303ea2f57 chore(.env.example): add MEILI_NO_ANALYTICS variable and set it to true (#647)
chore(docker-compose.yml): add MEILI_NO_ANALYTICS environment variable and set it to true
2023-07-15 08:23:34 -04:00
Danny Avila
20dde44512 fix(Settings.jsx): fix Settings inputs losing focus to main textarea (#646)
* fix(Settings.jsx): fix Settings inputs losing focus to main textarea

* refactor(Input/index.jsx): remove console.log statement in useEffect
2023-07-14 15:47:32 -04:00
Danny Avila
732a0b8029 Release: 0.5.4 (#645)
* Release: v.0.5.4

* fix(bingAI.js): fix condition to check if partialText is longer than response.text

The condition to check if partialText is longer than response.text was not working correctly because it was not properly trimming the partialText before comparing its length. This fix trims the partialText before checking its length to ensure accurate comparison.
2023-07-14 12:44:31 -04:00
Anirudh
50374f7539 fix: Minor UI Changes (#643)
* Minor UI Fixes

* Link Icon, from index.ts

* Import from components instead of svg

* Fix Sidebar and Icons
2023-07-14 12:33:28 -04:00
Danny Avila
1a21eb5bae fix(BingAI): show censored message, fix toneStyle UI bug (#644)
* fix(frontend/BingAI): prevent Settings from not showing on new conversation, also prevent showing toneStyle change without jailbreak

* fix(Input/index.jsx): fix typo in comment, change "also prevents toneStyle change without jailbreak" to "also prevents showing toneStyle change without jailbreak"

* fix(BingAI): show message despite censor trigger
2023-07-14 10:57:24 -04:00
Fuegovic
1a5144be76 Docs: Instruction to deploy on render.com (#638)
* Create render.md

* Update render.md

* Update mkdocs.yml

* Update render.md

* Update README.md

* Update render.md

* Update apis_and_tokens.md

add basic instruction for Anthropic Claude
2023-07-14 09:40:41 -04:00
Danny Avila
e5336039fc ci(backend-review.yml): add linter step to the backend review workflow (#625)
* ci(backend-review.yml): add linter step to the backend review workflow

* chore(backend-review.yml): remove prettier from lint-action configuration

* chore: apply new linting workflow

* chore(lint-staged.config.js): reorder lint-staged tasks for JavaScript and TypeScript files

* chore(eslint): update ignorePatterns in .eslintrc.js
chore(lint-action): remove prettier option in backend-review.yml
chore(package.json): add lint and lint:fix scripts

* chore(lint-staged.config.js): remove prettier --write command for js, jsx, ts, tsx files

* chore(titleConvo.js): remove unnecessary console.log statement
chore(titleConvo.js): add missing comma in options object

* chore: apply linting to all files

* chore(lint-staged.config.js): update lint-staged configuration to include prettier formatting
2023-07-14 09:36:49 -04:00
Danny Avila
637bb6bc11 feat(Anthropic, Google, OpenAI): allow changing settings/presets conversations mid-convo (#636)
feat(AnthropicClient.js, GoogleClient.js): add promptPrefix and modelLabel to getSaveOptions method
2023-07-13 23:59:44 -04:00
Danny Avila
1b999108e4 fix(titleConvo): use openAIApiKey and azure config from route handler (#637)
* fix(titleConvo): use openAIApiKey from route handler, handle azure conditional from route

* chore: remove comment
2023-07-13 23:59:14 -04:00
Danny Avila
3e5c5a828d feat(server/index.js): add warning message if social login is disabled (#635) 2023-07-13 21:49:36 -04:00
Marco Beretta
e3bf674cb7 Doc update, some i8n fix, Italian translation and a more realistic "chat.openai.com" login page (#634)
* Update user_auth_system.md

* Update Landing.tsx

* i8n bug fix & added Italian translation

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-07-13 21:43:08 -04:00
Fuegovic
c7e57cd3a2 update bing chat icon (#627)
* update bing chat icon

* add bing logo backup

* add BingIconBackup.jsx

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-07-13 21:36:44 -04:00
Dan Orlando
9e931229e2 feat: claude integration (#552)
* feat: bare bones implementation of claude client (WIP)

* feat: client implementation of Claude (WIP)

* fix: add claude to store

* feat: bare bones implementation of claude client (WIP)

* switch eventsource

* Try new method of calling claude with anthropic sdk

* (WIP) Finish initial claude client implementation and api

* debugging update

* fix(ClaudeClient.js): fix prompt prefixes for HUMAN_PROMPT and AI_PROMPT
fix(ClaudeClient.js): refactor buildMessages logic for correct handling of messages
refactor(ClaudeClient.js): refactor buildPrompt method to buildMessages for use in BaseClient sendMessage method
refactor(ClaudeClient.js): refactor getCompletion method to sendCompletion for use in BaseClient sendMessage method
refactor(ClaudeClient.js): omit getMessageMapMethod method for future refactoring
refactor(ClaudeClient.js): remove unused sendMessage method to prefer BaseClient message
fix(askClaude.js): error in getIds method was causing a frontend crash, userMessage was not defined
fix(askClaude.js): import abortMessage function from utils module
feat(askClaude.js): add /abort route to handle message abort requests
feat(askClaude.js): create abortControllers map to store abort controllers
feat(askClaude.js): implement abortAsk function to handle message abort logic
feat(askClaude.js): add onStart callback to handle message start logic
feat(HoverButtons.jsx): add 'claude' as a supported endpoint for branching

* fix(ClaudeClient.js): update defaultPrefix and promptPrefix messages

includes 'Remember your instructions' as Claude is trained to recognize labels preceding colons as participants of a conversation

* Change name from claude to anthropic

* add settings to handleSubmit and models to endpoints

* Implement Claude settings

* use svg for anthropic icon

* Implement abort

* Implement reverse proxy

* remove png icons

* replace web browser plugin

* remove default prefix

* fix styling of claude icon

* fix console error from svg properties

* remove single quote requirement from eslintrc

* fix(AnthropicClient.js): fix labels for HUMAN_PROMPT and AI_PROMPT
feat(AnthropicClient.js): add support for custom userLabel and modelLabel options
feat(AnthropicClient.js): add user_id metadata to requestOptions in getCompletion method
feat(anthropic, AnthropicClient.js): add debug logging

* refactor(AnthropicClient.js): change promptSuffix variable declaration from let to const

* fix(EndpointOptionsDialog.jsx): remove unnecessary code that changes endpointName from 'anthropic' to 'Claude'
fix(utils/index.jsx): fix alternateName value for 'anthropic' from 'Claude' to 'Anthropic'

* fix(AnthropicIcon): fix sizing/rendering/name of anthropic icon

* fix(AnthropicClient.js): change maxContextTokens default value to 99999
fix(AnthropicClient.js): change maxResponseTokens default value to 1500
fix(AnthropicClient.js): remove unnecessary code for setting maxContextTokens and maxResponseTokens based on modelOptions
fix(AnthropicClient.js): change max_tokens_to_sample default value to 1500
fix(anthropic.js): pass endpointOption.token to AnthropicClient constructor

* Update .env.example

* fix(AnthropicClient.js): remove exceeding message when it puts us over the token limit
fix(AnthropicClient.js): handle case when the first message exceeds the token limit
fix(AnthropicClient.js): throw error when prompt is too long
fix(AnthropicClient.js): adjust max tokens calculation to use maxOutputTokens
fix(anthropic.js): remove console.log statement in ask route

* feat(server/index): increase incoming json payload allowed size

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
2023-07-13 21:35:15 -04:00
Marco Beretta
981d009508 fix(discordStrategy): only authorize on first login (#626)
* Add files via upload

* Create linode-setup.md

* Create cloudflare-setup.md

* Update cloudflare-setup.md

* Delete 4-linode.png

* Delete 3-linode.png

* Add files via upload

* Add files via upload

* Update cloudflare-setup.md

* Update linode-setup.md

* Rename cloudflare-setup.md to cloudflare.md

* Rename linode-setup.md to linode.md

* Update mkdocs.yml

* Update cloudflare.md

* Update linode.md

* Update README.md

* Update README.md

* Update linode.md

sentence in Italian

* v1

The frontend has been completed, along with the .env variables.

However, there is an issue of infinite loading thereafter.

* Fix email and remove deprecated GitHub passport

* Update user_auth_system.md

add How to Set Up a Github Authentication

* Update .env.example

Improved the comment above the GitHub client ID and secret.

* Update user_auth_system.md

* Update package.json

* Remove unnecessary passport GitHub package

* fixed conflicts

 fixed conflicts between Berry-13:main and danny-avila:main

in api/server/index.js 45:54

* Delete e -i HEAD~2

* (WIP) Discord Login

* Fix duplicate githubLoginEnabled

* .env.example restore

* Update user_auth_system.md

Discord Login

* Fix and new Feature

1. Added Discord login to .env.example.
2. Created Google, Github, and Discord icons in client\src\components\svg.
3. Added the social login option in the .env file; it fixes the ---or---. Check Discord for more information.

* fix Login.tsx and Registration.tsx

* Update user_auth_system.md

* Update .env.example

* Added OpenID Icon

* quick discord icon fix

* discord strategy fix

* remove comment

* fix discord authorize every time
2023-07-12 18:15:11 -04:00
Danny Avila
f5672ddcf8 Auth fix (#624)
* chore(eslint): add ignore pattern for packages/data-provider/types
chore(data-provider): fix import formatting in index.ts
chore(data-provider): add types/index.d.ts to tsconfig include

* fix(Auth): fix "skip login" bug, where UI would render in an unauthenticated state
fix(Login.tsx): replace navigate('/chat/new') with navigate('/chat/new', { replace: true })
fix(AuthContext.tsx): replace navigate(redirect) with navigate(redirect, { replace: true })
fix(AuthContext.tsx): replace navigate('/login') with navigate('/login', { replace: true })
fix(AuthContext.tsx): replace navigate('/login') with navigate('/login', { replace: true })
fix(routes/Chat.jsx): add check for isAuthenticated: navigate to '/login' and render null if not authenticated
fix(routes/index.jsx): add check for isAuthenticated: navigate to '/login' and render null if not authenticated

* refactor(SubmitButton.jsx): create a set of endpoints to hide set tokens
fix(SubmitButton.jsx): fix condition to check if token is provided for certain endpoints
2023-07-12 11:37:27 -04:00
Marco Beretta
747e087cf5 Discord Login (#615)
* Add files via upload

* Create linode-setup.md

* Create cloudflare-setup.md

* Update cloudflare-setup.md

* Delete 4-linode.png

* Delete 3-linode.png

* Add files via upload

* Add files via upload

* Update cloudflare-setup.md

* Update linode-setup.md

* Rename cloudflare-setup.md to cloudflare.md

* Rename linode-setup.md to linode.md

* Update mkdocs.yml

* Update cloudflare.md

* Update linode.md

* Update README.md

* Update README.md

* Update linode.md

sentence in Italian

* v1

The frontend has been completed, along with the .env variables.

However, there is an issue of infinite loading thereafter.

* Fix email and remove deprecated GitHub passport

* Update user_auth_system.md

add How to Set Up a Github Authentication

* Update .env.example

Improved the comment above the GitHub client ID and secret.

* Update user_auth_system.md

* Update package.json

* Remove unnecessary passport GitHub package

* fixed conflicts

 fixed conflicts between Berry-13:main and danny-avila:main

in api/server/index.js 45:54

* Delete e -i HEAD~2

* (WIP) Discord Login

* Fix duplicate githubLoginEnabled

* .env.example restore

* Update user_auth_system.md

Discord Login

* Fix and new Feature

1. Added Discord login to .env.example.
2. Created Google, Github, and Discord icons in client\src\components\svg.
3. Added the social login option in the .env file; it fixes the ---or---. Check Discord for more information.

* fix Login.tsx and Registration.tsx

* Update user_auth_system.md

* Update .env.example

* Added OpenID Icon

* quick discord icon fix

* discord strategy fix

* remove comment
2023-07-11 17:17:58 -04:00
Danny Avila
c17c1488ca chore(LoginForm.tsx): remove extra whitespace (#619)
chore(Translation.tsx): add default return statement for getTranslations function
chore(Eng.tsx): fix indentation, remove escaped apostrophe, and remove trailing whitespace
chore(Zh.tsx): fix indentation, remove escaped apostrophe, and remove trailing whitespace
2023-07-11 16:02:31 -04:00
Abner Chou
47e5493744 Feature Localization (i18n) Support (#557)
* init localization

* Update defaul to en

* Fix merge issue and import path.

* Set default to en

* Change jsx to tsx

* Update the password max length string.

* Remove languageContext as using the recoil instead.
2023-07-11 15:55:21 -04:00
HyunggyuJang
13627c7f4f feat: Generate bing's title using bing (#612) 2023-07-09 08:41:43 -04:00
John Cao
9e15747455 Plugin needs index.js section (#607)
* add index.js section

Plugin needs to appear in index.js

* update typo

modeule -> module LOL
2023-07-08 16:36:47 -04:00
Danny Avila
ce6490109f fix(mongoMeili): allow convo deletion if meili errors or disabled (#609) 2023-07-08 11:41:51 -04:00
HyunggyuJang
f0e2639269 feat: Batch indexing (#606) 2023-07-07 22:20:57 -04:00
Danny Avila
cf3889d8e4 docs: Update docker_install.md with new container URL (#604) 2023-07-07 14:08:01 -04:00
Youngwook Kim
6efb5bd88e docs: update Docker installation and configuration guide (#601)
This PR updates the Docker installation and configuration instructions for LibreChat to improve clarity and readability. The changes include restructuring the document and refining certain parts for smoother understanding. Here's a summary of the modifications:

- The installation and configuration steps have been reorganized into separate sections for better organization.
- The LibreChat configuration section provides clearer instructions for updating the credentials in the `docker-compose.yml` file and setting up the `.env` file.
2023-07-07 14:04:24 -04:00
Danny Avila
a64342f515 fix(handleTools.js): refactor loading of openAIApiKey to handle user_provided value (#603)
fix(PluginController.js): handle user_provided value in isPluginAuthenticated function
refactor(PluginService.js): remove commented out code
2023-07-07 13:59:59 -04:00
Danny Avila
9eefa3e24c fix: add PaLM icon as SVG and improve meilisearch syncing to prevent large indicing jobs (#600)
* feat(getIcon.jsx): replace palm.png with google-palm.svg as the icon for the 'google' endpoint

* fix(mongoMeili): improve syncing, prevent large indicing jobs from being queued
fix(gptPlugins.js, openAI.js): use unfinished and cancelled values when saving messages to help optimize syncing
2023-07-07 02:03:23 -04:00
Danny Avila
2607f157d3 Release: 0.5.3 (#599)
* release: 0.5.3, add extra linting rule and minor fix for EndpointDialog

* chore: revert to deprecated Message Classes as weird behavior seen in linux

* chore(api): remove unused test scripts
chore(package.json): remove unused langchain dependency

* chore(.gitignore): add /images directory to the ignore list
2023-07-06 21:03:31 -04:00
Fuegovic
69d192bac3 Docs: assets clean up (#598)
* Update ngrok.md

* Update linode.md

* Update cloudflare.md

* Update testing.md

* Update google_search.md

* Update introduction.md

* Update stable_diffusion.md

* Update wolfram.md

* docs: assets clean up
2023-07-06 17:41:22 -04:00
Danny Avila
fabd85ff40 hotfix(Plugins): retain message history, improve completion prompt handling (#597) 2023-07-06 16:45:39 -04:00
Danny Avila
12e2826d39 Update azure.md 2023-07-06 14:29:03 -04:00
Danny Avila
206fc50bc9 docs(azure.md): update instructions for using Azure with the Plugins endpoint 2023-07-06 14:29:03 -04:00
Danny Avila
b6a2634a0a refactor(PluginsClient.js): remove unnecessary console.debug statement 2023-07-06 14:29:03 -04:00
Danny Avila
5ad0ef331f feat: user_provided token for plugins, temp remove user_provided azure creds for Plugins until solution is made 2023-07-06 14:29:03 -04:00
Danny Avila
5b30ab5d43 chore(api): update langchain dependency to version 0.0.103 2023-07-06 14:29:03 -04:00
Danny Avila
8b91145953 refactor(SetTokenDialog): refactor to TS, modularize config logic into separate components 2023-07-06 14:29:03 -04:00
Danny Avila
b6f21af69b chore(eslint): update max-len rule to limit code length to 120 characters
chore(eslint): update quotes rule to enforce the use of single quotes
2023-07-06 14:29:03 -04:00
David
684ffd8e66 Update styles to match to openai's panel button (#595) 2023-07-06 14:25:17 -04:00
John Cao
da17649557 Update apis_and_tokens.md (#594)
`accessToken` rather than `access_token`
2023-07-06 14:23:51 -04:00
Marco Beretta
c343ae16c6 Cloudflare tunnels doc (#592)
* Update cloudflare.md

* Update cloudflare.md
2023-07-06 14:22:35 -04:00
Danny Avila
77d5fb0c58 refactor(BaseClient, GoogleClient): make sendCompletion required, refactor Google to use Base sendMessage (#591) 2023-07-05 14:00:12 -04:00
Dan Orlando
4e317c85fd fix: Remove script for migrateDataToFirstUser (#590) 2023-07-05 12:45:25 -04:00
Marco Beretta
3c1aeab340 Ngrok Documentation (#586)
* Ngrok doc

* Add files via upload

* Update README.md

* Update mkdocs.yml
2023-07-05 09:20:23 -04:00
Danny Avila
75250f3a5f Fix: Azure "user_provided" Frontend Credentials and Save Last Selected Bing Settings (#587)
* fix(Messages.jsx): fix <body> tag warning

* fix(NewConversationMenu): update localStorage with lastBingSettings when endpoint is 'bingAI'
fix(getDefaultConversation): retrieve lastBingSettings from localStorage and use it to set default values for jailbreak and toneStyle when endpoint is 'bingAI'
feat(settings.spec.js): add test to check if the active class is set on the selected endpoint in the settings menu

* fix(BingAIOptions): add data-testid to BingAIOptions SelectDropDown component
fix(settings.spec.js): update test to include additional steps for testing settings persistence

* fix(azure): support user_provided credentials from client
2023-07-05 09:18:45 -04:00
Dan Orlando
04e4259005 Move data provider to shared package (#582)
* create data-provider package and move code from data-provider folder to be shared between apps

* fix type issues

* add packages to ignore

* add new data-provider package to apps

* refactor: change client imports to use @librechat/data-provider package

* include data-provider build script in frontend build

* fix type issue after rebasing

* delete admin/package.json from this branch

* update test ci script to include building of data-provider package

* Try using regular build for test action

* Switch frontend-review back to build:ci

* Remove loginRedirect from Login.tsx

* Add ChatGPT back to EModelEndpoint
2023-07-04 15:47:41 -04:00
Marco Beretta
d0078d478d GIthub Login (#578)
* Add files via upload

* Create linode-setup.md

* Create cloudflare-setup.md

* Update cloudflare-setup.md

* Delete 4-linode.png

* Delete 3-linode.png

* Add files via upload

* Add files via upload

* Update cloudflare-setup.md

* Update linode-setup.md

* Rename cloudflare-setup.md to cloudflare.md

* Rename linode-setup.md to linode.md

* Update mkdocs.yml

* Update cloudflare.md

* Update linode.md

* Update README.md

* Update README.md

* Update linode.md

sentence in Italian

* v1

The frontend has been completed, along with the .env variables.

However, there is an issue of infinite loading thereafter.

* Fix email and remove deprecated GitHub passport

* Update user_auth_system.md

add How to Set Up a Github Authentication

* Update .env.example

Improved the comment above the GitHub client ID and secret.

* Update user_auth_system.md

* Update package.json

* Remove unnecessary passport GitHub package

* fixed conflicts

 fixed conflicts between Berry-13:main and danny-avila:main

in api/server/index.js 45:54

* Delete e -i HEAD~2
2023-07-04 15:23:42 -04:00
Danny Avila
8819e83d2c refactor: Client Classes & Azure OpenAI as a separate Endpoint (#532)
* refactor: start new client classes, test localAi support

* feat: create base class, extend chatgpt from base

* refactor(BaseClient.js): change userId parameter to user
refactor(BaseClient.js): change userId parameter to user
feat(OpenAIClient.js): add sendMessage method
refactor(OpenAIClient.js): change getConversation method to use user parameter instead of userId
refactor(OpenAIClient.js): change saveMessageToDatabase method to use user parameter instead of userId
refactor(OpenAIClient.js): change buildPrompt method to use messages parameter instead of orderedMessages
feat(index.js): export client classes
refactor(askGPTPlugins.js): use req.body.token or process.env.OPENAI_API_KEY as OpenAI API key
refactor(index.js): comment out askOpenAI route
feat(index.js): add openAI route

feat(openAI.js): add new route for OpenAI API requests with support for progress updates and aborting requests.

* refactor(BaseClient.js): use optional chaining operator to access messageId property
refactor(OpenAIClient.js): use orderedMessages instead of messages to build prompt
refactor(OpenAIClient.js): use optional chaining operator to access messageId property
refactor(fetch-polyfill.js): remove fetch polyfill
refactor(openAI.js): comment out debug option in clientOptions

* refactor: update import statements and remove unused imports in several files
feat: add getAzureCredentials function to azureUtils module
docs: update comments in azureUtils module

* refactor(utils): rename migrateConversations to migrateDataToFirstUser for clarity and consistency

* feat(chatgpt-client.js): add getAzureCredentials function to retrieve Azure credentials
feat(chatgpt-client.js): use getAzureCredentials function to generate reverseProxyUrl
feat(OpenAIClient.js): add isChatCompletion property to determine if chat completion model is used
feat(OpenAIClient.js): add saveOptions parameter to sendMessage and buildPrompt methods
feat(OpenAIClient.js): modify buildPrompt method to handle chat completion model
feat(openAI.js): modify endpointOption to include modelOptions instead of individual options
refactor(OpenAIClient.js): modify getDelta property to use isChatCompletion property instead of isChatGptModel property
refactor(OpenAIClient.js): modify sendMessage method to use saveOptions parameter instead of modelOptions parameter
refactor(OpenAIClient.js): modify buildPrompt method to use saveOptions parameter instead of modelOptions parameter
refactor(OpenAIClient.js): modify ask method to include endpointOption parameter

* chore: delete draft file

* refactor(OpenAIClient.js): extract sendCompletion method from sendMessage method for reusability

* refactor(BaseClient.js): move sendMessage method to BaseClient class
feat(OpenAIClient.js): inherit from BaseClient class and implement necessary methods and properties for OpenAIClient class.

* refactor(BaseClient.js): rename getBuildPromptOptions to getBuildMessagesOptions
feat(BaseClient.js): add buildMessages method to BaseClient class
fix(ChatGPTClient.js): use message.text instead of message.message
refactor(ChatGPTClient.js): rename buildPromptBody to buildMessagesBody
refactor(ChatGPTClient.js): remove console.debug statement and add debug log for prompt variable

refactor(OpenAIClient.js): move setOptions method to the bottom of the class
feat(OpenAIClient.js): add support for cl100k_base encoding
feat(OpenAIClient.js): add support for unofficial chat GPT models
feat(OpenAIClient.js): add support for custom modelOptions
feat(OpenAIClient.js): add caching for tokenizers
feat(OpenAIClient.js): add freeAndInitializeEncoder method to free and reinitialize tokenizers
refactor(OpenAIClient.js): rename getBuildPromptOptions to getBuildMessagesOptions
refactor(OpenAIClient.js): rename buildPrompt to buildMessages
refactor(OpenAIClient.js): remove endpointOption from ask function arguments in openAI.js

* refactor(ChatGPTClient.js, OpenAIClient.js): improve code readability and consistency

- In ChatGPTClient.js, update the roleLabel and messageString variables to handle cases where the message object does not have an isCreatedByUser property or a role property with a value of 'user'.
- In OpenAIClient.js, rename the freeAndInitializeEncoder method to freeAndResetEncoder to better reflect its functionality. Also, update the method calls to reflect the new name. Additionally, update the getTokenCount method to handle errors by calling the freeAndResetEncoder method instead of the now-renamed freeAndInitializeEncoder method.

* refactor(OpenAIClient.js): extract instructions object to a separate variable and add it to payload after formatted messages
fix(OpenAIClient.js): handle cases where progressMessage.choices is undefined or empty

* refactor(BaseClient.js): extract addInstructions method from sendMessage method
feat(OpenAIClient.js): add maxTokensMap object to map maximum tokens for each model
refactor(OpenAIClient.js): use addInstructions method in buildMessages method instead of manually building the payload list

* refactor(OpenAIClient.js): remove unnecessary condition for modelOptions.model property in buildMessages method

* feat(BaseClient.js): add support for token count tracking and context strategy
feat(OpenAIClient.js): add support for token count tracking and context strategy
feat(Message.js): add tokenCount field to Message schema and updateMessage function

* refactor(BaseClient.js): add support for refining messages based on token limit
feat(OpenAIClient.js): add support for context refinement strategy
refactor(OpenAIClient.js): use context refinement strategy in message sending
refactor(server/index.js): improve code readability by breaking long lines

* refactor(BaseClient.js): change `remainingContext` to `remainingContextTokens` for clarity
feat(BaseClient.js): add `refinePrompt` and `refinePromptTemplate` to handle message refinement
feat(BaseClient.js): add `refineMessages` method to refine messages
feat(BaseClient.js): add `handleContextStrategy` method to handle context strategy
feat(OpenAIClient.js): add `abortController` to `buildPrompt` method options
refactor(OpenAIClient.js): change `payload` and `tokenCountMap` to let variables in `handleContextStrategy` method
refactor(BaseClient.js): change `remainingContext` to `remainingContextTokens` in `handleContextStrategy` method for consistency
refactor(BaseClient.js): change `remainingContext` to `remainingContextTokens` in `getMessagesWithinTokenLimit` method for consistency
refactor(BaseClient.js): change `remainingContext` to `remainingContext

* chore(openAI.js): comment out contextStrategy option in clientOptions

* chore(openAI.js): comment out debug option in clientOptions object

* test: BaseClient tests in progress

* test: Complete OpenAIClient & BaseClient tests

* fix(OpenAIClient.js): remove unnecessary whitespace
fix(OpenAIClient.js): remove unused variables and comments
fix(OpenAIClient.test.js): combine getTokenCount and freeAndResetEncoder tests

* chore(.eslintrc.js): add rule for maximum of 1 empty line
feat(ask/openAI.js): add abortMessage utility function
fix(ask/openAI.js): handle error and abort message if partial text is less than 2 characters
feat(utils/index.js): export abortMessage utility function

* test: complete additional tests

* feat: Azure OpenAI as a separate endpoint

* chore: remove extraneous console logs

* fix(azureOpenAI): use chatCompletion endpoint

* chore(initializeClient.js): delete initializeClient.js file

chore(askOpenAI.js): delete old OpenAI route handler

chore(handlers.js): remove trailing whitespace in thought variable assignment

* chore(chatgpt-client.js): remove unused chatgpt-client.js file
refactor(index.js): remove askClient import and export from index.js

* chore(chatgpt-client.tokens.js): update test script for memory usage and encoding performance

The test script in `chatgpt-client.tokens.js` has been updated to measure the memory usage and encoding performance of the client. The script now includes information about the initial memory usage, peak memory usage, final memory usage, and memory usage after a timeout. It also provides insights into the number of encoding requests that can be processed per second.

The script has been modified to use the `OpenAIClient` class instead of the `ChatGPTClient` class. Additionally, the number of iterations for the encoding loop has been reduced to 10,000.

A timeout function has been added to simulate a delay of 15 seconds. After the timeout, the memory usage is measured again.

The script now handles uncaught exceptions and logs any errors that occur, except for errors related to failed fetch requests.

Note: This is a test script and should not be used in production

* feat(FakeClient.js): add a new class `FakeClient` that extends `BaseClient` and implements methods for a fake client
feat(FakeClient.js): implement the `setOptions` method to handle options for the fake client
feat(FakeClient.js): implement the `initializeFakeClient` function to initialize a fake client with options and fake messages
fix(OpenAIClient.js): remove duplicate `maxTokensMap` import and use the one from utils
feat(BaseClient): return promptTokens and completionTokens

* refactor(gptPlugins): refactor ChatAgent to PluginsClient, which extends OpenAIClient

* refactor: client paths

* chore(jest.config.js): remove jest.config.js file

* fix(PluginController.js): update file path to manifest.json
feat(gptPlugins.js): add support for aborting messages

refactor(ask/index.js): rename askGPTPlugins to gptPlugins for consistency

* fix(BaseClient.js): fix spacing in generateTextStream function signature
refactor(BaseClient.js): remove unnecessary push to currentMessages in generateUserMessage function
refactor(BaseClient.js): remove unnecessary push to currentMessages in handleStartMethods function
refactor(PluginsClient.js): remove unused variables and date formatting in constructor
refactor(PluginsClient.js): simplify mapping of pastMessages in getCompletionPayload function

* refactor(GoogleClient): GoogleClient now extends BaseClient

* chore(.env.example): add AZURE_OPENAI_MODELS variable
fix(api/routes/ask/gptPlugins.js): enable Azure integration if PLUGINS_USE_AZURE is true
fix(api/routes/endpoints.js): getOpenAIModels function now accepts options, use AZURE_OPENAI_MODELS if PLUGINS_USE_AZURE is true
fix(client/components/Endpoints/OpenAI/Settings.jsx): remove console.log statement
docs(features/azure.md): add documentation for Azure OpenAI integration and environment variables

* fix(e2e:popup): includes the icon + endpoint names in role, name property
2023-07-03 16:51:12 -04:00
Danny Avila
10c772c9f2 fix(e2e): add support for setting localStorage before navigating to the page due to new Nav behavior (#579) 2023-07-03 16:37:23 -04:00
David
8e1473c3d8 feature: ChatGPT style show/hide panel button, panel state saving in local storage (#575)
* Add nav bar state saving to local storage

* Add ChatGPT style hide side panel button
2023-07-03 16:26:00 -04:00
Danny Avila
88683b9cc5 fix(Chat.jsx): Improve Message Creation UX by Eliminating Screen Flicker (#577)
* fix(Chat.jsx): conversation no longer navigates upon message creation, which would cause re-render/flicker

* chore(.gitignore): ignore storageState.json in all directories
chore(storageState.json): delete e2e/storageState.json file

* test(e2e): fix old tests with new playwright setup & add helper script for codegen

* fix(Conversation.jsx): add data-testid attribute to <a> element
test(messages.spec.js): add test for expected navigation after receiving message
test(messages.spec.js): add test for page navigations

* chore(Plugin.jsx): import Spinner from '~/components' instead of '../svg/Spinner'
chore(index.jsx): import Spinner from '~/components' instead of '../svg/Spinner'
chore(Spinner.jsx): change classProp prop to className prop in Spinner component
feat(index.ts): export Spinner component from './Spinner'
2023-07-03 16:00:04 -04:00
Danny Avila
6b843429c5 fix(Content.jsx): enable single dollar text math in remarkMath plugin (#574) 2023-07-03 08:57:43 -04:00
Fuegovic
b89d46dac0 Update mkdocs.yml (#569)
fix the "breaking changes" page 404 error
2023-07-03 01:08:29 -04:00
bsu3338
e61eb7b5bc Create CNAME (#568)
Need to not break custom domain on mkdoc update
2023-07-02 09:48:43 -04:00
Fuegovic
d2ce2ef2cd Fix: Increase password max length and accept '-' for username in regex (#564)
* fix: increase username max length and accept '-' in regex

* fix: increase username max length and accept '-' in regex

* fix: increase username max length and accept '-' in regex
2023-07-01 20:12:45 -04:00
Fuegovic
df2a68e1e7 Docs: updates & enhancements for MKDocs (#555)
* Update documents for mkdocs compatibility

* documents update

* documents update

* Update README.md

* Update README.md

add link to "https://docs.librechat.ai" on the logo

* document updates

* docs - badge updates

* docs - badge updates

* docs - badge updates

* Update docker_install.md

* Update .env.example

update default MONGO_URI to port 27018 so local install can communicate with the docker db

* Update windows_install.md

fix typo
2023-07-01 20:11:37 -04:00
bsu3338
d7270a1676 Update .env.example (#563)
changed to match api/server/routes/config.js
2023-07-01 20:10:53 -04:00
Danny Avila
8f6855116d feat: update compose file to automatically tagged gh container image (#559) 2023-06-27 09:44:51 -04:00
Danny Avila
83eea4825b Release: 0.5.2 (#558) 2023-06-27 09:21:22 -04:00
bsu3338
4a0cf11c90 Add social sites to MkDocs (#554) 2023-06-27 08:57:40 -04:00
bsu3338
5f3266c1eb Update user_auth_system.md (#553)
Changed fqdn to localhost:3080
2023-06-27 08:56:42 -04:00
Marco Beretta
abd1b10b46 Enhanced Documentation: Added Cloudflare and Linode Setup (#549)
* Add files via upload

* Create linode-setup.md

* Create cloudflare-setup.md

* Update cloudflare-setup.md

* Delete 4-linode.png

* Delete 3-linode.png

* Add files via upload

* Add files via upload

* Update cloudflare-setup.md

* Update linode-setup.md

* Rename cloudflare-setup.md to cloudflare.md

* Rename linode-setup.md to linode.md

* Update mkdocs.yml

* Update cloudflare.md

* Update linode.md

* Update README.md

* Update README.md

* Update linode.md

sentence in Italian
2023-06-26 09:23:50 -04:00
Dan Orlando
25211d6f23 fix: #546 issue with closing registration (#547)
* fix: #546 issue with closing registration

* refactor: change casing of controller files for consistency

* fix: ensure registrationEnabled is sending a boolean value

* refactor: modifications to openId code
2023-06-25 15:40:31 -04:00
bsu3338
fdc5265f48 MkDocs for Material (#545)
* Create mkdocs.yaml

* Create mkdocs.yml

* Update mkdocs.yml

* Update mkdocs.yml

* Update mkdocs.yml

* Update mkdocs.yml

* Update mkdocs.yml

* Update README.md

* Update coding_conventions.md

* Update documentation_guidelines.md

* Update testing.md

* Update heroku.md

* Update hetzner_ubuntu.md

* Update google_search.md

* Update introduction.md

* Update make_your_own.md

* Update stable_diffusion.md

* Update wolfram.md

* Update proxy.md

* Update user_auth_system.md

* Update bing_jailbreak_info.md

* Update breaking_changes.md

* Update multilingual_information.md

* Update project_origin.md

* Update tech_stack.md

* Update apis_and_tokens.md

* Update docker_install.md

* Update linux_install.md

* Update mac_install.md

* Update windows_install.md

* Update mkdocs.yml

* Update mkdocs.yml

* Update documentation_guidelines.md

* Add files via upload

* Create temp.txt

* Add files via upload

* Delete logo.png

* Create index.md

* Update mkdocs.yml

* Update mkdocs.yml

* Delete temp.txt

* Update README.md

* Update README.md

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-06-24 23:00:10 -04:00
bsu3338
eceba36f54 OpenID Authentication (#495)
* Squashed commit of the following:

commit 26ab03fb36fcc7fcee63fdf3ae8c2dfb29027eff
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:23:23 2023 -0500

    Update Registration.spec.tsx

commit e908dd82fe9ef1b43c75ee64c183d2f654bdac1c
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:23:01 2023 -0500

    Update Login.spec.tsx

commit 223734820fb77d7fb5af4802af642d1c1fd7c1f5
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:22:39 2023 -0500

    Update Registration.tsx

commit 7036d3dd0538979ee397d958ebc113bb0ea32411
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:21:55 2023 -0500

    Update Login.tsx

commit 76bb78221db3195fd930fe9cfd6a5da7194fa759
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:21:03 2023 -0500

    Update envConstants.js

commit ee2f69f33d75fbb57022afbcd9564bca38a46bee
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:20:08 2023 -0500

    Update docker-compose.yml

commit 5ac72d789b3446884c6e2f4f595cbf67d731d43c
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:18:41 2023 -0500

    Update Dockerfile

commit d24341db2bd5b17eb89ab01e171a5f51f3beab0a
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:16:38 2023 -0500

    Update .env.example

commit 22154f4a09c5fcdfee95d43609fb01a5a883b7a9
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:07:48 2023 -0500

    Update Registration.spec.tsx

commit 5163f7d372a6a03c94f4357b358211a03369456e
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:07:30 2023 -0500

    Update Login.spec.tsx

commit 61da49e330a9376e130b24dc944854f97ab58d80
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:07:00 2023 -0500

    Update Registration.tsx

commit 0e45d3f0dbde34388ff2f0b2dc51b983b472eb05
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:06:18 2023 -0500

    Update Login.tsx

commit dca1e5367e5f3b468c7964218cc5914ca53095af
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:05:07 2023 -0500

    Update envConstants.js

commit f48c058465d82b03716ba85224e9f97007e014d2
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:04:05 2023 -0500

    Update .env.example

commit 818226c9cb079acae4fcbfe5997e4aa9e3c6d2cc
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:59:08 2023 -0500

    Update .env.example

commit 9a805439189b352a38ac7654d7a31bb28f0f58dd
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:58:31 2023 -0500

    Update env.d.ts

commit 3f37ce54758b017c9281b7fad9b040a47630ec66
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:57:04 2023 -0500

    Update .env.example

commit 1026036f4dd529e9531c53084450ce768cfca4c1
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:50:36 2023 -0500

    Update docker-compose.yml

commit a61cf7b8c51d4a9bd73a20bd67abc29891c11463
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:50:00 2023 -0500

    Update Dockerfile

commit 79610d6648755cd5ec45215b9fdbe04ba8242fcf
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:35:34 2023 -0500

    Update package-lock.json

commit e40853fd2b77f2db5be1c3dfd8b170d650e23271
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:30:17 2023 -0500

    Update envConstants.js

commit 5529bc61b43f279fb4418c3851be2f9011b6454d
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:25:58 2023 -0500

    Update docker-compose.yml

commit 07848cc464a64f7cad484e24a1310dc61aa03b18
Merge: ec628a3 72e9828
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:24:03 2023 -0500

    Merge branch 'danny-avila:main' into openid-client

commit ec628a3044ba963b4e733c72229400074e7c2bc4
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:23:16 2023 -0500

    Update envConstants.js

commit 21272221db0f58c244f08335482d45b177d338ab
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:21:59 2023 -0500

    Update Registration.spec.tsx

commit d3f2949c0484d5760e7b689501852f86209992a3
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:21:12 2023 -0500

    Update Login.spec.tsx

commit f2cf23ddd6708a3bb8d032dde5f1ce300dbe8cad
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:20:15 2023 -0500

    Update Registration.tsx

commit 482c346b2a7baf958665c9474223d2557504dee5
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:17:53 2023 -0500

    Update Login.tsx

commit 2f017aa5bf4ef91b73fe027fb346132e1a5d8b87
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:14:17 2023 -0500

    Update env.d.ts

commit addfd95cf93ef19cae05bab652d634af64313e6a
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:13:16 2023 -0500

    Create openidStrategy.js

commit 84c3b5c2f078494d8380f3a02e3ba2d935d8d79f
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:09:02 2023 -0500

    Update oauth.js

commit 63225cdf33b7f42005b4a446797acbd91b7ee4a7
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:07:35 2023 -0500

    Update index.js

commit 6efe4dafd4359ed1c3139468bf9d43f70bbaf6aa
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:04:55 2023 -0500

    Update package.json

commit 201badbbb5a5c8d48f5c4cba3a1349d4cfc7a070
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:03:37 2023 -0500

    Update User.js

commit 7d13d5c303465be9b1268e5f6d9bdf7bb8dfb2e4
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:02:29 2023 -0500

    Update Dockerfile

commit 2ef7f84ea77f281c3dce61211d9fd841a6424e65
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:00:42 2023 -0500

    Update .env.example

* Update openidStrategy.js

* Update .env.example

* Update .env.example

* Update docker-compose.yml

* Update env.d.ts

* Update .env.example

* Update .env.example

* Update config.js

* Update Login.tsx

* Update config.js

* Update Login.tsx

* Update Registration.tsx

* Update docker-compose.yml

* Update openidStrategy.js

* Update docker-compose.yml

* Update config.spec.js

* Update Login.spec.tsx

* Update Registration.spec.tsx

* Update types.ts

* Update .env.example

* Update package-lock.json

* Update openidStrategy.js

* Update openidStrategy.js

* Update config.js

* Update config.js

* Update Login.tsx

* Update Registration.tsx

* Update oauth.js

* Update openidStrategy.js

* Update openidStrategy.js

* Update Registration.tsx

* Update Login.tsx

* Update Login.tsx

* Update Registration.tsx

* Update Registration.tsx

* Update index.js

* Update index.js

* Update .env.example

* Update user_auth_system.md

updated instruction that includes OpenID set up

* Update package.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update openidStrategy.js

* Update openidStrategy.js

Lookup user based on openID instead of email.  This is because not all AzureAD users may have an email tied to their account

* Update openidStrategy.js

First try to match an email, then try openIdID

* Update openidStrategy.js

* Update openidStrategy.js

Consider a family name or given name is not provided

---------

Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
2023-06-24 22:45:52 -04:00
Danny Avila
7efb90366f refactor(initializeFunctionsAgent.js): remove unused code and comments (#544)
feat(initializeFunctionsAgent.js): add support for openai-functions agent type
feat(askGPTPlugins.js): change default agent to functions and skip completion
feat(cleanupPreset.js): change default agent to functions and skip completion
feat(getDefaultConversation.js): change default agent to functions and skip completion
feat(handleSubmit.js): change default agent to functions and skip completion
2023-06-22 20:40:23 -04:00
Fuegovic
731304f96a refactor: update references from chatgpt-clone to LibreChat (#541)
* refactor: update references from chatgpt-clone to LibreChat

* refactor: update references from chatgpt-clone to LibreChat
2023-06-22 20:12:25 -04:00
Shi Jin
3d40dce76a feat: update Dockerfile to include curl (#539)
* Update Dockerfile for include curl

* missing RUN
2023-06-22 12:43:12 -04:00
Danny Avila
d1d7f61fe1 style(SubmitButton.jsx): fix formatting and indentation of code blocks (#540)
feat(SubmitButton.jsx): add z-index to button to ensure it is on top of other elements
2023-06-21 11:13:31 -04:00
Danny Avila
f84da37c9c feat(Functions Agent): use official langchain function executor/agent for better output handling (#538)
* style(FunctionsAgent.js): remove unnecessary comments and update PREFIX variable
refactor(initializeFunctionsAgent.js): update to use initializeAgentExecutorWithOptions
deps(package.json): update langchain to v0.0.95
refactor(askGPTPlugins.js): pass endpointOption to onStart function

* fix(ChatAgent.js): handle undefined delta content in progressMessage.choices array
2023-06-19 14:15:56 -04:00
Shi Jin
49e2cdf76c Create container.yml: build and push docker image upon tagging (#536) 2023-06-18 20:19:08 -04:00
Fuegovic
76e51b8ac5 Update[logo] README.md (#535) 2023-06-18 15:48:35 -04:00
Danny Avila
ac537b96f6 style: mobile optimizations, use fixed dialogs, and prevent auto-scroll for presets (#534)
* style(client): adjust height and add overflow to EditPresetDialog, AgentSettings, and Settings components
style(client): adjust size and add overflow to NewConversationMenu and PresetItems components
style(ui): add overflow to DialogContent component

* style(Settings.jsx): change height of settings component to md:h-[350px] h-[490px]
2023-06-18 11:24:22 -04:00
Danny Avila
4f47da8f0d build(docker-compose.yml): change the image name to librechat (#530)
fix(docker-compose.yml): add target node to build command
2023-06-17 16:41:22 -04:00
Danny Avila
f1f33de4db chore: Update docker, Minor Styling fix (#528)
* chore(docker): add .env and **/.env to .dockerignore
refactor(docker): remove unnecessary .env file copy and removal in Dockerfile

* style(AgentSettings): adjust Switch placement fix(EditPresetDialog): correctly show functions setting in preset
2023-06-17 11:38:48 -04:00
Daniel Avila
9778e73087 style(OptionHover.jsx): add descriptions for new types
feat(AgentSettings.jsx): change OptionHover type from 'temp' to 'func' and from 'temp' to 'skip'
2023-06-16 00:04:35 -04:00
Daniel Avila
4353d42035 feat(tools): add structured Wolfram tool 2023-06-16 00:04:35 -04:00
Daniel Avila
d0be2e6f4a feat(ChatAgent.js): add support for skipping completion mode in ChatAgent
feat(ChatAgent.js): add a check for images when completion is skipped to add to response
feat(askGPTPlugins.js): add skipCompletion option to agentOptions
feat(client): add Switch component to ui components and use for new Agent Settings
chore(package.json): ignore client directory in nodemonConfig
2023-06-16 00:04:35 -04:00
Daniel Avila
5b1efc48d1 refactor(FunctionsAgent.js): remove unnecessary text from PREFIX constant 2023-06-16 00:04:35 -04:00
Daniel Avila
7053d76f48 feat(FunctionsAgent): improve LLM instructions for more reliable results 2023-06-16 00:04:35 -04:00
Daniel Avila
9f930ecf7d fix: remove public/images directory and image created during testing 2023-06-16 00:04:35 -04:00
Daniel Avila
dfec4bfe3a refactor(ChatAgent.js, handlers.js): stringify toolInput object in logs
The `toolInput` object was not being properly logged in the `ChatAgent.js` and `handlers.js` files. The `JSON.stringify()` method was added to properly log the object.
2023-06-16 00:04:35 -04:00
Daniel Avila
7541e9b3d3 refactor(StableDiffusion.js): update prompt and negative_prompt schema descriptions to include minimum number of keywords
fix(StableDiffusion.js): fix output path for generated image
feat(askGPTPlugins.js): update import path for validateTools function
2023-06-16 00:04:35 -04:00
Daniel Avila
bffa9ad016 refactor(handleTools.js): change loadTools function signature to include functions parameter
feat(handleTools.test.js): add test for loading StructuredSD tool with functions parameter
2023-06-16 00:04:35 -04:00
Daniel Avila
d339c291fa refactor(langchain/tools): move availableTools import to tools/index.js 2023-06-16 00:04:35 -04:00
Daniel Avila
a42ef2944c feat(StableDiffusion.js): add StableDiffusionAPI as a StructuredTool for Functions Agent 2023-06-16 00:04:35 -04:00
Daniel Avila
1b3215c55d refactor(tools): restructure tool dir 2023-06-16 00:04:35 -04:00
Daniel Avila
71d812403e refactor(ChatAgent.js, handlers.js): improve logging format and add support for functionsAgent
- Improve logging format in ChatAgent.js by adding more details to the log
- Add support for functionsAgent in ChatAgent.js to format the log differently
- Improve formatAction function in handlers.js to handle empty thoughts and add support for functionsAgent
2023-06-16 00:04:35 -04:00
Daniel Avila
6e183b91e1 refactor(FunctionsAgent.js): change var to const in plan function 2023-06-16 00:04:35 -04:00
Daniel Avila
198f60c536 feat(frontend): add support for agent selection in GPT plugins in adding functions agent
Add support for selecting an agent in the GPT plugins endpoint. The agent can be selected from a dropdown menu in the AgentSettings component. The default agent is set to 'classic' in the cleanupPreset, getDefaultConversation, and handleSubmit functions.
2023-06-16 00:04:35 -04:00
Daniel Avila
3caddd6854 feat(experimental): FunctionsAgent, uses new function payload for tooling 2023-06-16 00:04:35 -04:00
Fuegovic
550e566097 docs: fix/update (#525)
* Update README.md

* Update Hetzner doc

* Update heroku.md

* Update README.md

* Create breaking_changes.md

* Update README.md

* Update breaking_changes.md
2023-06-16 00:02:29 -04:00
Dan Orlando
3634d8691a Feat/startup config api (#518)
* feat: add api for config

* feat: add data service to client

* feat: update client pages with values from config endpoint

* test: update tests

* Update configurations and documentation to remove VITE_SHOW_GOOGLE_LOGIN_OPTION and change VITE_APP_TITLE to APP_TITLE

* include APP_TITLE with startup config

* Add test for new route

* update backend-review pipeline

* comment out test until we can figure out testing routes in CI

* update: .env.example

---------

Co-authored-by: fuegovic <32828263+fuegovic@users.noreply.github.com>
2023-06-15 12:36:34 -04:00
Alex
2da81db440 Update stable_diffusion.md (#523)
* Update stable_diffusion.md

added description for dockerized stable diffusion deployment.

* Update stable_diffusion.md

minor changes to documentation
2023-06-14 10:17:11 -04:00
Alex
3e98486190 fully dockerized development with VS-code devcontainers (#524)
Co-authored-by: bll <bll@tgw-group.com>
2023-06-14 10:14:24 -04:00
Danny Avila
ff2c8e6614 style(Input): remove unnecessary z-index class from input field (#522) 2023-06-13 23:52:17 -04:00
Danny Avila
36a524a630 feat(OpenAI, PaLM): Add model support for new OpenAI models and codechat-bison (#516)
* feat(OpenAI, PaLM): add new models
refactor(chatgpt-client.js): use object to map max tokens for each model
refactor(askChatGPTBrowser.js, askGPTPlugins.js, askOpenAI.js): comment out unused function calls and error handling
feat(askGoogle.js): add support for codechat-bison model
refactor(endpoints.js): add gpt-4-0613 and gpt-3.5-turbo-16k to available models for OpenAI and GPT plugins
refactor(EditPresetDialog.jsx): hide examples for codechat-bison model in google endpoint

style(EndpointOptionsPopover.jsx): add cn utility function import and use it to set additionalButton className

refactor(Google/Settings.jsx): conditionally render custom name and prompt prefix fields based on model type

The code has been refactored to conditionally render the custom name and prompt prefix fields based on the type of model selected. If the model starts with 'codechat-', the fields will not be rendered.

refactor(Settings.jsx): remove duplicated code and wrap a section in a conditional statement based on a variable

style(Input): add z-index to Input component to fix overlapping issue
feat(GoogleOptions): disable Examples button when model starts with 'codechat-' prefix

* feat(.env.example, endpoints.js): add PLUGIN_MODELS environment variable and use it to get plugin models in endpoints.js
2023-06-13 16:42:01 -04:00
heathriel
42583e7344 Create HetznerUbuntuSetup.md (#492)
* Create HetznerUbuntuSetup.md

Step-by-step guide for someone who is starting from scratch on this project with a bare server.

* Updated Readme & Heroku

I submitted the original Heroku.md (to the discord) and they are way out of date. Just corrected them, moved the Hetzner file to the cloud deploy, and updated the readme to point to the file.

* Update HetznerUbuntuSetup.md

* Update README.md
2023-06-13 14:36:13 -04:00
Danny Avila
bccd0cb3dd fix(nodemon): will now follow nodemonConfig in package file (#514) 2023-06-13 14:31:45 -04:00
Alex
07fec3b958 Update .dockerignore (#510)
fixes bug: #505

as described here: https://devpress.csdn.net/cloudnative/63054374c67703293080f1eb.html

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-06-13 14:29:25 -04:00
Fuegovic
4dc3c31df8 docs update (#508)
* doc update: stable_diffusion.md

update docker specific instruction for Stable Diffusion

* doc update: .env.example

* Update README.md

update sponsors list

* Update stable_diffusion.md

* Update stable_diffusion.md

* Update stable_diffusion.md

* Update .env.example

* Update docker-compose.yml

* Update .env.example
2023-06-13 14:27:57 -04:00
Danny Avila
821b507e0e fix(docker): handle .env file to read frontend vars during build easily (#513)
- Remove api/.env and .env from .dockerignore file
- Add COPY .env .env to Dockerfile to copy .env file to docker build
- Add RUN rm .env to Dockerfile to remove .env file after build
- Remove build args from docker-compose.yml file
2023-06-13 12:12:27 -04:00
Danny Avila
9d3e749104 feat(prepare.js): add script to install husky only if NODE_ENV is not 'CI' (#512)
refactor(package.json): change prepare script to run prepare.js script instead of husky install directly
2023-06-13 11:36:54 -04:00
Dan Orlando
2003480fed Change prepare script to not run in CI mode and remove --ignore-scripts flag from workflow (#491)
* Change prepare script to not run in CI mode and remove --ignore-scripts flag from workflow

* add release/* to branches for workflows
2023-06-13 09:20:47 -04:00
Danny Avila
ee52533339 refactor(DALL-E.js): remove unused genAzureEndpoint import and commented code (#506)
feat(DALL-E.js): add getApiKey method to retrieve DALLE_API_KEY from environment variable
2023-06-13 08:58:09 -04:00
Danny Avila
72e9828b76 tests(api): refactor to mock database and network operations (#494) 2023-06-13 00:04:01 -04:00
LaraClara
f92e4f28be bugfix(install): Handle missing .env.example 2023-06-12 20:59:46 -04:00
LaraClara
60bcd7ae49 chore: Add more comments and user messages to create-user cmd 2023-06-12 20:59:46 -04:00
LaraClara
87fa9f9ab0 feature: Create a user from the command line 2023-06-12 20:59:46 -04:00
LaraClara
6c5bea0096 chore: Rename color-console.js to helper.js and add askQuestion as exported fn 2023-06-12 20:59:46 -04:00
LaraClara
c5325cee3a chore: Change package names to be meaningful
- Makes it easier to call them from the outside or even include the package in a different project (ie frontend only)
2023-06-12 20:59:46 -04:00
LaraClara
e726e42cb5 chore: Add color to the install script
- This will only work on terminals setup to use color
2023-06-12 20:59:46 -04:00
LaraClara
4c340fd0ba chore: Add warning if mongodb url looks incorrect in install 2023-06-12 20:59:46 -04:00
LaraClara
cefdd1fb88 feature: Ask for mongodb url during the npm install 2023-06-12 20:59:46 -04:00
Daniel Avila
3ff8d6f90b style(DropdownMenu.tsx): change DropdownMenuRadioItem className to fix styling issue 2023-06-11 16:45:54 -04:00
Daniel Avila
9a965907a2 test(DialogTemplate.spec.tsx): add tests for DialogTemplate component with all props and without optional props. Add test for selectHandler function when select button is clicked. 2023-06-11 16:45:54 -04:00
Daniel Avila
297291c541 feat(frontend-review.yml): add support for dev branch in frontend unit tests workflow 2023-06-11 16:45:54 -04:00
Daniel Avila
46bd04301d fix(client): change moduleResolution value from 'nodenext' to 'node' in tsconfig.json file 2023-06-11 16:45:54 -04:00
Daniel Avila
8c536cb93c test: add unit tests for ClearChatsButton and ThemeSelector components 2023-06-11 16:45:54 -04:00
Daniel Avila
931a89204c refactor(client): convert ClearConvos component from jsx to tsx
refactor(client): convert Dialog, DialogButton, Input, Label, Checkbox, and DialogTemplate components from jsx to tsx
refactor(client): export ClearChatsButton component from General.tsx
refactor(client): add useCallback hook to moveToTop function in Nav component
2023-06-11 16:45:54 -04:00
Daniel Avila
c661276888 refactor(DialogTemplate): convert to typescript
refactor(ui): import multiple components from ui folder in several files
2023-06-11 16:45:54 -04:00
Daniel Avila
544d72ee1d refactor(Settings.jsx) move tabs to separate components, rewrite General in TS, fix react warnings
chore(client): remove unnecessary dependencies from package.json
feat(client): add General tab to Settings component
feat(client): add CogIcon component
refactor(client): move General tab content to separate file
fix(client): fix clearConvosMutation call in Settings component

feat(svg): add CogIcon component
refactor(conversation.js): add useCallback hook to newConversation function
refactor(conversations.js): add useCallback hook to refreshConversations function
chore(tsconfig.json): change jsx option to 'preserve'
2023-06-11 16:45:54 -04:00
Daniel Avila
f171628948 refactor(index.test.js): change let to const for variables that are not reassigned 2023-06-11 16:45:54 -04:00
Daniel Avila
11775915d0 chore(api): update langchain dependency to version 0.0.92 2023-06-11 16:45:54 -04:00
Danny Avila
365fae58ff fix(logout.controller.js): destructure logoutUser function import from auth.service (#482) 2023-06-11 11:24:56 -04:00
Fuegovic
5e3809f22c docs: fix outdated references (#480)
* Update .env.example

* Update README.md

* Update bing_jailbreak_info.md

* Update heroku.md

* Update SECURITY.md

* Update CONTRIBUTING.md

* Update CODE_OF_CONDUCT.md

* Update LICENSE.md

* Update SECURITY.md

* Update coding_conventions.md

* Update documentation_guidelines.md

* Update testing.md

* Update heroku.md

* Update google_search.md

* Update introduction.md

* Update make_your_own.md

* Update stable_diffusion.md

* Update wolfram.md

* Update proxy.md

* Update user_auth_system.md

* Update bing_jailbreak_info.md

* Update multilingual_information.md

* Update project_origin.md

* Update tech_stack.md

* Update apis_and_tokens.md

* Update docker_install.md

* Update linux_install.md

* Update mac_install.md

* Update windows_install.md

* Update install.js
2023-06-11 11:18:32 -04:00
Danny Avila
3dadedaf69 test: frontend jest ci/cd & minor fixes post-release (#478)
* improve final reply for gpt-4, gpt-3.5 needs a more stable approach

* fix: better context output for gpt-3.5

* fix: added clarification for better context output for gpt-3.5

* feat(PluginsOptions): add advanced mode to show/hide options
style(PluginsOptions): add styles for advanced mode and show/hide options

* minor changes to styling

* refactor(langchain): add support for custom GPT-4 agent

This commit adds support for a custom GPT-4 agent in the langchain
module. The `CustomGpt4Agent` class extends the `ZeroShotAgent` class
and includes a new `createPrompt` method that generates a prompt
template for the agent. The `initializeCustomAgent` function has been
updated to use the `CustomGpt4Agent` class when the model is not GPT-3.

The `instructions.js` file has also been updated to include new
instructions for the GPT-4 agent. The `formatInstructions` method has
been removed and replaced with `gpt4Instructions` and `prefix2` and
`suffix2` have been added to include the new instructions.

feat(langchain): add custom output parser for langchain agents

This commit adds a custom output parser for langchain agents. The new parser is called CustomOutputParser and it extends ZeroShotAgentOutputParser. It takes a fields object as a parameter and sets the tools and longestToolName properties. It also sets the finishToolNameRegex property to match the final answer. The parse method of the CustomOutputParser class takes a text parameter and returns an object with returnValues, log, and toolInput properties.

This commit also adds a Gpt4OutputParser class that extends ZeroShotAgentOutputParser. It takes a fields object as a parameter and sets the tools and longestToolName properties. It also sets the finishToolNameRegex property to match the final answer. The parse method of the Gpt4OutputParser class takes a text parameter and returns an object with returnValues, log, and toolInput properties.

feat(langchain): add isGpt3 parameter to

* Stable Diffusion Plugin (#204)

* Added stable diffusion plugin

* Added example prompt

* Fixed naming

* Removed brackets in the prompt

* fix: improved agent for gpt-3.5

* fix: outparser, gpt3 instructions, and wolfram error handling

* chore: update langchain to 0.0.71

* fix: long parsing action input fix

* fix: make plugin select close on clicking label/button

* fix: make plugin select close on clicking label/button

* fix: wolfram input formatting and gpt-3 payload without plugins

* chore(api): update axios package version to 1.3.4
feat(api): add requireJwtAuth middleware to askGPTPlugins endpoint
fix(api): replace session user with user id in askGPTPlugins endpoint

docs(LOCAL_INSTALL.md): update guide for local installation and testing

This commit updates the guide for local installation and testing of the
ChatGPT-Clone app. It includes instructions for locally running the app,
updating the app version, and running tests. It also includes a new
option for running the app using Docker. The commit also fixes some
typos and formatting issues.

* add reverseProxy to plugins client

* chore(Dockerfile-app): add Dockerfile for building and running the app in a container
docs: remove outdated guides on Google search and Bing jailbreak mode

docs(LOCAL_INSTALL.md): remove outdated Windows installation instructions and update MeiliSearch configuration file

* fix: handle n/a parsing error better, reduce token waste if no agentic behavior is needed

* style: fix formatting and add parentheses around arrow function parameter
style: change hover background color to white and dark hover background color to gray-700

* chore: re-organize agent dir and files

* feat(ChatAgent.js): add support for PlanAndExecuteAgentExecutor
feat(PlanAndExecuteAgentExecutor.js): add PlanAndExecuteAgentExecutor class
feat(planExecutor.js): add demo for PlanAndExecuteAgentExecutor

* feat: add azure support to plugins

* refactor(utils): add basePath endpoint for genAzureEndpoint
feat(api): add support for Azure OpenAI API in various modules and tools

* feat: add plugin api for fetching available tools

* feat: add data service for getting available plugins

* feat: first iteration plugin store UI

* refactor: rename files to follow proper naming convention

* feat: Plugin store UI components

* feat: create separate user routes, service, controller, and add plugins to user model

* feat: create data service for adding and removing plugins per user

* feat: UI for adding and removing plugins, displaying plugins in dropdown based on what user has installed

* fix: merge conflicts from main

* fix: fix plugin items titles

* fix: tool.value -> tool.pluginKey

* fix: testing returnDirect for self-reflection

* fix: add browser tool to manifest

* refactor(outputParser.js): remove commented out code
feat(outputParser.js): add support for thought input when there is no action input

* handling 'use tool' edge case

* merge main to langchain

* fix(User.js, auth.service.js, localStrategy.js): change deprecated Joi.validate() to schema.validate() method (#322)

* fix(auth.service.js): fixes deprecated error callback in mongoose save method (#323)

* chore: run formatting script with new rules

* refactor: add requiresAuth to manifest, fix uninstall button

* version with plugin auth as dialog modal

* feat: Complete frontend for plugin auth

* frontend styling updates

* feat: api for plugin auth

* feat: Add tooltip with field description to plugin auth form

* fix: issue with plugin that has no auth

* feat(tools): add support for user-specific API keys

This commit adds support for user-specific API keys for the following tools:
- Google Search API
- Web Browser
- SerpAPI
- Zapier
- DALL-E
- Wolfram Alpha API

It also adds support for OpenAI API key for the Web Browser tool.

The `validateTools` function now takes a `user` parameter and checks for user-specific API keys before falling back to environment variables.

The `loadTools` function now takes a `user` parameter and initializes the tools with user-specific API keys if available.

The `manifest.json` file has been updated to include the new `authConfig` fields for the tools that support user-specific API keys.

The `askGPTPlugins.js` file has been updated to use the `validateTools` function with the `user` parameter.

refactor(ChatAgent.js): add user parameter to initialize function and pass it to loadTools function

refactor(tools/index.js): set default value for tools parameter in validateTools function
refactor(askGPTPlugins.js): remove duplicate user variable declaration and use the one from req object

* refactor(ChatAgent.js): await validTool() before pushing to this.tools array
refactor(tools/index.js): use Map instead of Set to store valid tools
refactor(tools/index.js): filter availableTools to only validate tools passed in
refactor(PluginController.js): filter out duplicate plugins by pluginKey
refactor(crypto.js): use environment variables for encryption key and initialization vector
feat(PluginService.js): add null check for pluginAuth in getUserPluginAuthValue()

* feat(api): add credentials key and IV to .env.example for securely storing credentials

* Adds testing for handling tools, introducing a test env to the backend
Fixes bugs & optimizes code as revealed through testing, including:
- wolfram.js: fixes bug where wolfram was not handling authentication
- ChatAgent.js: ChatAgent modified to reflect 'handleTools' changes
- handleTools.js: Moves logic out of index file
- handleTools.js: loadTools: returns only requested tools
- handleTools.js: validTools: correctly returns tools based on authentication

* test(index.test.js): add test to validate a tool from an environment variable

* test(tools): add test for initializing an authenticated tool through Environment Variables

* refactor(ChatAgent.js): remove commented out code and unused imports

* refactor(ChatAgent.js): move instructions to a separate file and import them
fix(ChatAgent.js): replace hardcoded instructions with imported ones

* refactor(ChatAgent.js): change import path for TextStream
refactor(stream.js): remove unused TextStream class

* chore(.gitignore): add .env.test to gitignore
refactor(ChatAgent.js): rename CustomChatAgent to ChatAgent
test(ChatAgent.test.js): add tests for ChatAgent class
refactor(outputParser.js): remove OldOutputParser class
refactor(outputParser.js): rename CustomOutputParser to OutputParser
docs(.env.test.example): add comment explaining how to use OPENAI_API_KEY
refactor(jestSetup.js): use dotenv to load environment variables from .env.test file

* Various optimizations and config, add tests for PluginStoreDialog

* test(ChatAgent.test.js): add test to check if chat history is returned correctly

* test: unit tests for plugin store

* test: add frontend-test script to root package.json

* feat(ChatAgent.js, askGPTPlugins.js): add support for aborting chat requests (in progress)

* test: add more client tests

* feat(ChatAgent): allow plugin requests to be cancelled

* feat(ChatAgent): allow message regeneration

* feat(ChatAgent): remember last selected tools

* Remove plugins we don't yet have from manifest.json

* fix(ChatAgent.js): increase maxAttempts from 1 to 2
fix(ChatAgent.js): change error message to 'Cancelled.' if message was aborted mid-generation
fix(openaiCreateImage.js): replace unwanted characters in input string
fix(handlers.js): compare action.tool in lowercase to 'self-reflection'

* fix(ChatAgent): Fix up plugin I/O formatting for n/a actions

* refactor(Plugin.jsx): remove unused import statement
feat(Plugin.jsx): add Plugin component with svg paths and styles

* refactor: simplify credential encryption/decryption by using a single key and IV for all environments. Update crypto.js and .env.example files accordingly.

* fix(ChatAgent.js): reduce maxAttempts from 2 to 1
feat(ChatAgent.js): add model information to responseMessage object
feat(Message.js): add model field to messageSchema
feat(Message.js): add model field to message object
feat(Message.jsx): pass model information to getIcon function
feat(getIcon.jsx): add Plugin component and handle plugin messages differently

* feat(askGPTPlugins.js): add model property to the ask function response object
feat(EndpointItem.jsx): add message property to the EndpointItem component
feat(MessageHeader.jsx): add Plugin icon to the plugins section
feat(MessageHeader.jsx): change alpha to beta in the plugins section
feat(svg): add Plugin, GPTIcon, and BingIcon components to the svg folder
refactor(EndpointItems.jsx): remove unused import statement

* refactor(googleSearch.js, wolfram.js): change error handling to return a message instead of throwing an error

* refactor(CustomAgent): remove commented code and change return object to include returnValues property

* feat(CustomAgent.js): add currentDateString to createPrompt method options
deps(api/package.json): update langchain to v0.0.81

* fix: do not show pagination if the maxPage is 1

* Add Zapier back to manifest (accidentally removed)

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

* feat(DALL-E.js): add DALL-E tool for generating images using OpenAI's DALL-E API
refactor(handleTools.js): update import for DALL-E tool
refactor(index.test.js): update import for DALL-E tool
refactor(stablediffusion.js): add check for image directory existence before saving image

* refactor(CustomAgent): rename instructions prefix variable to gpt3 and add gpt4 instructions
feat(CustomAgent): add support for gpt-4 model
fix(initializeCustomAgent.js): pass model name to createPrompt method
fix(outputParser.js): set selectedTool to 'self-reflection' when tool parsing fails

* style(langchain/tools): update guidelines for image creation in DALL-E and StableDiffusion

- Update guidelines for image creation in DALL-E and StableDiffusion tools
- Emphasize the importance of "showing" and not "telling" the imagery in crafting input
- Update formatting for the example prompt for generating a realistic portrait photo of a man
- Generate images only once per human query unless explicitly requested by the user

* docs(tools): update tool descriptions for DALL-E and Stable Diffusion

- Update the description for DALL-E tool to indicate that it is exclusively for visual content and provide guidelines for generating images with a focus on visual attributes.
- Update the description for Stable Diffusion tool to indicate that it is exclusively for visual content and provide guidelines for generating images with a focus on visual attributes.

* chore(api): update "@waylaidwanderer/chatgpt-api" dependency to version "^1.36.3"

* refactor(ChatAgent.js): use environment variable for reverse proxy url
refactor(ChatAgent.js): use environment variable for openai base path
refactor(instructions.js): update gpt3 and gpt3-v2 instructions
refactor(outputParser.js): update finishToolNameRegex in CustomOutputParser class

* refactor(DALL-E.js): change apiKey and azureKey fields to uppercase
refactor(googleSearch.js): change cx and apiKey fields to uppercase
feat(manifest.json): add authConfig field for Stable Diffusion WebUI API URL
refactor(stablediffusion.js): add url field to constructor and change getServerURL() to this.url
refactor(wolfram.js): change apiKey field to uppercase WOLFRAM_APP_ID

* refactor(handleTools.js): simplify tool loading and add support for custom tool constructors and options

* refactor(handleTools.js): remove commented out code and unused imports

* refactor(handleTools.js, index.js): change file name from wolfram.js to Wolfram.js and selfReflection.js to SelfReflection.js to follow PascalCase convention

* refactor(outputParser.js, askGPTPlugins.js): improve code readability and remove unnecessary comments

* feat(GoogleSearch.js): add GoogleSearchAPI tool to allow agents to use the Google Custom Search API
feat(SelfReflection.js): add SelfReflectionTool to allow agents to reflect on their thoughts and actions
feat(StableDiffusion.js): add StableDiffusionAPI tool to allow agents to generate images using stable diffusion webui's api

feat(Wolfram.js): add WolframAlphaAPI tool for computation, math, curated knowledge & real-time data through WolframAlpha.

* testing openai specs

* doc: fix link in .env.example

* package-update

* fix(MultiSelectDropDown.jsx): handle null or undefined values in availableValues array

* refactor(DALL-E.js, StableDiffusion.js): remove 'dist/' from image path
feat(docker-compose.yml): add comments for reverse proxy configuration

* chore(.gitignore): ignore client/public/images/
fix(DALL-E.js, StableDiffusion.js): change image path from dist/ to public/
feat(index.js): add support for serving static files from client/public/ directory

* fix: remove selected tool when uninstalled

* plugin options in progress

* fix: fix issue with uninstalling a plugin that is in use and typescript errors

* feat(gptPlugins): add Preset support for GPT Plugins endpoint
feat(ChatAgent.js): add support for agentOptions object
feat(convoSchema.js): add agentOptions field to conversation schema
feat(defaults.js): add agentOptions object to defaults
feat(presetSchema.js): add agentOptions field to preset schema
feat(askGPTPlugins.js): add support for agentOptions object in request body

feat(EditPresetDialog.jsx): add support for showing/hiding GPT Plugins agent settings
feat(EditPresetDialog.jsx): add support for setting GPT Plugins agent options
fix(EndpointOptionsDialog.jsx): change endpoint name from 'gptPlugins' to 'Plugins'

feat(AgentSettings.jsx): add AgentSettings component for GPT plugins configuration

feat(client): add GPT Plugins settings component and endpoint to Settings component
fix(client): remove unused imports in GoogleOptions component

feat(PluginsOptions): add support for agent settings and refactor code
feat(PluginsOptions): add GPTIcon to show/hide agent settings button
feat(index.ts): export SVG components

feat(GPTIcon.jsx): add className prop to GPTIcon component
feat(GPTIcon.jsx): import cn function from utils
feat(BingIcon.tsx): export BingIcon component
feat(index.ts): export BingIcon component
feat(index.ts): export MessagesSquared component
refactor(cleanupPreset.js): add default values for agentOptions in gptPlugins endpoint

feat(getDefaultConversation.js, handleSubmit.js): add agentOptions object to conversation object for GPT plugins endpoint. Update default temperature value to 0.8. Add chatGptLabel and promptPrefix properties to conversation object.

* fix: set default convo back to null

* refactor(ChatAgent.js, askGPTPlugins.js, AgentSettings.jsx): change variable names for better readability and remove redundant code

* test: add RecoilRoot to layout-test-utils

* refactor(askGPTPlugins.js): remove redundant code and use endpointOption directly
feat(askGPTPlugins.js): add validation for tools in endpointOption before using it

* chore(ChatAgent.js, Settings.jsx): add agentOptions to saveConvo function and adjust Settings component height

The ChatAgent.js file was modified to include the agentOptions object in the saveConvo function. The Settings.jsx file was modified to adjust the height of the component to ensure that all content is visible.

* refactor(ChatAgent.js): extract reverseProxyUrl option to a class property and add support for it
feat(ChatAgent.js): add support for completionMode option in sendApiMessage method
feat(ChatAgent.js): add support for user-provided promptPrefix in buildPrompt method

* feat(plugins): allow preset change mid conversation

* refactor: playwright config

* build: update configs

* test: add automated setup process for playwright

* test: update messages spec to delete message after its been created

* build: add github action for e2e to workflows

* make husky install conditional

* build: try making husky install exit if running in ci

* ignore husky on ci

* chore: update OPENAI_KEY to OPENAI_API_KEY in .github/playwright.yml and api/.env.example
refactor(chatgpt-client.js): update OPENAI_KEY to OPENAI_API_KEY
feat(langchain): add demo-aiplugin.js and demo-yaml.js, remove test2.js, test3.js, and test4.js

chore: remove unused test files
fix(titleConvo.js): fix typo in environment variable name
fix(askGPTPlugins.js): fix typo in environment variable name
fix(endpoints.js): fix typo in environment variable name
docs: update installation guide to use OPENAI_API_KEY instead of OPENAI_KEY in .env file

* fix(index.test.js): change import of GoogleSearchAPI to use uppercase G in GoogleSearch

* backtrack and redo

* add jwt secret

* try manually installing vite

* refactor workflow again

* move vite to root and add frontend:build script

* chore(api): bump langchain version

* feat(PluginController.js): authenticate plugins from environment variables if they are set
feat(PluginStoreDialog.tsx): show plugin auth form only if plugin is not authenticated by env var and require authentication
feat(types.ts): add authenticated field to TPlugin type definition

* docs: update google_search.md and add stable_diffusion.md

* Update stable_diffusion.md

* refactor(Wolfram.js): remove newline characters from query before encoding
docs(wolfram.md): add instructions for setting WOLFRAM_APP_ID in api.env to bypass prompt for AppID in plugin

* refactor(Wolfram.js): replace deprecated replaceAll method with replace method

* Update wolfram.md

* fix(askGPTPlugins): error message will reference correct Parent Message

* refactor(chatgpt-client.js, ChatAgent.js): simplify maxContextTokens calculation and add promptPrefix parameter to buildPrompt method

* docs: initial draft of intro to plugins

* Update introduction.md

* Update introduction.md

* Feature: User/Reg cleanup + Install / Upgrade script for langchain (#427)

* test: login tests

* test: finish login tests

* test: initial tests for registration

* test: registration specs

* feature: Init a app config file
- Simplifies the ENV vars too
- Legacy fallbacks for older builds

* refactor(auth): Refactor log in/out controllers
- Moves both login and logout controllers to their own file

* chore(jwt): Throw warning if secret is default

* feature(frontend): Ability to disable registration

* feature(env): Env in the root + version support
ie .env.prod, .env.dev, .env.test

* feature: Upgrade .env script for users

* chore(config): Refactor and remove legacy env refs

* feature(upgrade): Upgrade script for .env changes

* feature: Install script and upgrade script

* bugfix: Uncomment line to remove old .env file

* chore: rename OPENAI_KEY to OPENAI_API_KEY

* chore: Cleanup config changes/bugs

* bugfix: Fix config and node env issues

* bugfix: Config validation logic

* bugfix: Handle unusual env configs gracefully

* bugfix: Revert route changes and fix registration disable calling

* bugfix: Fix env issues in frontend

* bugfix: Fix login

* bugfix: Fix frontend envs

* bugfix: Fix frontend jest tests

* bugfix: Fix upgrade scripts

* bugfix: Allow install in non-tty envs

* bugfix(windows): Use cross-env to set for windows

* bugfix(env): Handle .env being incorrect to begin with for client domain

* chore(merge-conflict): Update to LibreChat

* chore(merge-conflict): Update to package-lock

---------

Co-authored-by: Daniel D Orlando <dan@danorlando.com>

* chore: comment out unused agent options

* refactor playwright.yml again

* try adding .npmrc

* delete playwright yml

* create new action

* test new playwright action

* Update langchain plugins docs (#461)

* Update: install docs (LibreChat) (#458)

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Update documentation_guidelines.md

* Update introduction.md

add link to readme

* Update stable_diffusion.md

add link back to readme

* Update wolfram.md

add link back to readme

* Update README.md

add Plugins to ToC

* feat(ChatAgent.js): add support for langchainProxy configuration option

Add a new configuration option `langchainProxy` to the ChatAgent class. If the option is set, the `basePath` configuration option of the `ChatOpenAI` instance is set to the base path of `langchainProxy`.

* bugfix(errors): Possible workaround for error flashing (#463)

* Test/user auth system client tests (#462)

* test: login tests

* test: finish login tests

* test: initial tests for registration

* test: registration specs

* modify for new secrets

* modify for new secrets

* fix corrupted package-lock

* remove env vars for testing

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

* Update introduction.md

* Update introduction.md

* Update introduction.md

* run it again

* try externalizing uuid package in rollupoptions to fix build error

* update authenticate script for LibreChat

* add npm install playwright/test to run

* try adding langchain dependency to root package.json to resolve zod error

* add missing env vars

* fix: no longer renders html in markdown content
fix: patch XSS vulnerability completely by handling cursor on the frontend without css/html

* fix(Content.jsx): fix cursor logic so it never shows for static messages

* bugfix(langchain): Upgrade script, docker, env and docs (#465)

* bugfix(errors): Remove incorrect manual fix from misunderstanding

* chore(env): Lets not make a .env.prod and use the prod values in the default root .env
- .env.dev will still be created

* chore(upgrade.js): Lets tell the user about .env.dev if we create it

* bugfix(env): Move to full name environments for vite
- .env.prod => .env.production
- .env.dev => .env.development

* chore(env-example): Explain how to get google login working in production

* bugfix(oauth): Minor fix to point isProduction to a correct value

* bugfix: Typo in public

* chore(docs): Update docs to note the changes to .env

* chore(docs): Include note on how to get google auth working in dev and how to disable registration

* bugfix: Fix missing env changes

* bugfix: Fix up docker to work with new env / npm changes

* Update .env.example

Cleanup the env of the palm2 instruction and fix to formating

* chore(docker): Simplify Docker deployments
- Needs work to support dev env/hotreload

* bugfix: Remove volume map for client dir

* chore(env-example): Change instructions to be more user centric

---------

Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>

* update: install docs (#466)

* Add files via upload

* Update apis-and-tokens.md

* Update apis-and-tokens.md

* Update docker_install.md

* Update linux_install.md

* Rename apis-and-tokens.md to apis_and_tokens.md

* Update docker_install.md

* Update linux_install.md

* Update mac_install.md

* Update linux_install.md

* Update docker_install.md

* Update windows_install.md

* Update apis_and_tokens.md

* Update mac_install.md

* Update linux_install.md

* Update docker_install.md

* Update README.md

* Update README.md : Breaking Changes

---------

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

* Update README.md (#468)

add new API/Token docs to Toc

* docs: guide on how to create your own plugin

* Update make_your_own.md

* Update make_your_own.md

* feat(docker): add build args for frontend variables in Dockerfile
feat(docker-compose): add build args for frontend variables in docker-compose.yml

* Update docker_install.md

* Update docker_install.md

* Update docker_install.md

* Update docker_install.md

* docs: update (#469)

* Update: make_your_own.md

* Update README.md

add `make_your_own.md` to ToC

* Update linux_install.md

* Update mac_install.md

* Update windows_install.md

* Update apis_and_tokens.md

* Update docker_install.md

* Update docker_install.md

* Update linux_install.md

* Update mac_install.md

* Update windows_install.md

* Update apis_and_tokens.md

* Update user_auth_system.md

* Update docker_install.md

clean up of repeated information

* Update docker_install.md

* Update docker_install.md

typo

* fix: fix issue with pluginstore next and prev buttons going out of bounds

* fix: add icon for web browser plugin

* docs(GoogleSearch.js): update description of GoogleSearchAPI class to be more descriptive of its functionality

* feat(ask/handlers.js): add cursor to indicate ongoing progress of a long-running task
fix(Content.jsx): handle null content in the message stream by replacing it with an empty string (with a space so a text space is rendered)

* Update README.md

* Update README.md

* fix: plugin option stacking order

* update: web browser icon (#470)

* Delete web-browser.png

* update: web browser icon

* Update readme (#472)

* Update README.md

Discord badge now displays the number of online users
Project description has been updated to reflect current status
Feature section has been updated to reflect current capabilities
Sponsors section is now located just above the contributors section
Roadmap has been removed as it was outdated.

* Delete roadmap.md

Roadmap has been removed to streamline document maintenance.

* Update README.md

* Update README.md

* Delete CHANGELOG.md

* add frontend unit test action

* action for backend unit test run

* add env vars for backend tests

* add step for install api deps

* use test:ci script

* make test:ci run from api folder

* add script for installing linux x64 sharp

* move jest setup from root to /api

* fix: pluginstore in mobile view getting clipped and not scrolling

* change scripts to test:client and test:api in root package.json

* test api pipeline sharp installation

* remove sharp installation

* last attempt

* revert

* docs(linux_install.md): remove duplicate git clone command

* chore(Dockerfile): comment out nginx-client build stage
docs(README.md): update installation instructions and mention docker-compose changes
docs(features/plugins/introduction.md): bold plugin names and add emphasis to notes

* feat: add superscript and subscript support to markdown rendering
refactor: support markdown citations for BingAI

* refactor: support markdown citations for BingAI

* chore(client): add cross-env package to dependencies
refactor(client): comment out uuid from external rollup options in vite.config.ts

* feat(package-lock.json): add Jest as a devDependency and remove uuidv4 dependency

* feat(workflows): add jest branch to backend and frontend unit tests on push and pull_request events

* chore(workflows): update Node.js version to 19.x in backend and frontend review workflows

* test(PluginStoreDialog.spec.tsx): update test to reflect new pagination

The test was updated to reflect the new pagination. The test now expects to see Plugin 6 and Plugin 7 on the second page instead of Plugin 3, Plugin 4, and Plugin 5. The previous expectations were commented out.

* chore: update package versions to 0.5.0
chore: remove jest branch from backend-review.yml
feat: add support for feat/playwright-jest-cicd branch in backend-review.yml
fix(api): fix sampleTools variable in index.test.js

* fix(Content.jsx): replace escaped whitespace with invisible character

---------

Co-authored-by: David Shin <42793498+dncc89@users.noreply.github.com>
Co-authored-by: Daniel D Orlando <dan@danorlando.com>
Co-authored-by: LaraClara <2524209+ClaraLeigh@users.noreply.github.com>
Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
2023-06-11 00:00:48 -04:00
Danny Avila
e4c91dfbea feat: Plugins endpoint - Reverse Engineering of official Plugins features (#197)
* components for plugins in progress

* WIP: add langchain client implementation for tools/plugins
feat(langchain): add loadHistory function for loading chat history from database
feat(langchain): add saveMessageToDatabase function for saving chat messages to database

* chore(Memory.js): remove Memory.js file from the project directory.

* WIP: adding plugin functionality
——————————————————
fix(eslintrc.js): change arrow-parens rule to always require parentheses

refactor(agent.js): reorganize imports and add new imports
feat(agent.js): add support for saving and loading chat history
feat(agent.js): add support for saving messages to database
feat(agent.js): add ChatAgent class with initialize and sendMessage methods

fix(langchain): use getConvo and saveMessage functions from models.js instead of Conversation and Message models
feat(langchain): add user parameter to loadHistory and saveMessageToDatabase functions
chore(package.json): update langchain package version to 0.0.59 and add langchain script to run test2.js file
——————————————————

* WIP: testing agent initialization

* WIP: testing various agent methods

feat(agent.js): add CustomChatAgent class and initializeAgentExecutorWithOptions method
feat(customChatAgent.js): add CustomPromptTemplate and CustomOutputParser classes

refactor(langchain): uncomment code for input2 and options
feat(langchain): add input1 to read comments on a youtube video
docs(langchain): remove commented code and add whitespace to package.json

* WIP: feat: plugin endpoint, backend class working

* feat(agent.js): add support for Zapier NLA API key
feat(agent.js): add ZapierToolKit to tools if zapierApiKey is provided
feat(customAgent.js): change prompt prefix and suffix to reflect new task-based prompt
feat(test4.js): add test for new task-based prompt

* style(langchain): improve readability and add comments to code
feat(langchain): update prompt message for custom agent
fix(langchain): update message format in test4.js

* style(customAgent.js): remove unnecessary capitalization and rephrase some sentences
test(langchain): add test2 and test3 scripts to package.json

* chore(customAgent.js): fix typo in comment, change "an" to "identical"

* WIP: gpt-4 testing

* feat(langchain): add AIPluginTool and HumanTool classes
fix(langchain): remove zapierApiKey option from ChatAgent constructor
refactor(langchain): update langchain package to v0.0.64
misc(langchain): update test2, test3, and test4 scripts to use --inspect flag

* feat(langchain): add GoogleSearchAPI tool for searching the web using Google Custom Search API

* feat(askGPTPlugins.js): add support for progress callback in ask function
fix(agent.js): pass progress callback to sendApiMessage function

* refactor(agent.js): load tools from options and initialize them in constructor
feat(agent.js): add support for environment variable SERPAPI_API_KEY
feat(agent.js): add support for environment variable ZAPIER_NLA_API_KEY
docs(agent.js): remove commented out code and add comments to clarify code

* chore(langchain): remove unused files loadHistory.js and saveMessage.js

* feat(validateTools.js): add function to validate API keys for supported tools

* feat(langchain): update langchain package to version 0.0.66
feat(langchain): add support for GPT-4 model
fix(server/index.js): fix uncaughtException handler to ignore 'fetch failed' errors

* refactor(agent.js): remove FORMAT_INSTRUCTIONS and replace with a more concise message
refactor(agent.js): remove unused variable 'errorMessage'
refactor(agent.js): change 'result' variable initialization to an empty object instead of null
refactor(agent.js): change error message when response generation fails
refactor(agent.js): change output message when response generation fails
refactor(agent.js): change output message when response generation succeeds

* chore(langchain): comment out unused model in ChatAgent constructor
feat(langchain): add test5 script to package.json for running test5.js script

* refactor(agent.js): change response to answer and update message
refactor(test3.js, test5.js): remove commented out code and add comments

The changes in agent.js are to improve the message that is returned to the user. The word "response" has been changed to "answer" to better reflect the output of the chatbot. The message has also been updated to provide clearer instructions to the user.

The changes in test3.js and test5.js are to remove commented out code and add comments to improve readability.

* docs: update links to LOCAL_INSTALL.md and defaultSystemMessage.md
fix: fix typo in BingAI/Settings.jsx
feat: add Dockerfile for app containerization

docs(google_search.md): add guide for setting up Google Custom Search API key and ID

* docs: update link to system message guidelines in Bing AI Settings component
docs: update link to system message guidelines in GOOGLE_SEARCH.md
feat: add JAILBREAK_INFO.md guide for Bing AI jailbreak mode system message guidelines

* style(api): remove unnecessary quotes and empty values from .env.example
style(agent.js): refactor getActions method to accept an input parameter
feat(agent.js): add handleChainEnd method to CustomChatAgent class
style(customAgent.js): add a new line to the end of the file
style(test5.js): comment out unused variable and update input1 variable
style(googleSearch.js): change tool name to kebab-case

* chore(langchain): comment out handleChainEnd method in agent.js
feat(langchain): add browser tool to ChatAgent in test2.js
feat(langchain): add modelOptions to ChatAgent in test2.js
feat(langchain): change question in input1 and request article review summary in test5.js

* fix(askGPTPlugins.js): fix syntax error by removing extra comma in parentMessageId field
feat(askGPTPlugins.js): add default value of null to parentMessageId parameter in ask function

* fix(askGPTPlugins.js): change endpoint string from 'GPTPlugins' to 'gptPlugins'
feat(endpoints.js): add support for gptPlugins endpoint
feat(PresetItem.jsx): add support for gptPlugins endpoint
feat(HoverButtons.jsx): add support for gptPlugins endpoint
feat(createPayload.ts): add support for gptPlugins endpoint
feat(types.ts): add gptPlugins endpoint to EModelEndpoint enum
feat(endpoints.js): add gptPlugins endpoint to availableEndpoints selector
feat(cleanupPreset.js): add support for gptPlugins endpoint
feat(getDefaultConversation.js): add support for gptPlugins endpoint
feat(getIcon.jsx): add support for gptPlugins endpoint
feat(handleSubmit.js): add support for gptPlugins endpoint

* refactor(agent.js): remove debug option from options object
refactor(agent.js): change tool name from 'google-search' to 'google'
refactor(agent.js): update description for 'google' tool
feat(agent.js): add support for citing sources when using web links in response message
fix(agent.js): update error message to not mention error to user
feat(agent.js): add unique message ids for user message and response message
feat(agent.js): limit number of search results to 5 in 'google' tool
refactor(validateTools.js): add console log to show valid tools

* feat(askGPTPlugins.js): add support for GPT-3.5-turbo model and validate model option
refactor(askGPTPlugins.js): remove unused imports and variables
refactor(askGPTPlugins.js): remove commented code
refactor(askGPTPlugins.js): remove unused parameters in ask function
feat(ask/index.js): add askGPTPlugins route to router

* feat(NewConversationMenu): add alpha tag to gptPlugins endpoint and rename it to Plugins

* refactor(askGPTPlugins.js): remove commented code and unused imports
feat(askGPTPlugins.js): add support for debug option in endpointOption
feat(askGPTPlugins.js): add support for chatGptLabel, promptPrefix, temperature, top_p, presence_penalty, and frequency_penalty in endpointOption
feat(askGPTPlugins.js): add support for sending plugin and pluginend events
feat(askGPTPlugins.js): add onAgentAction and onChainEnd callbacks to ChatAgent.sendMessage
refactor(titleConvo.js): comment out unused imports
refactor(validateTools.js): comment out console.log statement
refactor(agent.js): change saveMessage to include unfinished property
feat(agent.js): add endpoint property to saveConvo call in saveMessageToDatabase
feat(askGPTPlugins.js): add validateTools import and use it to validate endpointOption.tools before passing to ChatAgent constructor
feat(askGPTPlugins.js

* refactor(MessageHeader.jsx): extract plugins section into a separate variable and add support for gptPlugins endpoint
fix(MessageHeader.jsx): disable clicking on non-clickable endpoints

* components for plugins in progress

* feat(Plugin.jsx): add plugin prop to Plugin component and display plugin name
feat(Plugin.jsx): add loading state and display loading spinner
feat(Plugin.jsx): add Disclosure component to Plugin component
feat(Plugin.jsx): add Disclosure.Panel to Plugin component to display team pricing information
feat(Spinner.jsx): add classProp prop to Spinner component to allow for custom styling
feat(Landing.jsx): add Plugin component to Landing page for testing

testing gpt plugins

feat(plugins): Milestone commit

- Add formatAction function to format plugin actions.
- Add prefix.js file to store the prefix message for ChatAgent.
- Update ask function to include plugin object to store plugin data.
- Update onAgentAction and onChainEnd functions to format plugin data and send intermediate messages.
- Update response object to include plugin data.

The `handlers.js` file now includes a `formatAction` function that formats the action object for display in the UI. The `createOnProgress` function now returns a `sendIntermediateMessage` function that sends intermediate messages to the client.

feat (client): add support for plugins in messages

This commit adds support for plugins in messages. It includes changes to the `handlers.js`, `index.jsx`, `CodeBlock.jsx`, `Message.jsx`, `MessageHeader.jsx`, and `Plugin.jsx` files.

The `index.jsx` file now includes a `plugin` property in the `messageHandler` function.

The `CodeBlock.jsx` file now includes a `plugin` property that determines the language of the code block.

The `Message.jsx` file now includes a `Plugin` component that displays the plugin used in the message.

The `MessageHeader.jsx` file now includes a `Plugins` component that displays the enabled plugins.

feat(langchain): add OpenAICreateImage tool for generating images based on user prompts
fix(langchain): update validateTools to include create-image tool
fix(langchain): save plugin data to messageSchema
fix(server/routes/askGPTPlugins.js): save userMessage and response to messageSchema

feat(langchain): add SelfReflectionTool

Add a new tool to the LangChain agent, SelfReflectionTool, which enhances the agent's self-awareness by reflecting on its thoughts before taking action. The tool provides a space for the agent to explore and organize its ideas in response to the user's message.

Also, update the prefix message to reflect the changes in the agent's behavior and the way it should engage with the user. The prefix message now emphasizes the use of tools when necessary, and relying on the agent's knowledge for creative requests. It also provides clear instructions on how to use the 'Action' input and how to carry out tasks in the sequence written by the human.

Finally, update the OpenAICreateImage tool to return the image URL in markdown format. The tool replaces newlines and spaces in the input text with hyphens to create a valid markdown link.

Milestone commit: better error handling with custom output parser, dir and file re-org

style(langchain): fix formatting and add comments to prefix.js
fix(langchain): remove commented out code in test6.js
feat(langchain): reduce maxAttempts from 3 to 2 in CustomChatAgent's buildPromptPrefix method
feat(langchain): add null check for result.output in CustomChatAgent's buildPromptPrefix method

style(langchain): improve consistency and readability of code

This commit improves the consistency and readability of the code in the langchain directory. Specifically, it:

- Changes the case of the "Thought" output in the CustomChatAgent class to match the "Thought" output in the SelfReflectionTool class.
- Adds a currentDateString property to the CustomChatAgent class to avoid repeating the same code in multiple places.
- Updates the prefix in the prefix.js file to match the current objectives of the ChatGPT model.
- Changes the description of the OpenAICreateImage tool to request a description of the image to be generated.
- Updates the tools used by the ChatAgent in the askGPTPlugins.js file to include the Google and Browser tools instead of the Calculator and Create-Image tools.

feat: add wolfram, improve image creation, rename to dall-e

* refactor(langchain): update language and formatting in various files

- Update tool-based instructions to use proper Markdown syntax for image URLs
- Adjust temperature for modelOptions in CustomChatAgent class
- Comment out console.debug statement in CustomChatAgent class
- Update prefix in initializeCustomAgent function to use proper line breaks
- Update prefix in instructions.js to use proper line breaks and change "user" to "human"
- Update input in test6.js to use Ezra Pound instead of Hemingway
- Update return statement in OpenAICreateImage class to use "generated-image" as alt-text
- Update description in SelfReflectionTool class to provide clearer instructions
- Update tools in ask function in askGPTPlugins.js to use only the DALL-E tool and enable debug mode

feat(ask): add support for DALL-E tool in formatAction function
feat(ask): add support for self-reflection tool in formatAction function
feat(Plugin.jsx): add support for self-reflection tool in Plugin component
fix(Plugin.jsx): fix Plugin component to not display 'None' when latest is not available

* docs(openaiCreateImage.js): update tool description to clarify usage

* feat(agent.js): add message parameter to initialize function
feat(agent.js): pass message parameter to SelfReflectionTool constructor
feat(customAgent.js): add longestToolName variable to CustomOutputParser
feat(openaiCreateImage.js): replace new lines with spaces in prompt parameter
feat(selfReflection.js): add message parameter to SelfReflectionTool constructor
feat(selfReflection.js): add placeholder response to selfReflect function

* feat: frontend plugin selection

* fix: agent updates, available tools via endpoint config

* fix: improve frontend plugin selection

* feat: further customize agent and bypass executor when no tools are provided

* fix: key issue in multiselect and allow setting changes during convo in plugins endpoint

* fix: convo will save modelOptions, fix persistent errors with agent

* fix: add looser final answer parsing and edit action formatting

* fix: handle edge case where stop token is not hit and causes long parsing error

* feat: trying new prompt for image creation

* fix: improvements based on gpt-3.5

* feat: allow setting model options throughout plugin conversation

* fix: agent adjustments

* improve final reply for gpt-4, gpt-3.5 needs a more stable approach

* fix: better context output for gpt-3.5

* fix: added clarification for better context output for gpt-3.5

* feat(PluginsOptions): add advanced mode to show/hide options
style(PluginsOptions): add styles for advanced mode and show/hide options

* minor changes to styling

* refactor(langchain): add support for custom GPT-4 agent

This commit adds support for a custom GPT-4 agent in the langchain
module. The `CustomGpt4Agent` class extends the `ZeroShotAgent` class
and includes a new `createPrompt` method that generates a prompt
template for the agent. The `initializeCustomAgent` function has been
updated to use the `CustomGpt4Agent` class when the model is not GPT-3.

The `instructions.js` file has also been updated to include new
instructions for the GPT-4 agent. The `formatInstructions` method has
been removed and replaced with `gpt4Instructions` and `prefix2` and
`suffix2` have been added to include the new instructions.

feat(langchain): add custom output parser for langchain agents

This commit adds a custom output parser for langchain agents. The new parser is called CustomOutputParser and it extends ZeroShotAgentOutputParser. It takes a fields object as a parameter and sets the tools and longestToolName properties. It also sets the finishToolNameRegex property to match the final answer. The parse method of the CustomOutputParser class takes a text parameter and returns an object with returnValues, log, and toolInput properties.

This commit also adds a Gpt4OutputParser class that extends ZeroShotAgentOutputParser. It takes a fields object as a parameter and sets the tools and longestToolName properties. It also sets the finishToolNameRegex property to match the final answer. The parse method of the Gpt4OutputParser class takes a text parameter and returns an object with returnValues, log, and toolInput properties.

feat(langchain): add isGpt3 parameter to

* Stable Diffusion Plugin (#204)

* Added stable diffusion plugin

* Added example prompt

* Fixed naming

* Removed brackets in the prompt

* fix: improved agent for gpt-3.5

* fix: outparser, gpt3 instructions, and wolfram error handling

* chore: update langchain to 0.0.71

* fix: long parsing action input fix

* fix: make plugin select close on clicking label/button

* fix: make plugin select close on clicking label/button

* fix: wolfram input formatting and gpt-3 payload without plugins

* chore(api): update axios package version to 1.3.4
feat(api): add requireJwtAuth middleware to askGPTPlugins endpoint
fix(api): replace session user with user id in askGPTPlugins endpoint

docs(LOCAL_INSTALL.md): update guide for local installation and testing

This commit updates the guide for local installation and testing of the
ChatGPT-Clone app. It includes instructions for locally running the app,
updating the app version, and running tests. It also includes a new
option for running the app using Docker. The commit also fixes some
typos and formatting issues.

* add reverseProxy to plugins client

* chore(Dockerfile-app): add Dockerfile for building and running the app in a container
docs: remove outdated guides on Google search and Bing jailbreak mode

docs(LOCAL_INSTALL.md): remove outdated Windows installation instructions and update MeiliSearch configuration file

* fix: handle n/a parsing error better, reduce token waste if no agentic behavior is needed

* style: fix formatting and add parentheses around arrow function parameter
style: change hover background color to white and dark hover background color to gray-700

* chore: re-organize agent dir and files

* feat(ChatAgent.js): add support for PlanAndExecuteAgentExecutor
feat(PlanAndExecuteAgentExecutor.js): add PlanAndExecuteAgentExecutor class
feat(planExecutor.js): add demo for PlanAndExecuteAgentExecutor

* feat: add azure support to plugins

* refactor(utils): add basePath endpoint for genAzureEndpoint
feat(api): add support for Azure OpenAI API in various modules and tools

* feat: add plugin api for fetching available tools

* feat: add data service for getting available plugins

* feat: first iteration plugin store UI

* refactor: rename files to follow proper naming convention

* feat: Plugin store UI components

* feat: create separate user routes, service, controller, and add plugins to user model

* feat: create data service for adding and removing plugins per user

* feat: UI for adding and removing plugins, displaying plugins in dropdown based on what user has installed

* fix: merge conflicts from main

* fix: fix plugin items titles

* fix: tool.value -> tool.pluginKey

* fix: testing returnDirect for self-reflection

* fix: add browser tool to manifest

* refactor(outputParser.js): remove commented out code
feat(outputParser.js): add support for thought input when there is no action input

* handling 'use tool' edge case

* merge main to langchain

* fix(User.js, auth.service.js, localStrategy.js): change deprecated Joi.validate() to schema.validate() method (#322)

* fix(auth.service.js): fixes deprecated error callback in mongoose save method (#323)

* chore: run formatting script with new rules

* refactor: add requiresAuth to manifest, fix uninstall button

* version with plugin auth as dialog modal

* feat: Complete frontend for plugin auth

* frontend styling updates

* feat: api for plugin auth

* feat: Add tooltip with field description to plugin auth form

* fix: issue with plugin that has no auth

* feat(tools): add support for user-specific API keys

This commit adds support for user-specific API keys for the following tools:
- Google Search API
- Web Browser
- SerpAPI
- Zapier
- DALL-E
- Wolfram Alpha API

It also adds support for OpenAI API key for the Web Browser tool.

The `validateTools` function now takes a `user` parameter and checks for user-specific API keys before falling back to environment variables.

The `loadTools` function now takes a `user` parameter and initializes the tools with user-specific API keys if available.

The `manifest.json` file has been updated to include the new `authConfig` fields for the tools that support user-specific API keys.

The `askGPTPlugins.js` file has been updated to use the `validateTools` function with the `user` parameter.

refactor(ChatAgent.js): add user parameter to initialize function and pass it to loadTools function

refactor(tools/index.js): set default value for tools parameter in validateTools function
refactor(askGPTPlugins.js): remove duplicate user variable declaration and use the one from req object

* refactor(ChatAgent.js): await validTool() before pushing to this.tools array
refactor(tools/index.js): use Map instead of Set to store valid tools
refactor(tools/index.js): filter availableTools to only validate tools passed in
refactor(PluginController.js): filter out duplicate plugins by pluginKey
refactor(crypto.js): use environment variables for encryption key and initialization vector
feat(PluginService.js): add null check for pluginAuth in getUserPluginAuthValue()

* feat(api): add credentials key and IV to .env.example for securely storing credentials

* Adds testing for handling tools, introducing a test env to the backend
Fixes bugs & optimizes code as revealed through testing, including:
- wolfram.js: fixes bug where wolfram was not handling authentication
- ChatAgent.js: ChatAgent modified to reflect 'handleTools' changes
- handleTools.js: Moves logic out of index file
- handleTools.js: loadTools: returns only requested tools
- handleTools.js: validTools: correctly returns tools based on authentication

* test(index.test.js): add test to validate a tool from an environment variable

* test(tools): add test for initializing an authenticated tool through Environment Variables

* refactor(ChatAgent.js): remove commented out code and unused imports

* refactor(ChatAgent.js): move instructions to a separate file and import them
fix(ChatAgent.js): replace hardcoded instructions with imported ones

* refactor(ChatAgent.js): change import path for TextStream
refactor(stream.js): remove unused TextStream class

* chore(.gitignore): add .env.test to gitignore
refactor(ChatAgent.js): rename CustomChatAgent to ChatAgent
test(ChatAgent.test.js): add tests for ChatAgent class
refactor(outputParser.js): remove OldOutputParser class
refactor(outputParser.js): rename CustomOutputParser to OutputParser
docs(.env.test.example): add comment explaining how to use OPENAI_API_KEY
refactor(jestSetup.js): use dotenv to load environment variables from .env.test file

* Various optimizations and config, add tests for PluginStoreDialog

* test(ChatAgent.test.js): add test to check if chat history is returned correctly

* test: unit tests for plugin store

* test: add frontend-test script to root package.json

* feat(ChatAgent.js, askGPTPlugins.js): add support for aborting chat requests (in progress)

* test: add more client tests

* feat(ChatAgent): allow plugin requests to be cancelled

* feat(ChatAgent): allow message regeneration

* feat(ChatAgent): remember last selected tools

* Remove plugins we don't yet have from manifest.json

* fix(ChatAgent.js): increase maxAttempts from 1 to 2
fix(ChatAgent.js): change error message to 'Cancelled.' if message was aborted mid-generation
fix(openaiCreateImage.js): replace unwanted characters in input string
fix(handlers.js): compare action.tool in lowercase to 'self-reflection'

* fix(ChatAgent): Fix up plugin I/O formatting for n/a actions

* refactor(Plugin.jsx): remove unused import statement
feat(Plugin.jsx): add Plugin component with svg paths and styles

* refactor: simplify credential encryption/decryption by using a single key and IV for all environments. Update crypto.js and .env.example files accordingly.

* fix(ChatAgent.js): reduce maxAttempts from 2 to 1
feat(ChatAgent.js): add model information to responseMessage object
feat(Message.js): add model field to messageSchema
feat(Message.js): add model field to message object
feat(Message.jsx): pass model information to getIcon function
feat(getIcon.jsx): add Plugin component and handle plugin messages differently

* feat(askGPTPlugins.js): add model property to the ask function response object
feat(EndpointItem.jsx): add message property to the EndpointItem component
feat(MessageHeader.jsx): add Plugin icon to the plugins section
feat(MessageHeader.jsx): change alpha to beta in the plugins section
feat(svg): add Plugin, GPTIcon, and BingIcon components to the svg folder
refactor(EndpointItems.jsx): remove unused import statement

* refactor(googleSearch.js, wolfram.js): change error handling to return a message instead of throwing an error

* refactor(CustomAgent): remove commented code and change return object to include returnValues property

* feat(CustomAgent.js): add currentDateString to createPrompt method options
deps(api/package.json): update langchain to v0.0.81

* fix: do not show pagination if the maxPage is 1

* Add Zapier back to manifest (accidentally removed)

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

* feat(DALL-E.js): add DALL-E tool for generating images using OpenAI's DALL-E API
refactor(handleTools.js): update import for DALL-E tool
refactor(index.test.js): update import for DALL-E tool
refactor(stablediffusion.js): add check for image directory existence before saving image

* refactor(CustomAgent): rename instructions prefix variable to gpt3 and add gpt4 instructions
feat(CustomAgent): add support for gpt-4 model
fix(initializeCustomAgent.js): pass model name to createPrompt method
fix(outputParser.js): set selectedTool to 'self-reflection' when tool parsing fails

* style(langchain/tools): update guidelines for image creation in DALL-E and StableDiffusion

- Update guidelines for image creation in DALL-E and StableDiffusion tools
- Emphasize the importance of "showing" and not "telling" the imagery in crafting input
- Update formatting for the example prompt for generating a realistic portrait photo of a man
- Generate images only once per human query unless explicitly requested by the user

* docs(tools): update tool descriptions for DALL-E and Stable Diffusion

- Update the description for DALL-E tool to indicate that it is exclusively for visual content and provide guidelines for generating images with a focus on visual attributes.
- Update the description for Stable Diffusion tool to indicate that it is exclusively for visual content and provide guidelines for generating images with a focus on visual attributes.

* chore(api): update "@waylaidwanderer/chatgpt-api" dependency to version "^1.36.3"

* refactor(ChatAgent.js): use environment variable for reverse proxy url
refactor(ChatAgent.js): use environment variable for openai base path
refactor(instructions.js): update gpt3 and gpt3-v2 instructions
refactor(outputParser.js): update finishToolNameRegex in CustomOutputParser class

* refactor(DALL-E.js): change apiKey and azureKey fields to uppercase
refactor(googleSearch.js): change cx and apiKey fields to uppercase
feat(manifest.json): add authConfig field for Stable Diffusion WebUI API URL
refactor(stablediffusion.js): add url field to constructor and change getServerURL() to this.url
refactor(wolfram.js): change apiKey field to uppercase WOLFRAM_APP_ID

* refactor(handleTools.js): simplify tool loading and add support for custom tool constructors and options

* refactor(handleTools.js): remove commented out code and unused imports

* refactor(handleTools.js, index.js): change file name from wolfram.js to Wolfram.js and selfReflection.js to SelfReflection.js to follow PascalCase convention

* refactor(outputParser.js, askGPTPlugins.js): improve code readability and remove unnecessary comments

* feat(GoogleSearch.js): add GoogleSearchAPI tool to allow agents to use the Google Custom Search API
feat(SelfReflection.js): add SelfReflectionTool to allow agents to reflect on their thoughts and actions
feat(StableDiffusion.js): add StableDiffusionAPI tool to allow agents to generate images using stable diffusion webui's api

feat(Wolfram.js): add WolframAlphaAPI tool for computation, math, curated knowledge & real-time data through WolframAlpha.

* testing openai specs

* doc: fix link in .env.example

* package-update

* fix(MultiSelectDropDown.jsx): handle null or undefined values in availableValues array

* refactor(DALL-E.js, StableDiffusion.js): remove 'dist/' from image path
feat(docker-compose.yml): add comments for reverse proxy configuration

* chore(.gitignore): ignore client/public/images/
fix(DALL-E.js, StableDiffusion.js): change image path from dist/ to public/
feat(index.js): add support for serving static files from client/public/ directory

* fix: remove selected tool when uninstalled

* plugin options in progress

* fix: fix issue with uninstalling a plugin that is in use and typescript errors

* feat(gptPlugins): add Preset support for GPT Plugins endpoint
feat(ChatAgent.js): add support for agentOptions object
feat(convoSchema.js): add agentOptions field to conversation schema
feat(defaults.js): add agentOptions object to defaults
feat(presetSchema.js): add agentOptions field to preset schema
feat(askGPTPlugins.js): add support for agentOptions object in request body

feat(EditPresetDialog.jsx): add support for showing/hiding GPT Plugins agent settings
feat(EditPresetDialog.jsx): add support for setting GPT Plugins agent options
fix(EndpointOptionsDialog.jsx): change endpoint name from 'gptPlugins' to 'Plugins'

feat(AgentSettings.jsx): add AgentSettings component for GPT plugins configuration

feat(client): add GPT Plugins settings component and endpoint to Settings component
fix(client): remove unused imports in GoogleOptions component

feat(PluginsOptions): add support for agent settings and refactor code
feat(PluginsOptions): add GPTIcon to show/hide agent settings button
feat(index.ts): export SVG components

feat(GPTIcon.jsx): add className prop to GPTIcon component
feat(GPTIcon.jsx): import cn function from utils
feat(BingIcon.tsx): export BingIcon component
feat(index.ts): export BingIcon component
feat(index.ts): export MessagesSquared component
refactor(cleanupPreset.js): add default values for agentOptions in gptPlugins endpoint

feat(getDefaultConversation.js, handleSubmit.js): add agentOptions object to conversation object for GPT plugins endpoint. Update default temperature value to 0.8. Add chatGptLabel and promptPrefix properties to conversation object.

* fix: set default convo back to null

* refactor(ChatAgent.js, askGPTPlugins.js, AgentSettings.jsx): change variable names for better readability and remove redundant code

* test: add RecoilRoot to layout-test-utils

* refactor(askGPTPlugins.js): remove redundant code and use endpointOption directly
feat(askGPTPlugins.js): add validation for tools in endpointOption before using it

* chore(ChatAgent.js, Settings.jsx): add agentOptions to saveConvo function and adjust Settings component height

The ChatAgent.js file was modified to include the agentOptions object in the saveConvo function. The Settings.jsx file was modified to adjust the height of the component to ensure that all content is visible.

* refactor(ChatAgent.js): extract reverseProxyUrl option to a class property and add support for it
feat(ChatAgent.js): add support for completionMode option in sendApiMessage method
feat(ChatAgent.js): add support for user-provided promptPrefix in buildPrompt method

* feat(plugins): allow preset change mid conversation

* chore: update OPENAI_KEY to OPENAI_API_KEY in .github/playwright.yml and api/.env.example
refactor(chatgpt-client.js): update OPENAI_KEY to OPENAI_API_KEY
feat(langchain): add demo-aiplugin.js and demo-yaml.js, remove test2.js, test3.js, and test4.js

chore: remove unused test files
fix(titleConvo.js): fix typo in environment variable name
fix(askGPTPlugins.js): fix typo in environment variable name
fix(endpoints.js): fix typo in environment variable name
docs: update installation guide to use OPENAI_API_KEY instead of OPENAI_KEY in .env file

* fix(index.test.js): change import of GoogleSearchAPI to use uppercase G in GoogleSearch

* chore(api): bump langchain version

* feat(PluginController.js): authenticate plugins from environment variables if they are set
feat(PluginStoreDialog.tsx): show plugin auth form only if plugin is not authenticated by env var and require authentication
feat(types.ts): add authenticated field to TPlugin type definition

* docs: update google_search.md and add stable_diffusion.md

* Update stable_diffusion.md

* refactor(Wolfram.js): remove newline characters from query before encoding
docs(wolfram.md): add instructions for setting WOLFRAM_APP_ID in api.env to bypass prompt for AppID in plugin

* refactor(Wolfram.js): replace deprecated replaceAll method with replace method

* Update wolfram.md

* fix(askGPTPlugins): error message will reference correct Parent Message

* refactor(chatgpt-client.js, ChatAgent.js): simplify maxContextTokens calculation and add promptPrefix parameter to buildPrompt method

* docs: initial draft of intro to plugins

* Update introduction.md

* Update introduction.md

* Feature: User/Reg cleanup + Install / Upgrade script for langchain (#427)

* test: login tests

* test: finish login tests

* test: initial tests for registration

* test: registration specs

* feature: Init a app config file
- Simplifies the ENV vars too
- Legacy fallbacks for older builds

* refactor(auth): Refactor log in/out controllers
- Moves both login and logout controllers to their own file

* chore(jwt): Throw warning if secret is default

* feature(frontend): Ability to disable registration

* feature(env): Env in the root + version support
ie .env.prod, .env.dev, .env.test

* feature: Upgrade .env script for users

* chore(config): Refactor and remove legacy env refs

* feature(upgrade): Upgrade script for .env changes

* feature: Install script and upgrade script

* bugfix: Uncomment line to remove old .env file

* chore: rename OPENAI_KEY to OPENAI_API_KEY

* chore: Cleanup config changes/bugs

* bugfix: Fix config and node env issues

* bugfix: Config validation logic

* bugfix: Handle unusual env configs gracefully

* bugfix: Revert route changes and fix registration disable calling

* bugfix: Fix env issues in frontend

* bugfix: Fix login

* bugfix: Fix frontend envs

* bugfix: Fix frontend jest tests

* bugfix: Fix upgrade scripts

* bugfix: Allow install in non-tty envs

* bugfix(windows): Use cross-env to set for windows

* bugfix(env): Handle .env being incorrect to begin with for client domain

* chore(merge-conflict): Update to LibreChat

* chore(merge-conflict): Update to package-lock

---------

Co-authored-by: Daniel D Orlando <dan@danorlando.com>

* chore: comment out unused agent options

* Update langchain plugins docs (#461)

* Update: install docs (LibreChat) (#458)

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Update documentation_guidelines.md

* Update introduction.md

add link to readme

* Update stable_diffusion.md

add link back to readme

* Update wolfram.md

add link back to readme

* Update README.md

add Plugins to ToC

* feat(ChatAgent.js): add support for langchainProxy configuration option

Add a new configuration option `langchainProxy` to the ChatAgent class. If the option is set, the `basePath` configuration option of the `ChatOpenAI` instance is set to the base path of `langchainProxy`.

* bugfix(errors): Possible workaround for error flashing (#463)

* Test/user auth system client tests (#462)

* test: login tests

* test: finish login tests

* test: initial tests for registration

* test: registration specs

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

* Update introduction.md

* Update introduction.md

* Update introduction.md

* fix: no longer renders html in markdown content
fix: patch XSS vulnerability completely by handling cursor on the frontend without css/html

* fix(Content.jsx): fix cursor logic so it never shows for static messages

* bugfix(langchain): Upgrade script, docker, env and docs (#465)

* bugfix(errors): Remove incorrect manual fix from misunderstanding

* chore(env): Lets not make a .env.prod and use the prod values in the default root .env
- .env.dev will still be created

* chore(upgrade.js): Lets tell the user about .env.dev if we create it

* bugfix(env): Move to full name environments for vite
- .env.prod => .env.production
- .env.dev => .env.development

* chore(env-example): Explain how to get google login working in production

* bugfix(oauth): Minor fix to point isProduction to a correct value

* bugfix: Typo in public

* chore(docs): Update docs to note the changes to .env

* chore(docs): Include note on how to get google auth working in dev and how to disable registration

* bugfix: Fix missing env changes

* bugfix: Fix up docker to work with new env / npm changes

* Update .env.example

Cleanup the env of the palm2 instruction and fix to formating

* chore(docker): Simplify Docker deployments
- Needs work to support dev env/hotreload

* bugfix: Remove volume map for client dir

* chore(env-example): Change instructions to be more user centric

---------

Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>

* update: install docs (#466)

* Add files via upload

* Update apis-and-tokens.md

* Update apis-and-tokens.md

* Update docker_install.md

* Update linux_install.md

* Rename apis-and-tokens.md to apis_and_tokens.md

* Update docker_install.md

* Update linux_install.md

* Update mac_install.md

* Update linux_install.md

* Update docker_install.md

* Update windows_install.md

* Update apis_and_tokens.md

* Update mac_install.md

* Update linux_install.md

* Update docker_install.md

* Update README.md

* Update README.md : Breaking Changes

---------

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

* Update README.md (#468)

add new API/Token docs to Toc

* docs: guide on how to create your own plugin

* Update make_your_own.md

* Update make_your_own.md

* feat(docker): add build args for frontend variables in Dockerfile
feat(docker-compose): add build args for frontend variables in docker-compose.yml

* Update docker_install.md

* Update docker_install.md

* Update docker_install.md

* Update docker_install.md

* docs: update (#469)

* Update: make_your_own.md

* Update README.md

add `make_your_own.md` to ToC

* Update linux_install.md

* Update mac_install.md

* Update windows_install.md

* Update apis_and_tokens.md

* Update docker_install.md

* Update docker_install.md

* Update linux_install.md

* Update mac_install.md

* Update windows_install.md

* Update apis_and_tokens.md

* Update user_auth_system.md

* Update docker_install.md

clean up of repeated information

* Update docker_install.md

* Update docker_install.md

typo

* fix: fix issue with pluginstore next and prev buttons going out of bounds

* fix: add icon for web browser plugin

* docs(GoogleSearch.js): update description of GoogleSearchAPI class to be more descriptive of its functionality

* feat(ask/handlers.js): add cursor to indicate ongoing progress of a long-running task
fix(Content.jsx): handle null content in the message stream by replacing it with an empty string (with a space so a text space is rendered)

* Update README.md

* Update README.md

* fix: plugin option stacking order

* update: web browser icon (#470)

* Delete web-browser.png

* update: web browser icon

* Update readme (#472)

* Update README.md

Discord badge now displays the number of online users
Project description has been updated to reflect current status
Feature section has been updated to reflect current capabilities
Sponsors section is now located just above the contributors section
Roadmap has been removed as it was outdated.

* Delete roadmap.md

Roadmap has been removed to streamline document maintenance.

* Update README.md

* Update README.md

* Delete CHANGELOG.md

* fix: pluginstore in mobile view getting clipped and not scrolling

* docs(linux_install.md): remove duplicate git clone command

* chore(Dockerfile): comment out nginx-client build stage
docs(README.md): update installation instructions and mention docker-compose changes
docs(features/plugins/introduction.md): bold plugin names and add emphasis to notes

* feat: add superscript and subscript support to markdown rendering
refactor: support markdown citations for BingAI

* refactor: support markdown citations for BingAI

---------

Co-authored-by: David Shin <42793498+dncc89@users.noreply.github.com>
Co-authored-by: Daniel D Orlando <dan@danorlando.com>
Co-authored-by: LaraClara <2524209+ClaraLeigh@users.noreply.github.com>
Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
2023-06-10 19:10:03 -04:00
Fuegovic
aaa20309a0 Update: install docs (LibreChat) (#458)
* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Release: rename project from ChatGPT Clone to LibreChat

Release: rename project from ChatGPT Clone to LibreChat

* Update documentation_guidelines.md
2023-06-06 07:44:53 -04:00
Danny Avila
8c4a3b2729 bump version to 0.4.8 (#455) 2023-06-05 14:43:51 -04:00
Danny Avila
638faf9850 Release: rename project from ChatGPT Clone to LibreChat in various files and configurations (#454) 2023-06-05 14:24:08 -04:00
Danny Avila
f845192d2d Update README.md 2023-06-05 14:12:11 -04:00
Danny Avila
dfd93909e8 Update README.md 2023-06-05 14:11:59 -04:00
Danny Avila
47d0184990 npm all prod(deps): bump mdast-util-from-markdown from 1.3.0 to 1.3.1 (#447) (#453)
Bumps [mdast-util-from-markdown](https://github.com/syntax-tree/mdast-util-from-markdown) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/syntax-tree/mdast-util-from-markdown/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-from-markdown/compare/1.3.0...1.3.1)

---
updated-dependencies:
- dependency-name: mdast-util-from-markdown
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 12:15:53 -04:00
Danny Avila
ee59fa40f5 npm all prod(deps): bump @babel/plugin-transform-react-jsx (#446) (#452)
Bumps [@babel/plugin-transform-react-jsx](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-react-jsx) from 7.21.5 to 7.22.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.3/packages/babel-plugin-transform-react-jsx)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-react-jsx"
  dependency-type: indirect
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 12:10:41 -04:00
Danny Avila
68b3731d06 npm all prod(deps): bump micromark-extension-gfm-task-list-item (#445) (#451)
Bumps [micromark-extension-gfm-task-list-item](https://github.com/micromark/micromark-extension-gfm-task-list-item) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/micromark/micromark-extension-gfm-task-list-item/releases)
- [Commits](https://github.com/micromark/micromark-extension-gfm-task-list-item/compare/1.0.4...1.0.5)

---
updated-dependencies:
- dependency-name: micromark-extension-gfm-task-list-item
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 12:05:58 -04:00
Danny Avila
e4889ff8bb npm all prod(deps): bump postcss-double-position-gradients (#444) (#450)
Bumps [postcss-double-position-gradients](https://github.com/csstools/postcss-plugins/tree/HEAD/plugins/postcss-double-position-gradients) from 4.0.3 to 4.0.4.
- [Changelog](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-double-position-gradients/CHANGELOG.md)
- [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/plugins/postcss-double-position-gradients)

---
updated-dependencies:
- dependency-name: postcss-double-position-gradients
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 12:01:23 -04:00
Danny Avila
2c026d11a5 npm all prod(deps): bump @babel/helper-member-expression-to-functions (#443) (#449)
Bumps [@babel/helper-member-expression-to-functions](https://github.com/babel/babel/tree/HEAD/packages/babel-helper-member-expression-to-functions) from 7.21.5 to 7.22.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.3/packages/babel-helper-member-expression-to-functions)

---
updated-dependencies:
- dependency-name: "@babel/helper-member-expression-to-functions"
  dependency-type: indirect
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 11:55:15 -04:00
Danny Avila
1a252170f5 chore(package): bump meilisearch (#448)
* npm api prod(deps): bump meilisearch from 0.32.5 to 0.33.0 in /api (#436)

Bumps [meilisearch](https://github.com/meilisearch/meilisearch-js) from 0.32.5 to 0.33.0.
- [Release notes](https://github.com/meilisearch/meilisearch-js/releases)
- [Commits](https://github.com/meilisearch/meilisearch-js/compare/v0.32.5...v0.33.0)

---
updated-dependencies:
- dependency-name: meilisearch
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(package): update meilisearch package from 0.32.3 to 0.33.0
chore(package): update cross-fetch package from 3.1.5 to 3.1.6 in meilisearch package dependencies

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 11:48:05 -04:00
Danny Avila
d40aaa703d chore: change dependabot settings for all packages (#442) 2023-06-05 11:22:14 -04:00
Danny Avila
44005258fc chore: create develop branch and change dependabot settings (#435) 2023-06-05 10:35:04 -04:00
Danny Avila
19495a461d chore(.gitignore): add .env.test to gitignore (#424)
feat(api): update @waylaidwanderer/chatgpt-api to version 1.37.0
2023-06-03 08:23:11 -04:00
Danny Avila
fcf068dddf style(NewConversationMenu): change dropdown menu background color to dark gray (#419)
style(EndpointItem): change active background color to light gray
2023-06-02 00:35:43 -04:00
Anirudh
7468b3011f Added Settings Modal (#342)
* Improve UI with style changes and add Settings button

- Improved the UI of the `Input` and `Message` components.
- Added a `Settings` button to the `NavLinks` component.
- Introduced a `Settings` component to handle user settings.
- Refactored the `Dialog` component for consistency.

* Revert not needed changes

* Updated style.css to only work for select

* feat: Remove Dark Mode component and add theme selection feature

This commit removes the Dark Mode component from the navigation bar and replaces it with a theme selection dropdown menu in the Settings dialog. The implementation of the theme selection feature includes a function that allows the user to set the theme based on the system, light, or dark mode.

* Add auto theme setting to Settings component.

This commit adds a new state variable to keep track of whether the auto theme is enabled or not. It also registers an event listener to update the theme based on system preference changes. The event listener is removed when the component is unmounted.

* Improve user experience by allowing customized themes
- Create `selectedOption` state to track user-selected theme
- Remove unused `isAutoTheme` state variable

* feat(Nav): Add SVG icon to settings gear

This commit adds an SVG icon to the settings gear in the Navigation component's Settings file. The new SVG icon replaces the previous GearIcon component.

* refactor(ui): Update overlay background color

This commit updates the background color of the overlay in the AlertDialog and Dialog components by changing the classes applied to the elements. The new color is a transition from `bg-black/50 backdrop-blur-sm` to `bg-gray-500/90 dark:bg-gray-800/90`. This change improves the readability of the dialog boxes.

* Refactor ThemeContext to include system theme and fix bug in Settings

The ThemeContext now includes a "system" theme and ClearConvos no longer relies on the "selected option" state to update the theme. The bug is now fixed if the system theme changes.

* Refactor DialogTemplate styles and color scheme

Adjusted the color scheme of the DialogTemplate component to dark mode, updated the background color to gray-900 and removed unnecessary classes.

* Refactor: Change button logic to require confirmation before clearing convos

This commit refactors the code by adding a confirmation dialog to prompt for a user's confirmation before clearing all conversations in the Settings.jsx file. The change ensures the user is aware of the irreversible action before initiating the clearConvos function. Additionally, the commit updates the clear chat button's class name and changes the button's onClick logic to call the confirmClearConvos function instead of directly invoking the clearConvos method.

* Refactor component name to reflect functionality change.

- Changed component name from ClearConvos to Settings to support potential future use cases.

* Refactor conversation clearing functionality in `Settings.jsx`

This commit optimizes the conversation clearing functionality in the `Settings.jsx` component by removing the `confirmClearConvos` function and directly calling the `clearConvos` function on confirmation. This change will simplify the code and improve the user experience.

* Refactor Input component UI styles

Simplify Input component styles by simplifying the gradient background, removing border color styles, and updating button styles.

* feat: Add e2e test for Settings modal

This commit adds an e2e test to verify whether the Settings modal is displayed on the landing page. It uses a headless browser to navigate to the page and interacts with it to verify if the dialog and its components are visible.

* test: Add Navigation and Settings tests

Add Navigation and Settings tests to verify that the navigation bar and Settings button are visible and that the Settings modal displays the expected content. The settings modal verification includes checking whether the modal is visible, if the modal title, tab list, clear conversation button and theme are present, and if the theme option can be selected to change the mode.

* Quick fix

* feat(navbar): Add confirmation before clearing conversations

Adding confirmation modal to prevent accidentally clearing conversations. Before, once you clicked on the "Clear" button it immediately clears all conversations. With this change, if you click on "Clear" the first time, it will change the text to "Confirm Clear" and if you click it again, it will clear all conversations.

* Add click functionality to the navigation bar and improve UI design

The code introduced click functionality to the nav bar and improved the user interface. It also used the new theme select feature to change the theme to dark.

* test: Add test for dark mode theme change

Refactor the test for Navigation suite to check for the 'dark' class in the HTML element when the 'dark' theme is selected in the modal. This ensures that the dark mode theme change works correctly, and improves test coverage.

* Improve navigation test clarity

This commit improves code clarity and adds more detailed test assertions to the navigation suite. New assert statements are added to check whether the modal theme selection changes the theme and that the HTML element receives the 'dark' class. A new function `changeMode` was introduced to avoid code repetition. A short description was added to the commit message to adhere to best practices.

* Improve navigation test clarity

This commit improves code clarity and adds more detailed test assertions to the navigation suite. New assert statements are added to check whether the modal theme selection changes the theme and that the HTML element receives the 'dark' class. A new function `changeMode` was introduced to avoid code repetition. A short description was added to the commit message to adhere to best practices.

* Hotfix

* Removed repetation

* Refactor: Change text-gray-400 to text-white/50 to make tailwind more cleaner

* style: Update CSS classes to improve the conversation UI

- Update Conversation component to improve UX
- Changed styling for group hover effect using shades of gray
- Improved color contrast of the Message component for easy readability
- Replaced class names in buildTree.js with a new class name
- Added a new color theme (gray-1000) in tailwind.config to replace an old background color.

* Refactor EndpointItem, EndpointItems, and NewConversationMenu for better user experience

- The `EndpointItem` component now accepts an `isSelected` prop instead of `onSelect` to better reflect its usage in `EndpointItems` and `NewConversationMenu`.
- `EndpointItems` component now has a `selectedEndpoint` prop to highlight the selected item in the list.
- `NewConversationMenu` now has a gap between the endpoint options to improve user experience.

* Added error messages

* refactor: Improve endpoint menu highlighting and error handling

In the UI, when the user selects an endpoint, the active class is now properly set. In the error handling function, `isJson` is now a private function called by `getError`, which provides better parsing of error messages, and returns more succinct messages upon encountering specific errors. Finally, a new end-to-end test has been added to check if the active class is properly set on selecting an endpoint in the new conversation menu.

* test: Add Conversation and Change Path of Auth JSON

In the Landing spec, test the functionality to create conversations and check that the number of items has increased. In the Popup spec, change the path of the Auth JSON used by the context.

* Fixed logo issues

* Make everything not rounded

* Added time

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-06-02 00:32:35 -04:00
Anirudh
dade7b450f feat: Add clear button to search bar (#328)
* feat: Add clear button to search bar

This commit introduces a clear button to the SearchBar component using the X icon from Lucide-React. When the user enters a query in the input field, the clear button appears allowing them to easily remove the search term. The clear button is hidden when there is no search term entered.

* Refactor SearchBar component to improve user experience

Changed SearchBar's input field to add padding on the left side and an absolute positioned search icon. Also, added absolute positioned X icon on the right side when there is an input value, ensuring a better user experience.

* Refactor SearchBar component to show Clear Search icon dynamically

This commit makes changes to the SearchBar React component to render the Clear Search X icon only when the input field has a value. A showClearIcon state using useState hook is added and updated every time the input value changes. The useEffect hook is used to handle the case when the user clears the input value. This allows better UX by providing clear intent to the user that the icon is clickable and will clear the search query.

* Improve UX: Add styling to clear button & export button

This commit modifies the NavLinks component to improve user experience by removing a rounded styling to the "Clear conversations" and "Export conversations" buttons. Prior to this change, the buttons had a rounded styling.

* Refactor submit button styling for improved accessibility and readability.

Changed submit button styling for better accessibility and readability, including adjustments to padding and hover effects. The new styles ensure that the button is easily clickable for all users, while also improving its visual appearance.

* hotfix

* Improve UI styling in Conversation component

Changed the background color and hover effect of the conversation link in Conversation component to make it more visually appealing. The previous background color was '#2A2B32' and now it's 'gray-800'. The 'px-4' class has also been changed to 'hover:pr-4' for better readability.

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-06-02 00:11:34 -04:00
Danny Avila
7fbf27c5aa chore(.gitignore): add client/public/images/ to ignore list (#417)
refactor(chatgpt-client.js): free encoder memory after use
feat(chatgpt-client.tokens.js): add script to test memory usage of ChatGPTClient
2023-06-02 00:08:19 -04:00
Fuegovic
4705975e59 feat:add hyperlink to bing.com in SetTokenDialog (#414) 2023-05-31 00:41:01 -04:00
Danny Avila
2f59c82bec chore(api): update chatgpt-api package version to 1.36.3 (#404)
docs(api): update BINGAI_TOKEN instructions in .env.example
docs(client): update BINGAI_TOKEN instructions in SetTokenDialog component
2023-05-29 11:00:51 -04:00
Fuegovic
6a34978e98 Fix: typo and phrasing (#393)
* Update FEATURE-REQUEST.yml

Fix typo and phrasing

* Update pull_request_template.md

add one option to type of change
2023-05-28 17:55:57 -04:00
Fuegovic
d437e4b8cd update: "documents" folder to "docs" (#391)
* Rename .github/PULL_REQUEST_TEMPLATE/PULL-REQUEST.md to .github/pull_request_template.md

fix: Pull Request Template Location

* documents -> docs

* Update windows_install.md

Fix: Docker hyperlink

* Update linux_install.md

Fix: Layout (step 6)

* Rename docs/contributions/code_of_conduct.md to CODE_OF_CONDUCT.md

fix: Code of Conduct location according to GitHub's Guide

* Update CODE_OF_CONDUCT.md

Update: Contact info

* Update README.md

Update: Code of Conduct hyperlink in TOC

* Update CODE_OF_CONDUCT.md

Update: Link to ReadMe

* Update CONTRIBUTORS.md

update: add new name to the list

* Update and rename docs/contributions/contributor_guidelines.md to CONTRIBUTING.md

fix: change location according to GitHub's standards

* Delete CONTRIBUTORS.md

delete: contributor.md from root (already present in readme)

* Update SECURITY.md

* Update CONTRIBUTING.md

Update discord link to point to rules

* Update README.md

Update discord link to point to rules

* Update README.md

fix: ToC
2023-05-27 07:03:28 -04:00
Fuegovic
f40a2f8ee8 update: documentation (#389)
* Update docker_install.md

update Bing Token instructions

* Update linux_install.md

Update Bing Token Instructions
Add # markers to sections

* Update mac_install.md

Update Bing Token Instructions
Fix Formating
Recommend Docker

* Update windows_install.md

Update Bing Token Instructions

* Update linux_install.md

Recommend Docker

* Create QUESTION.yml

Questions Template

* Update QUESTION.yml

fix syntax

* Update QUESTION.yml

* Update QUESTION.yml

* Create FEATURE-REQUEST

* Rename FEATURE-REQUEST to FEATURE-REQUEST.yml

add file extension
2023-05-26 22:22:11 -04:00
Danny Avila
2d31c9f8b6 chore: bump package versions to 0.4.7 (#388) 2023-05-26 17:56:23 -04:00
Danny Avila
fd5afc09a2 chore(tests): add e2e tests for messaging suite (#387)
* feat(NewConversationMenu): add id to the new conversation menu button
refactor(EndpointItem): remove onSelect prop and setTokenDialogOpen state variable
test(messages.spec.js): add e2e test for messaging suite to check if textbox is focused after receiving message

* test(Input): add test id to input field for e2e testing
test(messages.spec.js): add endpoint variable and refactor test to check if textbox is focused after receiving message

* test(messages.spec.js): refactor test to use a variable for message content

Refactored the test to use a variable for message content instead of a hardcoded string.
2023-05-26 17:34:08 -04:00
Danny Avila
c0845ad0b1 Fix Input losing focus (#382)
* fix(PaLM2): input losing focus on message stream ending

* fix(askOpenAI.js): fix typo in variable name from newUserMassageId to newUserMessageId

* feat(chatgpt-browser.js, askBingAI.js, askChatGPTBrowser.js): add onEventMessage callback to browserClient

Add onEventMessage callback to browserClient to handle event messages from the server. In askChatGPTBrowser.js, add a getPartialMessage variable to store the partial message text. In askBingAI.js, fix a typo in the variable name newUserMassageId to newUserMessageId. In askChatGPTBrowser.js, remove the preSendRequest parameter and move the sendMessage call to the onEventMessage callback. In askChatGPTBrowser.js, add a check for null or undefined value of getPartialMessage before appending it to the error message.

* fix(bing): input no longer loses input focus as convoId is persisted from beginning of convo

* refactor(Input): remove unused code and fix input autofocus
feat(package.json): add e2e:test-auth script to test authentication flow with saved storage
2023-05-26 14:32:13 -04:00
Danny Avila
11b98d3d13 refactor(chatgpt-client.js): initialize usage object with empty object instead of null (#386)
refactor(chatgpt-client.js): simplify usage object assignment
2023-05-26 09:43:35 -04:00
Danny Avila
4f17e69f1b fix(tokenizer): error handle encoding for invalid encoding data (#385) 2023-05-26 09:40:08 -04:00
Danny Avila
b912e7a3dd Update BUG-REPORT.yml 2023-05-26 09:02:32 -04:00
Danny Avila
743a9315ff Update BUG-REPORT.yml 2023-05-26 09:00:33 -04:00
Danny Avila
ea2135a237 chore(api): remove unused crypto dependency from package.json (#381) 2023-05-25 14:54:46 -04:00
Dan Orlando
6a1983bc6c refactor: remove bcrypt (#375) 2023-05-25 14:54:24 -04:00
Fuegovic
07796d9e48 Update BUG-REPORT.yml (#379)
remove other tags than "bug" from the bug report
2023-05-25 13:17:03 -04:00
Danny Avila
634849ec12 fix(Bing): Use full cookies string instead of just _U cookie (#369) 2023-05-23 13:58:18 -04:00
Danny Avila
112c6c5b19 fix (PaLM2): messages will properly regenerate (#368)
* making progress to fix regen for PaLM

* fix (PaLM2): messages will properly regenerate
2023-05-23 06:55:23 -04:00
Olivier Contant
b8c3ae5e8f Update SECURITY.md (#367)
Include OWASP reference to Vulnerability Disclosure process.
2023-05-23 06:41:01 -04:00
Danny Avila
07fa0f39fd Fix (PaLM2): Persist PaLM presets after initial message (#366)
* refactor(askGoogle.js): extract saveConvo function call to a separate function
feat(askGoogle.js): add endpoint property to the conversation object
refactor(handleSubmit.js): rename chatGptLabel to modelLabel in useMessageHandler function

* refactor(askGoogle.js): remove unused endpointOption spread operator
2023-05-22 20:50:10 -04:00
Dan Orlando
4eda4542b7 feat: Setup Unit Test Environment and Refactor Typescript Config (#365)
* modify tsconfig and set up unit tests

* generate .d.ts files

* setup project dependencies and configuration for unit tests

* Add test setup and layout-test-utils along with first spec

* Add paths back to tsconfig

* remove type=module from package.json

* Add typescript definition for .env

* update package-lock
2023-05-22 20:49:48 -04:00
dependabot[bot]
dbfef342e2 npm all prod(deps): bump fast-redact from 3.1.2 to 3.2.0 (#360)
Bumps [fast-redact](https://github.com/davidmarkclements/fast-redact) from 3.1.2 to 3.2.0.
- [Release notes](https://github.com/davidmarkclements/fast-redact/releases)
- [Commits](https://github.com/davidmarkclements/fast-redact/compare/v3.1.2...v3.2.0)

---
updated-dependencies:
- dependency-name: fast-redact
  dependency-type: indirect
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 13:28:19 -04:00
Danny Avila
735eb159db npm client dev(deps-dev): bump @vitejs/plugin-react from 3.1.0 to 4.0.0 (#364) 2023-05-22 13:23:17 -04:00
Danny Avila
bf911074cf npm client prod(deps): bump @types/node from 18.16.14 to 20.2.3 (#363) 2023-05-22 12:33:52 -04:00
dependabot[bot]
7ec061c694 npm all prod(deps): bump @csstools/postcss-oklab-function (#356)
Bumps [@csstools/postcss-oklab-function](https://github.com/csstools/postcss-plugins/tree/HEAD/plugins/postcss-oklab-function) from 2.2.1 to 2.2.2.
- [Changelog](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-oklab-function/CHANGELOG.md)
- [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/plugins/postcss-oklab-function)

---
updated-dependencies:
- dependency-name: "@csstools/postcss-oklab-function"
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 12:21:55 -04:00
Danny Avila
a6f3eb4c0d npm client prod(deps): bump esbuild from 0.17.15 to 0.17.19 (#361) 2023-05-22 12:17:34 -04:00
dependabot[bot]
dc5f9d8474 npm all prod(deps): bump eslint from 8.40.0 to 8.41.0 (#354)
Bumps [eslint](https://github.com/eslint/eslint) from 8.40.0 to 8.41.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.40.0...v8.41.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 12:10:50 -04:00
dependabot[bot]
c1349fbfaa npm all prod(deps): bump tar from 6.1.14 to 6.1.15 (#353)
Bumps [tar](https://github.com/isaacs/node-tar) from 6.1.14 to 6.1.15.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.1.14...v6.1.15)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 12:04:41 -04:00
Olivier Contant
6f9da5f7df Update coding_conventions.md (#350)
Fixed typo missing letter.
2023-05-22 00:09:33 -04:00
Danny Avila
10de50416b feat(HoverButtons.jsx): enable message regeneration for bingAI endpoint (#349)
feat(HoverButtons.jsx): add active class to copy button only if message is not created by user
2023-05-21 14:01:46 -04:00
Danny Avila
4beb06aa4b Minor fixes: tokenizer, default Bing toneStyle, SiblingSwitch (#348)
* fix: tokenizer will count completion tokens correctly, remove global var, will allow unofficial models for alternative endpoints

* refactor(askBingAI.js, Settings.jsx, types.ts, cleanupPreset.js, getDefaultConversation.js, handleSubmit.js): change default toneStyle to 'creative' instead of 'fast' for Bing AI endpoint.

* fix(SiblingSwitch): correctly appears now
style(HoverButtons.jsx): add 'active' class to hover buttons
2023-05-21 12:43:06 -04:00
Danny Avila
791b515937 Cleanup root dir, move dev-related files into /documents/ (#347)
* chore: cleanup root dir and move extraneous dev related files to documents/dev

* chore: cleanup root dir and move extraneous dev related files to documents/dev
2023-05-21 08:56:06 -04:00
Fuegovic
8d4ef16b7f docs : update the documentation (#345)
* Add files via upload

* Delete documents/report_templates directory

* Update PR-TEMPLATE.md

* Update README.md

removed templates from TOC

* Update SECURITY.md

- update to follow documentation guidelines
- update discord link to point to issues

* Update SECURITY.md

* Update README.md

add security to TOC

* Delete pull_request_template.md

moved to .github

* Rename PR-TEMPLATE.md to PULL-REQUEST.md

* Update mac_install.md

clean up and update

* Update windows_install.md

fix formating and change update instructions

* Update windows_install.md

add docker recommendation

* Update windows_install.md

* Update mac_install.md
2023-05-21 08:42:16 -04:00
Danny Avila
5964b71e14 Setup tests with new user system (#344)
* chore(.gitignore): add auth.json to gitignore
test(landing.spec.js): remove commented out code and add check for landing page title
test(login.spec.js): add test for login page title
feat(package.json): add e2e:auth script to generate auth.json storage file for e2e tests

* test(landing.spec.js): add beforeEach hook to create a new browser context with auth.json storage state
test(landing.spec.js): change test name from 'landing page' to 'Landing title'
fix(package.json): change e2e:auth script to save auth.json in e2e directory
2023-05-20 09:00:45 -04:00
Danny Avila
8c7ad09977 style(mobile.css): decrease z-index of .nav-mask to 35 (#337) 2023-05-19 21:09:07 -04:00
Danny Avila
cef2668f53 style(NewConversationMenu): add z-index to Dialog and DropdownMenuContent (#335)
style(mobile.css): decrease z-index of .nav to 40
2023-05-19 19:58:53 -04:00
Danny Avila
ab7cfc6041 Hotfix (#334)
* style(NavLinks.jsx): add 'as="div"' to Menu.Item components
refactor(Nav.jsx): remove unused code and add isMobile function to check if user is on mobile device

* conditionally render menuitem with search

---------

Co-authored-by: stunt_pilot <twitchstuntpilot@gmail.com>
2023-05-19 19:37:56 -04:00
Danny Avila
a9444b66a1 Release 0.4.6 (#332) 2023-05-19 16:21:45 -04:00
Danny Avila
ec561fcd7f Fixes all Nav Menu related errors and bugs (#331)
* chore(client): update lucide-react package to version 0.220.0
style(client): change color of MessageHeader component text to gray-500
style(client): change color of nav-close-button to gray-400 and nav-open-button to gray-500
feat(client): add Panel component to replace svg icons in Nav component

* fix: forwardRef errors in Nav Menu

* refactor(SearchBar.jsx): change clearSearch prop destructuring to props destructuring
refactor(SearchBar.jsx): add ref prop to SearchBar component
refactor(getIcon.jsx): remove unused imports
refactor(getIcon.jsx): add nullish coalescing operator to user.name and user.avatar properties

* fix (NavLinks): modals no longer close on nav menu close

* style(ExportModel.jsx): remove unnecessary z-index property from a div element

* style(ExportModel.jsx): remove trailing whitespace in input element

* refactor(Message.jsx): remove unused cancelled variable
fix(Message.jsx): fix error message length exceeding 512 characters
refactor(MenuItem.jsx): remove unused MenuItem component
2023-05-19 16:02:41 -04:00
Anirudh
ee2b3e4fb2 Refactor UI styles & configurations (#324)
* Refactor UI styles & configurations

-  Modify button styles and their color schemes to create a consistent user experience when interacting with buttons.
-  Adjust the design of the search bar to a more user-friendly layout by changing its background color and styling.
-  Create a responsive mobile behavior for the navigation bar to hide it behind a menu icon instead of permanently displaying it.

* Update .gitignore to exclude unnecessary files for Meilisearch

Update .gitignore to exclude meilisearch.exe and data.ms/*, which are not necessary for Meilisearch.

* feat: Add getCurrentBreakpoint function to get current breakpoint

This commit adds a getCurrentBreakpoint function to determine the current breakpoint of the viewport. The function uses fullConfig to determine the biggest breakpoint value of the window, and returns the corresponding breakpoint. It also updates the useEffect function to use getCurrentBreakpoint instead of checking if the userAgent matches a mobile regex.

* Update tailwind import path in Nav component

The import path for the tailwind config was updated in the Nav component to match the new project structure. This ensures that the correct Tailwind styles are applied to the component and improves maintainability.

* Add ThemeContext and cn utility function to Nav component

This commit adds the ThemeContext and cn utility function to the Nav component's dependencies with useContext and import respectively. It also modifies a class name with a ternary operator that toggles based on the theme value passed via ThemeContext.

* Update Nav button styles for better visibility

Changed the button styles for the Nav close and open buttons to enhance visibility. The text color for both buttons will now change when hovering to gray and gray-600 respectively.

* Improve message header styles and add transition effects

This commit updates the MessageHeader component styles by adjusting the text color, as well as adding transition effects to enhance the hover experience. The commit also tweaks mobile styles by adding a transition effect to `.nav` when resizing the window to mobile size.

* Refactor the message header component styling for better visual contrast

The message header component was refactored to improve its visual contrast by changing the text color for better readability. The styles of the component were modified to improve hover behavior as well as transition effects. The setSaveAsDialogShow method was shifted to the onClick prop to only execute when the endpoint is not 'chatGPTBrowser'.

* refactor: Update styling of MessageHeader and Nav buttons

The commit message describes changes made to the MessageHeader and Nav components. It summarizes the code changes as a refactor of the CSS styling for the buttons in both components, specifically updating the text and hover colors for the dark and light themes.
2023-05-19 10:51:34 -04:00
Danny Avila
67716f0d2d fix(auth.service.js): fixes deprecated error callback in mongoose save method (#323) 2023-05-18 20:08:35 -04:00
Danny Avila
e56d90e45a fix(User.js, auth.service.js, localStrategy.js): change deprecated Joi.validate() to schema.validate() method (#322) 2023-05-18 17:39:06 -04:00
Danny Avila
92eee52c52 feat (presets): hide/show endpoints, increase preset menu size in general and dynamic to endpoints (#320) 2023-05-18 16:01:16 -04:00
Danny Avila
f4d995be4c chore: dependabot updates (#319) 2023-05-18 15:32:03 -04:00
dependabot[bot]
fbdfbdd620 npm all prod(deps): bump react-router-dom from 6.11.1 to 6.11.2 (#308)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.11.1 to 6.11.2.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.11.2/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:28:36 -04:00
dependabot[bot]
fec733e10b npm client dev(deps-dev): bump source-map-loader in /client (#307)
Bumps [source-map-loader](https://github.com/webpack-contrib/source-map-loader) from 1.1.3 to 4.0.1.
- [Release notes](https://github.com/webpack-contrib/source-map-loader/releases)
- [Changelog](https://github.com/webpack-contrib/source-map-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/source-map-loader/compare/v1.1.3...v4.0.1)

---
updated-dependencies:
- dependency-name: source-map-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:28:19 -04:00
dependabot[bot]
23905dd344 npm all prod(deps): bump jake from 10.8.5 to 10.8.6 (#306)
Bumps [jake](https://github.com/jakejs/jake) from 10.8.5 to 10.8.6.
- [Changelog](https://github.com/jakejs/jake/blob/main/changelog.md)
- [Commits](https://github.com/jakejs/jake/compare/v10.8.5...v10.8.6)

---
updated-dependencies:
- dependency-name: jake
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:27:58 -04:00
dependabot[bot]
ec13d74b84 npm client prod(deps): bump class-variance-authority in /client (#304)
Bumps [class-variance-authority](https://github.com/joe-bell/cva) from 0.4.0 to 0.6.0.
- [Release notes](https://github.com/joe-bell/cva/releases)
- [Commits](https://github.com/joe-bell/cva/compare/v0.4.0...v0.6.0)

---
updated-dependencies:
- dependency-name: class-variance-authority
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:27:40 -04:00
dependabot[bot]
231906161b npm client dev(deps-dev): bump typescript from 4.9.5 to 5.0.4 in /client (#303)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.5 to 5.0.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.5...v5.0.4)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:27:20 -04:00
dependabot[bot]
5c787035e5 npm all prod(deps): bump remark-parse from 10.0.1 to 10.0.2 (#302)
Bumps [remark-parse](https://github.com/remarkjs/remark) from 10.0.1 to 10.0.2.
- [Release notes](https://github.com/remarkjs/remark/releases)
- [Changelog](https://github.com/remarkjs/remark/blob/main/changelog.md)
- [Commits](https://github.com/remarkjs/remark/compare/remark-parse@10.0.1...remark-parse@10.0.2)

---
updated-dependencies:
- dependency-name: remark-parse
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:27:00 -04:00
dependabot[bot]
2694690ed0 npm all prod(deps): bump cross-fetch from 3.1.5 to 3.1.6 (#301)
Bumps [cross-fetch](https://github.com/lquixada/cross-fetch) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/lquixada/cross-fetch/releases)
- [Changelog](https://github.com/lquixada/cross-fetch/blob/v3.1.6/CHANGELOG.md)
- [Commits](https://github.com/lquixada/cross-fetch/compare/v3.1.5...v3.1.6)

---
updated-dependencies:
- dependency-name: cross-fetch
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:26:39 -04:00
dependabot[bot]
562bf8c920 npm api prod(deps): bump joi from 14.3.1 to 17.9.2 in /api (#300)
Bumps [joi](https://github.com/hapijs/joi) from 14.3.1 to 17.9.2.
- [Commits](https://github.com/hapijs/joi/compare/v14.3.1...v17.9.2)

---
updated-dependencies:
- dependency-name: joi
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:26:14 -04:00
dependabot[bot]
2781154df3 npm all prod(deps): bump mongoose from 6.11.1 to 7.1.1 (#299)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.11.1 to 7.1.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.11.1...7.1.1)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:25:55 -04:00
dependabot[bot]
691b6d9029 npm api prod(deps): bump mongoose from 6.11.1 to 7.1.1 in /api (#298)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.11.1 to 7.1.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.11.1...7.1.1)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:25:36 -04:00
dependabot[bot]
c6c3054c22 npm client prod(deps): bump filenamify from 5.1.1 to 6.0.0 in /client (#297)
Bumps [filenamify](https://github.com/sindresorhus/filenamify) from 5.1.1 to 6.0.0.
- [Release notes](https://github.com/sindresorhus/filenamify/releases)
- [Commits](https://github.com/sindresorhus/filenamify/compare/v5.1.1...v6.0.0)

---
updated-dependencies:
- dependency-name: filenamify
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 15:25:17 -04:00
Danny Avila
d71b61ad71 minor fixes (#318)
* refactor(SearchBar.jsx): extract onChange function to a separate function and add onKeyDown event listener to prevent spacebar from propagating

* refactor(SearchBar.jsx): extract onChange function to a separate function and add onKeyDown event listener to prevent spacebar from propagating

* refactor(SearchBar.jsx): remove unused React import statement
2023-05-18 15:22:48 -04:00
Dan Orlando
47533736e3 fix: turn off react-in-jsx-scope rule (#317) 2023-05-18 15:12:19 -04:00
Dan Orlando
a17b878617 refactor: reformat files to require parens around params (#316) 2023-05-18 14:44:07 -04:00
dependabot[bot]
91ef4872d6 npm api prod(deps): bump meilisearch from 0.31.1 to 0.32.3 in /api (#296)
Bumps [meilisearch](https://github.com/meilisearch/meilisearch-js) from 0.31.1 to 0.32.3.
- [Release notes](https://github.com/meilisearch/meilisearch-js/releases)
- [Commits](https://github.com/meilisearch/meilisearch-js/compare/v0.31.1...v0.32.3)

---
updated-dependencies:
- dependency-name: meilisearch
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-18 14:30:23 -04:00
Danny Avila
c1ddd07166 chore(EditPresetDialog.jsx): fix formatting and linting issues
feat(Settings.jsx): change max-height of the settings dialog to fit the content better
2023-05-18 14:20:41 -04:00
Dan Orlando
7fdc862042 Build/Refactor: lint pre-commit hook and reformat repo to spec (#314)
* build/refactor: move lint/prettier packages to project root, install husky, add pre-commit hook

* refactor: reformat files

* build: put full eslintrc back with all rules
2023-05-18 14:09:31 -04:00
Danny Avila
8d75b25104 Fixes (#313)
* refactor(endpoints.js): remove console.log statement
refactor(index.html): change title to "ChatGPT Clone"

* feat(Chat.jsx): set document title to conversation title or VITE_APP_TITLE or 'Chat' if conversation is null
2023-05-18 07:45:07 -04:00
Danny Avila
26152d7e5f feat(api): add support for user-provided OpenAI API key (#311)
- Add support for user-provided OpenAI API key by setting OPENAI_KEY to
  "user_provided" in .env.example
- Pass oaiApiKey to titleConvo function in titleConvo.js
- Pass oaiApiKey to askClient function in askOpenAI.js
- Modify openAI object in endpoints.js to include userProvide property
  based on whether OPENAI_KEY is set to "user_provided" or not.
2023-05-17 21:58:56 -04:00
John Chen
61a4231feb fix duplicate instructions (#310) 2023-05-17 20:18:50 -04:00
Olivier Contant
c9b035a0bd Docs/security guideline (#295)
* Create dependabot.yml

Initial dependabot.yml

* Create SECURITY.md

Guideline for security researcher to report vulnerabilities and communicate the discovery to our project community.

* Update SECURITY.md

Change wording for Discord channel initial contact and added Github Issues guideline.
2023-05-17 19:23:58 -04:00
dncc89
44ea3601c9 feat: Frontend app title environment variable (#291)
* Add app name change support

* fix indentation
2023-05-17 19:23:13 -04:00
Pawan Kumar
782a899ab3 calculate and add token usage to streaming chat (#287) 2023-05-17 19:22:35 -04:00
Anirudh
14104b276f Added functionality to allow users to set custom api keys (#276)
* Added functionality to allow users to set custom api keys

* Added error handling

* Changed token to apiKey

* Changed apiKey to oaiApiKey

* added azure openai ui

* Removed logging

* Changed configure to Use

* Made checked position more rounded

* Made setting api key optional if it is openai

* Modified error handling

* Add support for insufficient_quota errors

* Fixed faulty error detection

* removed logging
2023-05-17 19:21:30 -04:00
Danny Avila
08f3a77d58 Update README.md 2023-05-17 14:48:47 -04:00
Danny Avila
ca26732cb8 Update docker_install.md 2023-05-17 09:58:10 -04:00
Danny Avila
dbf45196ee Release 0.4.5 (#282)
* Release 0.4.5

* Update @waylaidwanderer/node-chatgpt-api to latest version
* Update dockerfiles to use workspaces and ensure packages are @ latest
* Remove package-lock.json files from workspace directories as no longer needed

* refactor(api): remove deprecated text-davinci-002-render-paid model from CHATGPT_MODELS
refactor(api/client): change model comparison to use startsWith() instead of === for GPT-4 models
2023-05-16 14:30:24 -04:00
Fuegovic
45a2aaf7b8 docs : add basic info document in multiple languages (#285)
* Create multilingual_information.md

add a multilingual document with basic information about the project for non-native English speakers

* Update README toc to add multilingual info

add the multilingual info doc to the table of content (under General Information)
2023-05-16 13:30:52 -04:00
Dan Orlando
c02c62f3b1 fix: fix link to coding conventions doc in contributor guidelines (#283)
* doc: coding conventions and proposal submissions

* make coding_contention.md path relative in contributor guidelines

* fix: remove / from coding conventions link
2023-05-16 12:09:17 -04:00
Dan Orlando
4718674688 doc: coding conventions and proposal submissions (#250)
* doc: coding conventions and proposal submissions

* doc: add code standards to TOC

* make coding_contention.md path relative in contributor guidelines
2023-05-16 09:50:16 -04:00
Danny Avila
0e3c115368 Update README.md 2023-05-16 09:48:54 -04:00
Danny Avila
cc506c23af Update README.md 2023-05-16 06:53:56 -04:00
Danny Avila
1f77d94b7e Update README.md 2023-05-16 06:52:47 -04:00
Danny Avila
5711ff27ee fix(getIcon.jsx): match initial styling better with official (#277) 2023-05-15 12:15:33 -04:00
David Shin
3120602d6a feat: Add user icon in messages (#275)
* Update GPT4 model icon

* Add user icon support in messages
2023-05-15 11:51:58 -04:00
David Shin
9f36e195bc Update GPT4 model icon (#274) 2023-05-15 10:08:30 -04:00
Fuegovic
9de7da91a7 Fix: install instructions (#272)
* Update windows_install.md

removed -dev argument

* Update mac_install.md

removed `-dev` arguments

* Update linux_install.md

removed "-dev" argument

* Update windows_install.md

correction to update procedure

* Update windows_install.md

updat bat file instruction

* Update mac_install.md

update bash command

* Update linux_install.md

update bash script and update instructions

* Update linux_install.md

fix mistake in update instruction
2023-05-15 07:49:49 -04:00
518 changed files with 40384 additions and 55197 deletions

View File

@@ -0,0 +1,57 @@
// {
// "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
}
}
}
},
"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"
// },
}

View File

@@ -0,0 +1,76 @@
version: '3.4'
services:
app:
# container_name: LibreChat_dev
image: node:19-alpine
# 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"
# ports:
# - 3080:3080 # Change it to 9000:3080 to use nginx
extra_hosts: # if you are running APIs on docker you need access to, you will need to uncomment this line and next
- "host.docker.internal:host-gateway"
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
# Runs app on the same network as the service container, allows "forwardPorts" in devcontainer.json function.
# network_mode: service:another-service
# 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
# 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"
# ports:
# - 27018:27017
image: mongo
# restart: always
volumes:
- ./data-node:/data/db
command: mongod --noauth
meilisearch:
container_name: chat-meilisearch
image: getmeili/meilisearch:v1.0
network_mode: "host"
# ports:
# - 7700:7700
# env_file:
# - .env
environment:
- SEARCH=false
- MEILI_HOST=http://0.0.0.0:7700
- MEILI_HTTP_ADDR=0.0.0.0:7700
- MEILI_MASTER_KEY=5c71cf56d672d009e36070b5bc5e47b743535ae55c818ae3b735bb6ebfb4ba63
volumes:
- ./meili_data:/meili_data

View File

@@ -1,2 +1,5 @@
**/node_modules
api/.env
client/dist/images
data-node
.env
**/.env

263
.env.example Normal file
View File

@@ -0,0 +1,263 @@
##########################
# Server configuration:
##########################
APP_TITLE=LibreChat
# 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
# Change this to proxy any API request.
# It's useful if your machine has difficulty calling the original API server.
# PROXY=
# 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"
# 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
# Reverse proxy settings for OpenAI:
# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy
# OPENAI_REVERSE_PROXY=
##########################
# 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"
##########################
# 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.
# 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
##########################
# 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>
##########################
# 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
#############################
# 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
# 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
##########################
# 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=
##########################
# 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_REGISTRATION=true
# Allow Social Registration
ALLOW_SOCIAL_LOGIN=false
# JWT Secrets
JWT_SECRET=secret
JWT_REFRESH_SECRET=secret
# 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
# 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
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_BUTTON_LABEL=
OPENID_IMAGE_URL=
# Set the expiration delay for the secure cookie with the JWT token
# Delay is in millisecond e.g. 7 days is 1000*60*60*24*7
SESSION_EXPIRY=(1000 * 60 * 60 * 24) * 7
# Github:
# Get the Client ID and Secret from your Discord Application
# Add your Discord Client ID and Client Secret here:
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
# Discord:
# Get the Client ID and Secret from your Discord Application
# Add your Github Client ID and Client Secret here:
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
###########################
# Application Domains
###########################
# 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
DOMAIN_CLIENT=http://localhost:3080
DOMAIN_SERVER=http://localhost:3080

118
.eslintrc.js Normal file
View File

@@ -0,0 +1,118 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
commonjs: true,
es6: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jest/recommended',
'prettier',
],
ignorePatterns: ['client/dist/**/*', 'client/public/**/*', 'e2e/playwright-report/**/*'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
plugins: ['react', 'react-hooks', '@typescript-eslint', 'import'],
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow' }],
indent: ['error', 2, { SwitchCase: 1 }],
'max-len': [
'error',
{
code: 120,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreComments: true,
},
],
'import/no-cycle': 'error',
'linebreak-style': 0,
curly: ['error', 'all'],
semi: ['error', 'always'],
'no-trailing-spaces': 'error',
'object-curly-spacing': ['error', 'always'],
'no-multiple-empty-lines': ['error', { max: 1 }],
'comma-dangle': ['error', 'always-multiline'],
// "arrow-parens": [2, "as-needed", { requireForBlockBody: true }],
// 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-console': 'off',
'import/extensions': 'off',
'no-promise-executor-return': 'off',
'no-param-reassign': 'off',
'no-continue': 'off',
'no-restricted-syntax': 'off',
'react/prop-types': ['off'],
'react/display-name': ['off'],
quotes: ['error', 'single'],
},
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-unused-vars': 'off', // off because it conflicts with '@typescript-eslint/no-unused-vars'
'react/display-name': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
},
},
{
files: ['rollup.config.js', '.eslintrc.js', 'jest.config.js'],
env: {
node: true,
},
},
{
files: [
'**/*.test.js',
'**/*.test.jsx',
'**/*.test.ts',
'**/*.test.tsx',
'**/*.spec.js',
'**/*.spec.jsx',
'**/*.spec.ts',
'**/*.spec.tsx',
'setupTests.js',
],
env: {
jest: true,
node: true,
},
rules: {
'react/display-name': 'off',
'react/prop-types': 'off',
'react/no-unescaped-entities': 'off',
},
},
{
files: '**/*.+(ts)',
parser: '@typescript-eslint/parser',
parserOptions: {
project: './client/tsconfig.json',
},
plugins: ['@typescript-eslint/eslint-plugin', 'jest'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
],
},
],
settings: {
react: {
createClass: 'createReactClass', // Regex for Component Factory to use,
// default to "createReactClass"
pragma: 'React', // Pragma to use, default to "React"
fragment: 'Fragment', // Fragment to use (may be a property of <pragma>), default to "Fragment"
version: 'detect', // React version. "detect" automatically picks the version you have installed.
},
},
};

64
.github/ISSUE_TEMPLATE/BUG-REPORT.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
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:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Please give as many details as possible
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
description: Please list the steps needed to reproduce the issue.
placeholder: "1. Step 1\n2. Step 2\n3. Step 3"
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
- Mobile (iOS)
- Mobile (Android)
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem. You can drag and drop, paste images directly here or link to them.
- type: checkboxes
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)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -0,0 +1,57 @@
name: Feature Request
description: File a feature request
title: "Enhancement: "
labels: ["enhancement"]
body:
- type: markdown
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:
label: What features would you like to see added?
description: Please provide as many details as possible.
placeholder: Please provide as many details as possible.
validations:
required: true
- type: textarea
id: details
attributes:
label: More details
description: Please provide additional details if needed.
placeholder: Please provide additional details if needed.
validations:
required: true
- type: dropdown
id: subject
attributes:
label: Which components are impacted by your request?
multiple: true
options:
- General
- UI
- Endpoints
- Plugins
- Other
- type: textarea
id: screenshots
attributes:
label: Pictures
description: If relevant, please include images to help clarify your request. You can drag and drop images directly here, paste them, or provide a link to them.
- type: checkboxes
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)
options:
- label: I agree to follow this project's Code of Conduct
required: true

58
.github/ISSUE_TEMPLATE/QUESTION.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Question
description: Ask your question
title: "[Question]: "
labels: ["question"]
body:
- type: markdown
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:
label: What is your question?
description: Please give as many details as possible
placeholder: Please give as many details as possible
validations:
required: true
- type: textarea
id: more-details
attributes:
label: More Details
description: Please provide more details if needed.
placeholder: Please provide more details if needed.
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What is the main subject of your question?
multiple: true
options:
- Documentation
- Installation
- UI
- Endpoints
- User System/OAuth
- Other
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem. You can drag and drop, paste images directly here or link to them.
- type: checkboxes
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)
options:
- label: I agree to follow this project's Code of Conduct
required: true

47
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/api" # Location of package manifests
target-branch: "dev"
versioning-strategy: increase-if-necessary
schedule:
interval: "weekly"
allow:
# Allow both direct and indirect updates for all packages
- dependency-type: "all"
commit-message:
prefix: "npm api prod"
prefix-development: "npm api dev"
include: "scope"
- package-ecosystem: "npm" # See documentation for possible values
directory: "/client" # Location of package manifests
target-branch: "dev"
versioning-strategy: increase-if-necessary
schedule:
interval: "weekly"
allow:
# Allow both direct and indirect updates for all packages
- dependency-type: "all"
commit-message:
prefix: "npm client prod"
prefix-development: "npm client dev"
include: "scope"
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
target-branch: "dev"
versioning-strategy: increase-if-necessary
schedule:
interval: "weekly"
allow:
# Allow both direct and indirect updates for all packages
- dependency-type: "all"
commit-message:
prefix: "npm all prod"
prefix-development: "npm all dev"
include: "scope"

View File

@@ -1,72 +1,62 @@
name: Playwright Tests
on:
push:
branches: [ main ]
branches: [feat/playwright-jest-cicd]
pull_request:
branches: [ main ]
branches: [feat/playwright-jest-cicd]
jobs:
tests_e2e:
name: Run end-to-end tests
name: Run Playwright tests
timeout-minutes: 60
runs-on: ubuntu-latest
env:
BINGAI_TOKEN: ${{ secrets.BINGAI_TOKEN }}
CHATGPT_TOKEN: ${{ secrets.CHATGPT_TOKEN }}
# BINGAI_TOKEN: ${{ secrets.BINGAI_TOKEN }}
# CHATGPT_TOKEN: ${{ secrets.CHATGPT_TOKEN }}
MONGO_URI: ${{ secrets.MONGO_URI }}
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
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 }}
CREDS_KEY: ${{ secrets.CREDS_KEY }}
CREDS_IV: ${{ secrets.CREDS_IV }}
# NODE_ENV: ${{ vars.NODE_ENV }}
DOMAIN_CLIENT: ${{ vars.DOMAIN_CLIENT }}
DOMAIN_SERVER: ${{ vars.DOMAIN_SERVER }}
# PALM_KEY: ${{ secrets.PALM_KEY }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Cache API dependencies
uses: actions/cache@v2
with:
path: ./api/node_modules
key: api-${{ runner.os }}-node-${{ hashFiles('./api/package-lock.json') }}
restore-keys: |
api-${{ runner.os }}-node-
- name: Install global dependencies
run: npm ci --ignore-scripts
- name: Install API dependencies
working-directory: ./api
run: npm ci
- name: Install API dependencies
working-directory: ./api
run: npm ci --ignore-scripts
- name: Cache Client dependencies
uses: actions/cache@v2
with:
path: ./client/node_modules
key: client-${{ runner.os }}-node-${{ hashFiles('./client/package-lock.json') }}
restore-keys: |
client-${{ runner.os }}-node-
- name: Install Client dependencies
working-directory: ./client
run: npm ci --ignore-scripts
- name: Install Client dependencies
working-directory: ./client
run: npm ci
- name: Build Client
run: cd client && npm run build:ci
- name: Build Client
working-directory: ./client
run: npm run build
- name: Install Playwright Browsers
run: npx playwright install --with-deps && npm install -D @playwright/test
- name: Install global dependencies
run: npm ci
- name: Start server
run: |
npm run backend & sleep 10
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test --config=e2e/playwright.config.ts
- name: Start API server
working-directory: ./api
run: |
npm run start &
sleep 10 # Wait for the server to start
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: e2e/playwright-report/
retention-days: 30
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: e2e/playwright-report/
retention-days: 30

View File

@@ -1,40 +1,35 @@
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] 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
# How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
- [ ] Test A
- [ ] Test B
**Test Configuration**:
* Firmware version:
* Hardware:
* Toolchain:
* SDK:
# Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
##
## [Go Back to ReadMe](../../README.md)
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] 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
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration:
##
### **Test Configuration**:
##
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules

44
.github/workflows/backend-review.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Backend Unit Tests
on:
push:
branches:
- main
- dev
- release/*
pull_request:
branches:
- main
- dev
- release/*
jobs:
tests_Backend:
name: Run Backend unit tests
timeout-minutes: 60
runs-on: ubuntu-latest
env:
MONGO_URI: ${{ secrets.MONGO_URI }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
CREDS_KEY: ${{ secrets.CREDS_KEY }}
CREDS_IV: ${{ secrets.CREDS_IV }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 19.x
uses: actions/setup-node@v3
with:
node-version: 19.x
cache: 'npm'
- name: Install dependencies
run: npm ci
# - name: Install Linux X64 Sharp
# run: npm install --platform=linux --arch=x64 --verbose sharp
- name: Run unit tests
run: cd api && npm run test:ci
- name: Run linters
uses: wearerequired/lint-action@v2
with:
eslint: true

38
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Linux_Container_Workflow
on:
workflow_dispatch:
env:
RUNNER_VERSION: 2.293.0
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
# checkout the repo
- name: 'Checkout GitHub Action'
uses: actions/checkout@main
- name: 'Login via Azure CLI'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: 'Build GitHub Runner container image'
uses: azure/docker-login@v1
with:
login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- run: |
docker build --build-arg RUNNER_VERSION=${{ env.RUNNER_VERSION }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }} .
- name: 'Push container image to ACR'
uses: azure/docker-login@v1
with:
login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- run: |
docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }}

47
.github/workflows/container.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
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
# 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

38
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Deploy_GHRunner_Linux_ACI
on:
workflow_dispatch:
env:
RUNNER_VERSION: 2.293.0
ACI_RESOURCE_GROUP: 'Demo-ACI-GitHub-Runners-RG'
ACI_NAME: 'gh-runner-linux-01'
DNS_NAME_LABEL: 'gh-lin-01'
GH_OWNER: ${{ github.repository_owner }}
GH_REPOSITORY: 'LibreChat' #Change here to deploy self hosted runner ACI to another repo.
jobs:
deploy-gh-runner-aci:
runs-on: ubuntu-latest
steps:
# checkout the repo
- name: 'Checkout GitHub Action'
uses: actions/checkout@main
- name: 'Login via Azure CLI'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: 'Deploy to Azure Container Instances'
uses: 'azure/aci-deploy@v1'
with:
resource-group: ${{ env.ACI_RESOURCE_GROUP }}
image: ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }}
registry-login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
registry-username: ${{ secrets.REGISTRY_USERNAME }}
registry-password: ${{ secrets.REGISTRY_PASSWORD }}
name: ${{ env.ACI_NAME }}
dns-name-label: ${{ env.DNS_NAME_LABEL }}
environment-variables: GH_TOKEN=${{ secrets.PAT_TOKEN }} GH_OWNER=${{ env.GH_OWNER }} GH_REPOSITORY=${{ env.GH_REPOSITORY }}
location: 'eastus'

51
.github/workflows/dev-images.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Docker Dev Images Build
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'api/**'
- 'client/**'
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 }}
# Build Docker images
- name: Build Docker images
run: |
cp .env.example .env
docker build -f Dockerfile.multi --target api-build -t librechat-dev-api .
docker build -f Dockerfile -t librechat-dev .
# Tag and push the images to GitHub Container Registry
- name: Tag and push images
run: |
docker tag librechat-dev-api:latest ghcr.io/${{ github.repository_owner }}/librechat-dev-api:${{ github.sha }}
docker push ghcr.io/${{ github.repository_owner }}/librechat-dev-api:${{ github.sha }}
docker tag librechat-dev-api:latest ghcr.io/${{ github.repository_owner }}/librechat-dev-api:latest
docker push ghcr.io/${{ github.repository_owner }}/librechat-dev-api:latest
docker tag librechat-dev:latest ghcr.io/${{ github.repository_owner }}/librechat-dev:${{ github.sha }}
docker push ghcr.io/${{ github.repository_owner }}/librechat-dev:${{ github.sha }}
docker tag librechat-dev:latest ghcr.io/${{ github.repository_owner }}/librechat-dev:latest
docker push ghcr.io/${{ github.repository_owner }}/librechat-dev:latest

34
.github/workflows/frontend-review.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
#github action to run unit tests for frontend with jest
name: Frontend Unit Tests
on:
push:
branches:
- main
- dev
- release/*
pull_request:
branches:
- main
- dev
- release/*
jobs:
tests_frontend:
name: Run frontend unit tests
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 19.x
uses: actions/setup-node@v3
with:
node-version: 19.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build Client
run: npm run frontend:ci
- name: Run unit tests
run: cd client && npm run test:ci

24
.github/workflows/mkdocs.yaml vendored Normal file
View File

@@ -0,0 +1,24 @@
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

13
.gitignore vendored
View File

@@ -26,6 +26,7 @@ dist/
public/main.js
public/main.js.map
public/main.js.LICENSE.txt
client/public/images/
client/public/main.js
client/public/main.js.map
client/public/main.js.LICENSE.txt
@@ -38,6 +39,7 @@ meili_data/
api/node_modules/
client/node_modules/
bower_components/
types/
# Floobits
.floo
@@ -47,7 +49,9 @@ bower_components/
# Environment
.npmrc
.env
.env*
!**/.env.example
!**/.env.test.example
cache.json
api/data/
owner.yml
@@ -60,8 +64,15 @@ src/style - official.css
.DS_Store
*.code-workspace
.idea
*.pem
config.local.ts
**/storageState.json
junit.xml
# meilisearch
meilisearch
data.ms/*
auth.json
/packages/ux-shared/
/images

5
.husky/pre-commit Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
[ -n "$CI" ] && exit 0
npx lint-staged

20
.prettierrc.js Normal file
View File

@@ -0,0 +1,20 @@
module.exports = {
plugins: ['prettier-plugin-tailwindcss'],
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
// bracketSpacing: false,
trailingComma: 'all',
arrowParens: 'always',
embeddedLanguageFormatting: 'auto',
insertPragma: false,
proseWrap: 'preserve',
quoteProps: 'as-needed',
requirePragma: false,
rangeStart: 0,
endOfLine: 'auto',
jsxBracketSameLine: false,
jsxSingleQuote: false,
};

View File

@@ -1,136 +0,0 @@
# # Changelog
<details open>
<summary><strong>2023-05-14</strong></summary>
**Released [v0.4.4](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.4):**
1. The Msg Clipboard was changed to a checkmark for improved user experience by @techwithanirudh in PR [#247](https://github.com/danny-avila/chatgpt-clone/pull/247).
2. A typo in the auth.json path for accessing Google Palm was corrected by @antonme in PR [#266](https://github.com/danny-avila/chatgpt-clone/pull/266).
3. @techwithanirudh added a Popup Menu to save sidebar space in PR [#260](https://github.com/danny-avila/chatgpt-clone/pull/260).
4. The default pageSize in Conversation.js was increased from 12 to 14 by @danny-avila in PR [#267](https://github.com/danny-avila/chatgpt-clone/pull/267).
5. Fonts were updated by @techwithanirudh in PR [#261](https://github.com/danny-avila/chatgpt-clone/pull/261).
6. Font file paths in style.css were changed by @danny-avila in PR [#268](https://github.com/danny-avila/chatgpt-clone/pull/268).
7. Code was fixed to adjust max_tokens according to model selection by @p4w4n in PR [#263](https://github.com/danny-avila/chatgpt-clone/pull/263).
8. Various improvements were made, such as fixing react errors and adjusting the mobile view, by @danny-avila in PR [#269](https://github.com/danny-avila/chatgpt-clone/pull/269).
New contributors to the project include:
- @techwithanirudh, who made their first contribution in PR [#247](https://github.com/danny-avila/chatgpt-clone/pull/247).
- @antonme, who made their first contribution in PR [#266](https://github.com/danny-avila/chatgpt-clone/pull/266).
- @p4w4n, who made their first contribution in PR [#263](https://github.com/danny-avila/chatgpt-clone/pull/263).
The [full changelog can be found here](https://github.com/danny-avila/chatgpt-clone/compare/v0.4.3...v0.4.4)
</details>
<details>
<summary><strong>2023-05-13</strong></summary>
**Released [v0.4.3](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.3) which now supports Google's PaLM 2!**
![image](https://github.com/danny-avila/chatgpt-clone/assets/110412045/ec5e8ff3-6c3a-4f25-9687-d8558435d094)
**How to Setup PaLM 2 (via Google Cloud Vertex AI API)**
- Enable the Vertex AI API on Google Cloud:
- - https://console.cloud.google.com/vertex-ai
- Create a Service Account:
- - https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1
- Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
- Create a JSON key, rename as 'auth.json' and save it in /api/data/.
**Alternatively**
- In your ./api/.env file, set PALM_KEY as "user_provided" to allow the user to provide a Service Account key JSON from the UI.
- They will follow the steps above except for renaming the file, simply importing the JSON when prompted.
- The key is sent to the server but never saved except in your local storage
**Note:**
- Vertex AI does not (yet) support response streaming for text generations, so response may seem to take long when generating a lot of text.
- Text streaming is simulated
You can check the full changelog in between v0.4.2 and v0.4.3 [here](https://github.com/danny-avila/chatgpt-clone/compare/v0.4.2...v0.4.3).
</details>
<details>
<summary><strong>2023-05-11</strong></summary>
**Released [v0.4.2](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.2)**
ChatGPT-Clone received some important upgrades and improvements. A new contributor, [@qcgm1978](https://github.com/qcgm1978), makes their first contribution by adding a null check for adaptiveCards variable. Additionally, support for titling conversations with the Azure endpoint is added by [@danny-avila](https://github.com/danny-avila) in PR [#234](https://github.com/danny-avila/chatgpt-clone/pull/234). In PR [#235](https://github.com/danny-avila/chatgpt-clone/pull/235), [@danny-avila](https://github.com/danny-avila) also makes some necessary fixes to titling, quotation marks, and endpoints being unavailable with only the Azure key provided. The logging system is now powered by Pino and sanitization, thanks to [@danorlando](https://github.com/danorlando) in PR [#227](https://github.com/danny-avila/chatgpt-clone/pull/227). To bulletproof the Docker container, the .dockerignore file is updated to include the client/.env file by [@danny-avila](https://github.com/danny-avila) in PR [#241](https://github.com/danny-avila/chatgpt-clone/pull/241). This issue was brought to our attention on discord.
There is active work on the new Plugins feature, converting the frontend to Typescript, and looking to integrate Palm2, google's new generative AI accessible via API, to the project as a new endpoint.
You can check the full changelog in between [v0.4.1](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.1) and [v0.4.2](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.2) [here](https://github.com/danny-avila/chatgpt-clone/compare/v0.4.1...v0.4.2)."
For discussion and suggestion you can join us: **[community discord server](https://discord.gg/NGaa9RPCft)**
</details>
<details>
<summary><strong>2023-05-09</strong></summary>
**Released [v0.4.1](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.1)**
* update user system section of readme by @danorlando in #207
* remove github-passport and update package.lock files by @danorlando in #208
* Update README.md by @fuegovic in #209
* fix: fix browser refresh redirecting to /chat/new by @danorlando in #210
* fix: fix issue with validation when google account has multiple spaces in username by @danorlando in #211
* chore: update docker image version to use latest by @danny-avila in #218
* update documentation structure by @fuegovic in #220
* Feat: Add Azure support by @danny-avila in #219
* Update Message.js by @DavidDev1334 in #191
⚠️ **IMPORTANT :** Since V0.4.0 You should register and login with a local account (email and password) for the first time sign-up. if you use login for the first time with a social login account (eg. Google, facebook, etc.), the conversations and presets that you created before the user system was implemented will NOT be migrated to that account.
⚠️ **Breaking - new Env Variables :** Since V0.4.0 You will need to add the new env variables from .env.example for the app to work, even if you're not using multiple users for your purposes.
For discussion and suggestion you can join us: **[community discord server](https://discord.gg/NGaa9RPCft)**
</details>
<details>
<summary><strong>2023-05-07</strong></summary>
**Released [v0.4.0](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.0)**, Introducing User/Auth System and OAuth2/Social Login! You can now register and login with an email account or use Google login. Your your previous conversations and presets will migrate to your new profile upon creation. Check out the details in the [User/Auth System](#userauth-system) section of the README.md.
⚠️ **IMPORTANT :** You should register and login with a local account (email and password) for the first time sign-up. if you use login for the first time with a social login account (eg. Google, facebook, etc.), the conversations and presets that you created before the user system was implemented will NOT be migrated to that account.
⚠️ **Breaking - new Env Variables :** You will need to add the new env variables from .env.example for the app to work, even if you're not using multiple users for your purposes.
For discussion and suggestion you can join us: **[community discord server](https://discord.gg/NGaa9RPCft)**
</details>
<details>
<summary><strong>2023-04-05</strong></summary>
**Released [v0.3.0](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.3.0)**, Introducing more customization for both OpenAI & BingAI conversations! This is one of the biggest updates yet and will make integrating future LLM's a lot easier, providing a lot of customization features as well, including sharing presets! Please feel free to share them in the **[community discord server](https://discord.gg/NGaa9RPCft)**
</details>
<details>
<summary><strong>2023-03-23</strong></summary>
**Released [v0.1.0](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.1.0)**, **searching messages/conversations is live!** Up next is more custom parameters for customGpt's. Join the discord server for more immediate assistance and update: **[community discord server](https://discord.gg/NGaa9RPCft)**
</details>
<details>
<summary><strong>2023-03-22</strong></summary>
**Released [v0.0.6](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.0.6)**, the latest stable release before **Searching messages** goes live tomorrow. See exact updates to date in the tag link. By request, there is now also a **[community discord server](https://s
</details>
<details>
<summary><strong>2023-03-20</strong></summary>
**Searching messages** is almost here as I test more of its functionality. There've been a lot of great features requested and great contributions and I will work on some soon, namely, further customizing the custom gpt params with sliders similar to the OpenAI playground, and including the custom params and system messages available to Bing.
The above features are next and then I will have to focus on building the **test environment.** I would **greatly appreciate** help in this area with any test environment you're familiar with (mocha, chai, jest, playwright, puppeteer). This is to aid in the velocity of contributing and to save time I spend debugging.
On that note, I had to switch the default branch due to some breaking changes that haven't been straight forward to debug, mainly related to node-chat-gpt the main dependency of the project. Thankfully, my working branch, now switched to default as main, is working as expected.
</details>
##
## [Go Back to ReadMe](README.md)

View File

@@ -1,4 +1,4 @@
# Contributor Covenant Code of Conduct
# Contributor Covenant Code of Conduct
## Our Pledge
@@ -59,8 +59,8 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://t.me/proffapt.
reported to the community leaders responsible for enforcement here on GitHub or
on the official [Discord Server](https://discord.gg/uDyZ5Tzhct).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -127,6 +127,6 @@ For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
##
---
## [Go Back to ReadMe](../../README.md)
## [Go Back to ReadMe](README.md)

100
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,100 @@
# Contributor Guidelines
Thank you to all the contributors who have helped make this project possible! We welcome various types of contributions, such as bug reports, documentation improvements, feature requests, and code contributions.
## Contributing Guidelines
If the feature you would like to contribute has not already received prior approval from the project maintainers (i.e., the feature is currently on the roadmap or on the [Trello board]()), please submit a proposal in the [proposals category](https://github.com/danny-avila/LibreChat/discussions/categories/proposals) of the discussions board before beginning work on it. The proposals should include specific implementation details, including areas of the application that will be affected by the change (including designs if applicable), and any other relevant information that might be required for a speedy review. However, proposals are not required for small changes, bug fixes, or documentation improvements. Small changes and bug fixes should be tied to an [issue](https://github.com/danny-avila/LibreChat/issues) and included in the corresponding pull request for tracking purposes.
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.
## Our Standards
We strive to maintain a positive and inclusive environment within our project community. We expect all contributors to adhere to the following standards:
- Using welcoming and inclusive language.
- Being respectful of differing viewpoints and experiences.
- Gracefully accepting constructive criticism.
- Focusing on what is best for the community.
- Showing empathy towards other community members.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that do not align with these standards.
## To contribute to this project, please adhere to the following guidelines:
## 1. Git Workflow
We utilize a GitFlow workflow to manage changes to this project's codebase. Follow these general steps when contributing code:
1. Fork the repository and create a new branch with a descriptive slash-based name (e.g., `new/feature/x`).
2. Implement your changes and ensure that all tests pass.
3. Commit your changes using conventional commit messages with GitFlow flags. Begin the commit message with a tag indicating the change type, such as "feat" (new feature), "fix" (bug fix), "docs" (documentation), or "refactor" (code refactoring), followed by a brief summary of the changes (e.g., `feat: Add new feature X to the project`).
4. Submit a pull request with a clear and concise description of your changes and the reasons behind them.
5. We will review your pull request, provide feedback as needed, and eventually merge the approved changes into the main branch.
## 2. Commit Message Format
We have defined precise rules for formatting our Git commit messages. This format leads to an easier-to-read commit history. Each commit message consists of a header, a body, and an optional footer.
### Commit Message Header
The header is mandatory and must conform to the following format:
```
<type>(<scope>): <short summary>
```
- `<type>`: Must be one of the following:
- **build**: Changes that affect the build system or external dependencies.
- **ci**: Changes to our CI configuration files and script.
- **docs**: Documentation-only changes.
- **feat**: A new feature.
- **fix**: A bug fix.
- **perf**: A code change that improves performance.
- **refactor**: A code change that neither fixes a bug nor adds a feature.
- **test**: Adding missing tests or correcting existing tests.
- `<scope>`: Optional. Indicates the scope of the commit, such as `common`, `plays`, `infra`, etc.
- `<short summary>`: A brief, concise summary of the change in the present tense. It should not be capitalized and should not end with a period.
### Commit Message Body
The body is mandatory for all commits except for those of type "docs". When the body is present, it must be at least 20 characters long and should explain the motivation behind the change. You can include a comparison of the previous behavior with the new behavior to illustrate the impact of the change.
### Commit Message Footer
The footer is optional and can contain information about breaking changes, deprecations, and references to related GitHub issues, Jira tickets, or other pull requests. For example, you can include a "BREAKING CHANGE" section that describes a breaking change along with migration instructions. Additionally, you can include a "Closes" section to reference the issue or pull request that this commit closes or is related to.
### Revert commits
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. The commit message body should include the SHA of the commit being reverted and a clear description of the reason for reverting the commit.
## 3. Pull Request Process
When submitting a pull request, please follow these guidelines:
- Ensure that any installation or build dependencies are removed before the end of the layer when doing a build.
- Update the README.md with details of changes to the interface, including new environment variables, exposed ports, useful file locations, and container parameters.
- Increase the version numbers in any example files and the README.md to reflect the new version that the pull request represents. We use [SemVer](http://semver.org/) for versioning.
Ensure that your changes meet the following criteria:
- All tests pass.
- The code is well-formatted and adheres to our coding standards.
- The commit history is clean and easy to follow. You can use `git rebase` or `git merge --squash` to clean your commit history before submitting the pull request.
- The pull request description clearly outlines the changes and the reasons behind them. Be sure to include the steps to test the pull request.
## 4. Naming Conventions
Apply the following naming conventions to branches, labels, and other Git-related entities:
- Branch names: Descriptive and slash-based (e.g., `new/feature/x`).
- Labels: Descriptive and snake_case (e.g., `bug_fix`).
- Directories and file names: Descriptive and snake_case (e.g., `config_file.yaml`).
---
## [Go Back to ReadMe](README.md)

View File

@@ -1,26 +0,0 @@
# Contributors List
We appreciate all the contributors who helped make this project possible:
- danny-avila (Admin)
- wtlyu (Contributor)
- danorlando (Contributor)
- alfredo-f (Contributor)
- HyunggyuJang (Contributor)
- fuegovic (Contributor)
- DavidDev1334
- toordog (Contributor)
- heathriel (External Contributor)
- hackreactor-bot (Contributor)
- git-bruh (Contributor)
- zhangsean (Contributor)
- llk89 (Contributor)
- adamrb (Contributor)
If you have contributed to this project and would like to be added to the list of contributors, please submit a pull request updating this file with your name and GitHub username.
##
## [Go Back to ReadMe](README.md)

View File

@@ -1,38 +1,26 @@
FROM node:19-alpine AS react-client
WORKDIR /client
# copy package.json into the container at /client
COPY /client/.env /client/.env
COPY /client/package*.json /client/
# install dependencies
RUN npm ci
# Copy the current directory contents into the container at /client
COPY /client/ /client/
# Set the memory limit for Node.js
ENV NODE_OPTIONS="--max-old-space-size=2048"
# Build artifacts
RUN npm run build
# Base node image
FROM node:19-alpine AS node
FROM node:19-alpine AS node-api
WORKDIR /api
# copy package.json into the container at /api
COPY /api/package*.json /api/
# install dependencies
# Install curl for health check
RUN apk --no-cache add curl
COPY . /app
# Install dependencies
WORKDIR /app
RUN npm ci
# Copy the current directory contents into the container at /api
COPY /api/ /api/
# Copy the client side code
COPY --from=react-client /client/dist /client/dist
# Make port 3080 available to the world outside this container
# React client build
ENV NODE_OPTIONS="--max-old-space-size=2048"
RUN npm run frontend
# Node API setup
EXPOSE 3080
# Expose the server to 0.0.0.0
ENV HOST=0.0.0.0
# Run the app when the container launches
CMD ["npm", "start"]
CMD ["npm", "run", "backend"]
# Optional: for client with nginx routing
FROM nginx:stable-alpine AS nginx-client
WORKDIR /usr/share/nginx/html
COPY --from=react-client /client/dist /usr/share/nginx/html
# Add your nginx.conf
COPY /client/nginx.conf /etc/nginx/conf.d/default.conf
ENTRYPOINT ["nginx", "-g", "daemon off;"]
# FROM nginx:stable-alpine AS nginx-client
# WORKDIR /usr/share/nginx/html
# COPY --from=node /app/client/dist /usr/share/nginx/html
# COPY client/nginx.conf /etc/nginx/conf.d/default.conf
# ENTRYPOINT ["nginx", "-g", "daemon off;"]

32
Dockerfile.multi Normal file
View File

@@ -0,0 +1,32 @@
# Build API, Client and Data Provider
FROM node:19-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
# React client build
FROM base AS client-build
WORKDIR /app/client
COPY ./client/ ./
RUN npm install
ENV NODE_OPTIONS="--max-old-space-size=2048"
RUN npm run build
# Node API setup
FROM base AS api-build
COPY --from=client-build /app/client/dist /app/client/dist
EXPOSE 3080
ENV HOST=0.0.0.0
CMD ["node", "server/index.js"]
# Nginx setup
FROM nginx:1.21.1-alpine AS prod-stage
COPY ./client/nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,7 +1,9 @@
# MIT License
# MIT License
Copyright (c) 2023 Danny Avila
##
---
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
@@ -22,6 +24,6 @@ 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)

221
README.md
View File

@@ -1,176 +1,153 @@
<p align="center">
<a href="https://discord.gg/NGaa9RPCft">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/110412045/228325485-9d3e618f-a980-44fe-89e9-d6d39164680e.png">
<img src="https://user-images.githubusercontent.com/110412045/228325485-9d3e618f-a980-44fe-89e9-d6d39164680e.png" height="128">
</picture>
<h1 align="center">ChatGPT Clone</h1>
<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>
</p>
<p align="center">
<a aria-label="Join the community on Discord" href="https://discord.gg/NGaa9RPCft">
<img alt="" src="https://img.shields.io/badge/Join%20the%20community-blueviolet.svg?style=for-the-badge&logo=DISCORD&labelColor=000000&logoWidth=20">
<a href="https://discord.gg/NGaa9RPCft">
<img
src="https://img.shields.io/discord/1086345563026489514?label=&logo=discord&style=for-the-badge&logoWidth=20&logoColor=white&labelColor=000000&color=blueviolet">
</a>
<a href="https://www.youtube.com/@LibreChat">
<img
src="https://img.shields.io/badge/YOUTUBE-red.svg?style=for-the-badge&logo=youtube&logoColor=white&labelColor=000000&logoWidth=20">
</a>
<a href="https://docs.librechat.ai">
<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">
<img alt="" src="https://img.shields.io/badge/SPONSORS-brightgreen.svg?style=for-the-badge&labelColor=000000&logoWidth=20">
<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 AI Conversations under One Roof. ##
Assistant AIs are the future and OpenAI revolutionized this movement with ChatGPT. While numerous UIs exist, this app commemorates the original styling of ChatGPT, with the ability to integrate any current/future AI models, while integrating and improving upon original client features, such as conversation/message search and prompt templates (currently WIP). Through this clone, you can avoid ChatGPT Plus in favor of free or pay-per-call APIs. I will soon deploy a demo of this app. Feel free to contribute, clone, or fork. Currently dockerized.
## 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.
![clone3](https://user-images.githubusercontent.com/110412045/230538752-9b99dc6e-cd02-483a-bff0-6c6e780fa7ae.gif)
<!-- 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)
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 (through 3 endpoints: OpenAI API, BingAI, and ChatGPT Browser)
- Create, Save, & Share custom presets for OpenAI and BingAI endpoints - [More info on customization here](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.3.0)
- Edit and Resubmit messages just like the official site (with conversation branching)
- Search all messages/conversations - [More info here](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.1.0)
- Integrating plugins soon
##
# Sponsors
Sponsored by <a href="https://github.com/DavidDev1334"><b>@DavidDev1334</b></a>, <a href="https://github.com/mjtechguy"><b>@mjtechguy</b></a>, <a href="https://github.com/Pharrcyde"><b>@Pharrcyde</b></a>, & <a href="https://github.com/fuegovic"><b>@fuegovic</b></a>
##
## **Google's PaLM 2 is now supported as of [v0.4.3](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.3)**
![image](https://github.com/danny-avila/chatgpt-clone/assets/110412045/ec5e8ff3-6c3a-4f25-9687-d8558435d094)
<details>
<summary><strong>How to Setup PaLM 2 (via Google Cloud Vertex AI API)</strong></summary>
- Enable the Vertex AI API on Google Cloud:
- - https://console.cloud.google.com/vertex-ai
- Create a Service Account:
- - https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1
- Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
- Create a JSON key, rename as 'auth.json' and save it in /api/data/.
**Alternatively**
- In your ./api/.env file, set PALM_KEY as "user_provided" to allow the user to provide a Service Account key JSON from the UI.
- They will follow the steps above except for renaming the file, simply importing the JSON when prompted.
- The key is sent to the server but never saved except in your local storage
**Note:**
- Vertex AI does not (yet) support response streaming for text generations, so response may seem to take long when generating a lot of text.
- Text streaming is simulated
</details>
- 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)
---
<details open>
<summary><strong>2023-05-14</strong></summary>
## ⚠️ [Breaking Changes](docs/general_info/breaking_changes.md) ⚠️
**Applies to [v0.5.4](docs/general_info/breaking_changes.md#v054) & [v0.5.5](docs/general_info/breaking_changes.md#v055)**
**Released [v0.4.4](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.4.4):**
**Please read this before updating from a previous version**
1. The Msg Clipboard was changed to a checkmark for improved user experience by @techwithanirudh in PR [#247](https://github.com/danny-avila/chatgpt-clone/pull/247).
2. A typo in the auth.json path for accessing Google Palm was corrected by @antonme in PR [#266](https://github.com/danny-avila/chatgpt-clone/pull/266).
3. @techwithanirudh added a Popup Menu to save sidebar space in PR [#260](https://github.com/danny-avila/chatgpt-clone/pull/260).
4. The default pageSize in Conversation.js was increased from 12 to 14 by @danny-avila in PR [#267](https://github.com/danny-avila/chatgpt-clone/pull/267).
5. Fonts were updated by @techwithanirudh in PR [#261](https://github.com/danny-avila/chatgpt-clone/pull/261).
6. Font file paths in style.css were changed by @danny-avila in PR [#268](https://github.com/danny-avila/chatgpt-clone/pull/268).
7. Code was fixed to adjust max_tokens according to model selection by @p4w4n in PR [#263](https://github.com/danny-avila/chatgpt-clone/pull/263).
8. Various improvements were made, such as fixing react errors and adjusting the mobile view, by @danny-avila in PR [#269](https://github.com/danny-avila/chatgpt-clone/pull/269).
---
New contributors to the project include:
## Changelog
Keep up with the latest updates by visiting the releases page - [Releases](https://github.com/danny-avila/LibreChat/releases)
- @techwithanirudh, who made their first contribution in PR [#247](https://github.com/danny-avila/chatgpt-clone/pull/247).
- @antonme, who made their first contribution in PR [#266](https://github.com/danny-avila/chatgpt-clone/pull/266).
- @p4w4n, who made their first contribution in PR [#263](https://github.com/danny-avila/chatgpt-clone/pull/263).
The [full changelog can be found here](https://github.com/danny-avila/chatgpt-clone/compare/v0.4.3...v0.4.4)
⚠️ **IMPORTANT :** Since V0.4.0 You should register and login with a local account (email and password) for the first time sign-up. if you use login for the first time with a social login account (eg. Google, facebook, etc.), the conversations and presets that you created before the user system was implemented will NOT be migrated to that account.
⚠️ **Breaking - new Env Variables :** Since V0.4.0 You will need to add the new env variables from .env.example for the app to work, even if you're not using multiple users for your purposes.
For discussion and suggestion you can join us: **[community discord server](https://discord.gg/NGaa9RPCft)**
</details>
[Past Updates](CHANGELOG.md)
##
---
<h1>Table of Contents</h1>
<details open>
<summary><strong>Getting Started</strong></summary>
* [Docker Install](/documents/install/docker_install.md)
* [Linux Install](documents/install/linux_install.md)
* [Mac Install](documents/install/mac_install.md)
* [Windows Install](documents/install/windows_install.md)
* Installation
* [Docker Install🐳](docs/install/docker_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)
</details>
<details>
<summary><strong>General Information</strong></summary>
* [Project Origin](documents/general_info/project_origin.md)
* [Roadmap](documents/general_info/roadmap.md)
* [Tech Stack](documents/general_info/tech_stack.md)
* [Changelog](CHANGELOG.md)
* [Bing Jailbreak Info](documents/general_info/bing_jailbreak_info.md)
* [Code of Conduct](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>
* [User Auth System](documents/features/user_auth_system.md)
* [Proxy](documents/features/proxy.md)
* **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)
* [Proxy](docs/features/proxy.md)
* [Bing Jailbreak](docs/features/bing_jailbreak.md)
</details>
<details>
<summary><strong>Cloud Deployment</strong></summary>
* [Heroku](documents/deployment/heroku.md)
* [Hetzner](docs/deployment/hetzner_ubuntu.md)
* [Heroku](docs/deployment/heroku.md)
* [Linode](docs/deployment/linode.md)
* [Cloudflare](docs/deployment/cloudflare.md)
* [Ngrok](docs/deployment/ngrok.md)
* [Render](docs/deployment/render.md)
</details>
<details>
<summary><strong>Contributions</strong></summary>
* [Code of Conduct](documents/contributions/code_of_conduct.md)
* [Contributor Guidelines](documents/contributions/contributor_guidelines.md)
* [Documentation Guidelines](documents/contributions/documentation_guidelines.md)
* [Testing](documents/contributions/testing.md)
* [Pull Request Template](documents/contributions/pull_request_template.md)
* [Contributors](CONTRIBUTORS.md)
* [Trello Board](https://trello.com/b/17z094kq/chatgpt-clone)
* [Contributor Guidelines](CONTRIBUTING.md)
* [Documentation Guidelines](docs/contributions/documentation_guidelines.md)
* [Code Standards and Conventions](docs/contributions/coding_conventions.md)
* [Testing](docs/contributions/testing.md)
* [Security](SECURITY.md)
* [Trello Board](https://trello.com/b/17z094kq/LibreChate)
</details>
<details>
<summary><strong>Report Templates</strong></summary>
* [Bug Report Template](documents/report_templates/bug_report_template.md)
* [Custom Issue Template](documents/report_templates/custom_issue_template.md)
* [Feature Request Template](documents/report_templates/feature_request_template.md)
</details>
##
### [Alternative Documentation](https://chatgpt-clone.gitbook.io/chatgpt-clone-docs/get-started/docker)
##
## Contributing
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/NGaa9RPCft)
## License
This project is licensed under the [MIT License](LICENSE.md).
##
---
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=danny-avila/chatgpt-clone&type=Date)](https://star-history.com/#danny-avila/chatgpt-clone&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date)](https://star-history.com/#danny-avila/LibreChat&Date)
---
## 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>

63
SECURITY.md Normal file
View File

@@ -0,0 +1,63 @@
# Security Policy
At LibreChat, we prioritize the security of our project and value the contributions of security researchers in helping us improve the security of our codebase. If you discover a security vulnerability within our project, we appreciate your responsible disclosure. Please follow the guidelines below to report any vulnerabilities to us:
**Note: Only report sensitive vulnerability details via the appropriate private communication channels mentioned below. Public channels, such as GitHub issues and Discord, should be used for initiating contact and establishing private communication channels.**
## Communication Channels
When reporting a security vulnerability, you have the following options to reach out to us:
- **Option 1: GitHub Security Advisory System**: We encourage you to use GitHub's Security Advisory system to report any security vulnerabilities you find. This allows us to receive vulnerability reports directly through GitHub. For more information on how to submit a security advisory report, please refer to the [GitHub Security Advisories documentation](https://docs.github.com/en/code-security/getting-started-with-security-vulnerability-alerts/about-github-security-advisories).
- **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.
_After the initial contact, we will establish a private communication channel for further discussion._
### When submitting a vulnerability report, please provide us with the following information:
- A clear description of the vulnerability, including steps to reproduce it.
- The version(s) of the project affected by the vulnerability.
- Any additional information that may be useful for understanding and addressing the issue.
We strive to acknowledge vulnerability reports within 72 hours and will keep you informed of the progress towards resolution.
## Security Updates and Patching
We are committed to maintaining the security of our open-source project, LibreChat, and promptly addressing any identified vulnerabilities. To ensure the security of our project, we adhere to the following practices:
- We prioritize security updates for the current major release of our software.
- We actively monitor the GitHub Security Advisory system and the `#issues` channel on Discord for any vulnerability reports.
- We promptly review and validate reported vulnerabilities and take appropriate actions to address them.
- We release security patches and updates in a timely manner to mitigate any identified vulnerabilities.
Please note that as a security-conscious community, we may not always disclose detailed information about security issues until we have determined that doing so would not put our users or the project at risk. We appreciate your understanding and cooperation in these matters.
## Scope
This security policy applies to the following GitHub repository:
- Repository: [LibreChat](https://github.com/danny-avila/LibreChat)
## 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.
## Acknowledgments
We would like to express our gratitude to the security researchers and community members who help us improve the security of our project. Your contributions are invaluable, and we sincerely appreciate your efforts.
## Bug Bounty Program
We currently do not have a bug bounty program in place. However, we welcome and appreciate any
security-related contributions through pull requests (PRs) that address vulnerabilities in our codebase. We believe in the power of collaboration to improve the security of our project and invite you to join us in making it more robust.
**Reference**
- https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html
---
## [Go Back to ReadMe](README.md)

View File

@@ -1,173 +0,0 @@
##########################
# 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
NODE_ENV=production
# Change this to proxy any API request.
# It's useful if your machine has difficulty calling the original API server.
# PROXY=
# Change this to your MongoDB URI if different. I recommend appending chatgpt-clone.
MONGO_URI=mongodb://127.0.0.1:27017/chatgpt-clone
##########################
# OpenAI Endpoint:
##########################
# Access key from OpenAI platform.
# Leave it blank to disable this feature.
OPENAI_KEY=
# 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-0301,text-davinci-003,gpt-4
# Reverse proxy settings for OpenAI:
# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy
# OPENAI_REVERSE_PROXY=
##########################
# 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.
# AZURE_OPENAI_API_KEY=
# AZURE_OPENAI_API_INSTANCE_NAME=
# AZURE_OPENAI_API_DEPLOYMENT_NAME=
# AZURE_OPENAI_API_VERSION=
# AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME= # Optional, but may be used in future updates
# AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME= # Optional, but may be used in future updates
##########################
# BingAI Endpoint:
##########################
# Also used for Sydney and jailbreak
# BingAI Tokens: the "_U" cookies value from bing.com
# 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
##########################
# 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,text-davinci-002-render-paid,gpt-4
# Reverse proxy settings for ChatGPT
# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy
# By default, the server will use the node-chatgpt-api recommended proxy (a third party server).
# CHATGPT_REVERSE_PROXY=
##########################
# PaLM (Google) Endpoint:
##########################
# PaLM 2 Client (via Google Cloud Vertex AI API)
# Steps:
# Enable the Vertex AI API on Google Cloud:
# https://console.cloud.google.com/vertex-ai
# Create a Service Account:
# https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1
# Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
# Create a JSON key, rename as 'auth.json' and save it in /api/data/.
# Alternatively
# Uncomment below PALM_KEY and set as "user_provided" to allow the user to provide a Service Account key JSON from the UI.
# They will follow the steps above except for renaming the file.
# Leave blank or omit to disable this endpoint
# PALM_KEY="user_provided"
# In case you need a reverse proxy for this endpoint:
# GOOGLE_REVERSE_PROXY=
##########################
# 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=false
# 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:
##########################
# 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
#JWT:
JWT_SECRET_DEV=secret
# Add a secure secret for production if deploying to live domain.
JWT_SECRET_PROD=secret
# Set the expiration delay for the secure cookie with the JWT token
# Delay is in millisecond e.g. 7 days is 1000*60*60*24*7
SESSION_EXPIRY=1000 * 60 * 60 * 24 * 7
# Site URLs:
# Don't forget to set Node env to development in the Server configuration section above
# if you want to run in dev mode
CLIENT_URL_DEV=http://localhost:3090
SERVER_URL_DEV=http://localhost:3080
# Change these values to domain if deploying:
CLIENT_URL_PROD=http://localhost:3080
SERVER_URL_PROD=http://localhost:3080

View File

@@ -1,39 +0,0 @@
module.exports = {
env: {
es2021: true,
node: true
},
extends: ['eslint:recommended'],
overrides: [],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
indent: ['error', 2, { SwitchCase: 1 }],
'max-len': [
'error',
{
code: 150,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreComments: true
}
],
'linebreak-style': 0,
'arrow-parens': [2, 'as-needed', { requireForBlockBody: true }],
// 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-console': 'off',
'import/extensions': 'off',
'no-use-before-define': [
'error',
{
functions: false
}
],
'no-promise-executor-return': 'off',
'no-param-reassign': 'off',
'no-continue': 'off',
'no-restricted-syntax': 'off'
}
};

View File

@@ -1,22 +0,0 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"singleAttributePerLine": true,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 110,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"parser": "babel"
}

View File

@@ -14,22 +14,23 @@ const askBing = async ({
invocationId,
toneStyle,
token,
onProgress
onProgress,
}) => {
const { BingAIClient } = await import('og-chatgpt-api');
const { BingAIClient } = await import('@waylaidwanderer/chatgpt-api');
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
store: new KeyvFile({ filename: './data/cache.json' }),
};
const bingAIClient = new BingAIClient({
// "_U" cookie from bing.com
userToken: process.env.BINGAI_TOKEN == 'user_provided' ? token : process.env.BINGAI_TOKEN ?? null,
// userToken:
// process.env.BINGAI_TOKEN == 'user_provided' ? token : process.env.BINGAI_TOKEN ?? null,
// If the above doesn't work, provide all your cookies as a string instead
// cookies: '',
cookies: process.env.BINGAI_TOKEN == 'user_provided' ? token : process.env.BINGAI_TOKEN ?? null,
debug: false,
cache: store,
host: process.env.BINGAI_HOST || null,
proxy: process.env.PROXY || null
proxy: process.env.PROXY || null,
});
let options = {};
@@ -38,23 +39,43 @@ const askBing = async ({
jailbreakConversationId = false;
}
if (jailbreak)
if (jailbreak) {
options = {
jailbreakConversationId: jailbreakConversationId || jailbreak,
context,
systemMessage,
parentMessageId,
toneStyle,
onProgress
onProgress,
clientOptions: {
features: {
genImage: {
server: {
enable: true,
type: 'markdown_list',
},
},
},
},
};
else {
} else {
options = {
conversationId,
context,
systemMessage,
parentMessageId,
toneStyle,
onProgress
onProgress,
clientOptions: {
features: {
genImage: {
server: {
enable: true,
type: 'markdown_list',
},
},
},
},
};
// don't give those parameters for new conversation

View File

@@ -8,27 +8,30 @@ const browserClient = async ({
model,
token,
onProgress,
onEventMessage,
abortController,
userId
userId,
}) => {
const { ChatGPTBrowserClient } = await import('og-chatgpt-api');
const { ChatGPTBrowserClient } = await import('@waylaidwanderer/chatgpt-api');
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
store: new KeyvFile({ filename: './data/cache.json' }),
};
const clientOptions = {
// Warning: This will expose your access token to a third party. Consider the risks before using this.
reverseProxyUrl: process.env.CHATGPT_REVERSE_PROXY || 'https://ai.fakeopen.com/api/conversation',
reverseProxyUrl:
process.env.CHATGPT_REVERSE_PROXY || 'https://ai.fakeopen.com/api/conversation',
// Access token from https://chat.openai.com/api/auth/session
accessToken: process.env.CHATGPT_TOKEN == 'user_provided' ? token : process.env.CHATGPT_TOKEN ?? null,
accessToken:
process.env.CHATGPT_TOKEN == 'user_provided' ? token : process.env.CHATGPT_TOKEN ?? null,
model: model,
debug: false,
proxy: process.env.PROXY || null,
user: userId
user: userId,
};
const client = new ChatGPTBrowserClient(clientOptions, store);
let options = { onProgress, abortController };
let options = { onProgress, onEventMessage, abortController };
if (!!parentMessageId && !!conversationId) {
options = { ...options, parentMessageId, conversationId };

View File

@@ -0,0 +1,324 @@
const Keyv = require('keyv');
// const { Agent, ProxyAgent } = require('undici');
const BaseClient = require('./BaseClient');
const {
encoding_for_model: encodingForModel,
get_encoding: getEncoding,
} = require('@dqbd/tiktoken');
const Anthropic = require('@anthropic-ai/sdk');
const HUMAN_PROMPT = '\n\nHuman:';
const AI_PROMPT = '\n\nAssistant:';
const tokenizersCache = {};
class AnthropicClient extends BaseClient {
constructor(apiKey, options = {}, cacheOptions = {}) {
super(apiKey, options, cacheOptions);
cacheOptions.namespace = cacheOptions.namespace || 'anthropic';
this.conversationsCache = new Keyv(cacheOptions);
this.apiKey = apiKey || process.env.ANTHROPIC_API_KEY;
this.sender = 'Anthropic';
this.userLabel = HUMAN_PROMPT;
this.assistantLabel = AI_PROMPT;
this.setOptions(options);
}
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
// nested options aren't spread properly, so we need to do this manually
this.options.modelOptions = {
...this.options.modelOptions,
...options.modelOptions,
};
delete options.modelOptions;
// now we can merge options
this.options = {
...this.options,
...options,
};
} else {
this.options = options;
}
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 || 'claude-1',
temperature: typeof modelOptions.temperature === 'undefined' ? 0.7 : modelOptions.temperature, // 0 - 1, 0.7 is recommended
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.maxResponseTokens = this.modelOptions.maxOutputTokens || 1500;
this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) {
throw new Error(
`maxPromptTokens + maxOutputTokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${
this.maxPromptTokens + this.maxResponseTokens
}) must be less than or equal to maxContextTokens (${this.maxContextTokens})`,
);
}
this.startToken = '||>';
this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('cl100k_base');
if (!this.modelOptions.stop) {
const stopTokens = [this.startToken];
if (this.endToken && this.endToken !== this.startToken) {
stopTokens.push(this.endToken);
}
stopTokens.push(`${this.userLabel}`);
stopTokens.push('<|diff_marker|>');
this.modelOptions.stop = stopTokens;
}
return this;
}
getClient() {
if (this.options.reverseProxyUrl) {
return new Anthropic({
apiKey: this.apiKey,
baseURL: this.options.reverseProxyUrl,
});
} else {
return new Anthropic({
apiKey: this.apiKey,
});
}
}
async buildMessages(messages, parentMessageId) {
const orderedMessages = this.constructor.getMessagesForConversation(messages, parentMessageId);
if (this.options.debug) {
console.debug('AnthropicClient: orderedMessages', orderedMessages, parentMessageId);
}
const formattedMessages = orderedMessages.map((message) => ({
author: message.isCreatedByUser ? this.userLabel : this.assistantLabel,
content: message?.content ?? message.text,
}));
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}`;
}
const promptSuffix = `${promptPrefix}${this.assistantLabel}\n`; // Prompt AI to respond.
let currentTokenCount = 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 && formattedMessages.length > 0) {
const message = formattedMessages.pop();
const isCreatedByUser = message.author === this.userLabel;
const messageString = `${message.author}\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;
// 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();
}
const 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 };
}
getCompletion() {
console.log('AnthropicClient doesn\'t use getCompletion (all handled in sendCompletion)');
}
// TODO: implement abortController usage
async sendCompletion(payload, { onProgress, abortController }) {
if (!abortController) {
abortController = new AbortController();
}
const { signal } = abortController;
const modelOptions = { ...this.modelOptions };
if (typeof onProgress === 'function') {
modelOptions.stream = true;
}
const { debug } = this.options;
if (debug) {
console.debug();
console.debug(modelOptions);
console.debug();
}
const client = this.getClient();
const metadata = {
user_id: this.user,
};
let text = '';
const requestOptions = {
prompt: payload,
model: this.modelOptions.model,
stream: this.modelOptions.stream || true,
max_tokens_to_sample: this.modelOptions.maxOutputTokens || 1500,
metadata,
...modelOptions,
};
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);
}
signal.removeEventListener('abort', () => {
if (this.options.debug) {
console.log('AnthropicClient: message aborted!');
}
response.controller.abort();
});
return text.trim();
}
// I commented this out because I will need to refactor this for the BaseClient/all clients
// getMessageMapMethod() {
// return ((message) => ({
// author: message.isCreatedByUser ? this.userLabel : this.assistantLabel,
// content: message?.content ?? message.text
// })).bind(this);
// }
getSaveOptions() {
return {
promptPrefix: this.options.promptPrefix,
modelLabel: this.options.modelLabel,
...this.modelOptions,
};
}
getBuildMessagesOptions() {
if (this.options.debug) {
console.log('AnthropicClient doesn\'t use getBuildMessagesOptions');
}
}
static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) {
if (tokenizersCache[encoding]) {
return tokenizersCache[encoding];
}
let tokenizer;
if (isModelName) {
tokenizer = encodingForModel(encoding, extendSpecialTokens);
} else {
tokenizer = getEncoding(encoding, extendSpecialTokens);
}
tokenizersCache[encoding] = tokenizer;
return tokenizer;
}
getTokenCount(text) {
return this.gptEncoder.encode(text, 'all').length;
}
}
module.exports = AnthropicClient;

View File

@@ -0,0 +1,561 @@
const crypto = require('crypto');
const TextStream = require('./TextStream');
const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter');
const { ChatOpenAI } = require('langchain/chat_models/openai');
const { loadSummarizationChain } = require('langchain/chains');
const { refinePrompt } = require('./prompts/refinePrompt');
const { getConvo, getMessages, saveMessage, updateMessage, saveConvo } = require('../../models');
class BaseClient {
constructor(apiKey, options = {}) {
this.apiKey = apiKey;
this.sender = options.sender || 'AI';
this.contextStrategy = null;
this.currentDateString = new Date().toLocaleDateString('en-us', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
setOptions() {
throw new Error('Method \'setOptions\' must be implemented.');
}
getCompletion() {
throw new Error('Method \'getCompletion\' must be implemented.');
}
async sendCompletion() {
throw new Error('Method \'sendCompletion\' must be implemented.');
}
getSaveOptions() {
throw new Error('Subclasses must implement getSaveOptions');
}
async buildMessages() {
throw new Error('Subclasses must implement buildMessages');
}
getBuildMessagesOptions() {
throw new Error('Subclasses must implement getBuildMessagesOptions');
}
async generateTextStream(text, onProgress, options = {}) {
const stream = new TextStream(text, options);
await stream.processTextStream(onProgress);
}
async setMessageOptions(opts = {}) {
if (opts && typeof opts === 'object') {
this.setOptions(opts);
}
const user = opts.user || null;
const conversationId = opts.conversationId || crypto.randomUUID();
const parentMessageId = opts.parentMessageId || '00000000-0000-0000-0000-000000000000';
const userMessageId = opts.overrideParentMessageId || crypto.randomUUID();
const responseMessageId = crypto.randomUUID();
const saveOptions = this.getSaveOptions();
this.abortController = opts.abortController || new AbortController();
this.currentMessages = (await this.loadHistory(conversationId, parentMessageId)) ?? [];
return {
...opts,
user,
conversationId,
parentMessageId,
userMessageId,
responseMessageId,
saveOptions,
};
}
createUserMessage({ messageId, parentMessageId, conversationId, text }) {
const userMessage = {
messageId,
parentMessageId,
conversationId,
sender: 'User',
text,
isCreatedByUser: true,
};
return userMessage;
}
async handleStartMethods(message, opts) {
const { user, conversationId, parentMessageId, userMessageId, responseMessageId, saveOptions } =
await this.setMessageOptions(opts);
const userMessage = this.createUserMessage({
messageId: userMessageId,
parentMessageId,
conversationId,
text: message,
});
if (typeof opts?.getIds === 'function') {
opts.getIds({
userMessage,
conversationId,
responseMessageId,
});
}
if (typeof opts?.onStart === 'function') {
opts.onStart(userMessage);
}
return {
...opts,
user,
conversationId,
responseMessageId,
saveOptions,
userMessage,
};
}
addInstructions(messages, instructions) {
const payload = [];
if (!instructions) {
return messages;
}
if (messages.length > 1) {
payload.push(...messages.slice(0, -1));
}
payload.push(instructions);
if (messages.length > 0) {
payload.push(messages[messages.length - 1]);
}
return payload;
}
async handleTokenCountMap(tokenCountMap) {
if (this.currentMessages.length === 0) {
return;
}
for (let i = 0; i < this.currentMessages.length; i++) {
// Skip the last message, which is the user message.
if (i === this.currentMessages.length - 1) {
break;
}
const message = this.currentMessages[i];
const { messageId } = message;
const update = {};
if (messageId === tokenCountMap.refined?.messageId) {
if (this.options.debug) {
console.debug(`Adding refined props to ${messageId}.`);
}
update.refinedMessageText = tokenCountMap.refined.content;
update.refinedTokenCount = tokenCountMap.refined.tokenCount;
}
if (message.tokenCount && !update.refinedTokenCount) {
if (this.options.debug) {
console.debug(`Skipping ${messageId}: already had a token count.`);
}
continue;
}
const tokenCount = tokenCountMap[messageId];
if (tokenCount) {
message.tokenCount = tokenCount;
update.tokenCount = tokenCount;
await this.updateMessageInDatabase({ messageId, ...update });
}
}
}
concatenateMessages(messages) {
return messages.reduce((acc, message) => {
const nameOrRole = message.name ?? message.role;
return acc + `${nameOrRole}:\n${message.content}\n\n`;
}, '');
}
async refineMessages(messagesToRefine, remainingContextTokens) {
const model = new ChatOpenAI({ temperature: 0 });
const chain = loadSummarizationChain(model, {
type: 'refine',
verbose: this.options.debug,
refinePrompt,
});
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1500,
chunkOverlap: 100,
});
const userMessages = this.concatenateMessages(
messagesToRefine.filter((m) => m.role === 'user'),
);
const assistantMessages = this.concatenateMessages(
messagesToRefine.filter((m) => m.role !== 'user'),
);
const userDocs = await splitter.createDocuments([userMessages], [], {
chunkHeader: 'DOCUMENT NAME: User Message\n\n---\n\n',
appendChunkOverlapHeader: true,
});
const assistantDocs = await splitter.createDocuments([assistantMessages], [], {
chunkHeader: 'DOCUMENT NAME: Assistant Message\n\n---\n\n',
appendChunkOverlapHeader: true,
});
// const chunkSize = Math.round(concatenatedMessages.length / 512);
const input_documents = userDocs.concat(assistantDocs);
if (this.options.debug) {
console.debug('Refining messages...');
}
try {
const res = await chain.call({
input_documents,
signal: this.abortController.signal,
});
const refinedMessage = {
role: 'assistant',
content: res.output_text,
tokenCount: this.getTokenCount(res.output_text),
};
if (this.options.debug) {
console.debug('Refined messages', refinedMessage);
console.debug(
`remainingContextTokens: ${remainingContextTokens}, after refining: ${
remainingContextTokens - refinedMessage.tokenCount
}`,
);
}
return refinedMessage;
} catch (e) {
console.error('Error refining messages');
console.error(e);
return null;
}
}
/**
* This method processes an array of messages and returns a context of messages that fit within a token limit.
* It iterates over the messages from newest to oldest, adding them to the context until the token limit is reached.
* If the token limit would be exceeded by adding a message, that message and possibly the previous one are added to a separate array of messages to refine.
* The method uses `push` and `pop` operations for efficient array manipulation, and reverses the arrays at the end to maintain the original order of the messages.
* The method also includes a mechanism to avoid blocking the event loop by waiting for the next tick after each iteration.
*
* @param {Array} messages - An array of messages, each with a `tokenCount` property. The messages should be ordered from oldest to newest.
* @returns {Object} An object with three properties: `context`, `remainingContextTokens`, and `messagesToRefine`. `context` is an array of messages that fit within the token limit. `remainingContextTokens` is the number of tokens remaining within the limit after adding the messages to the context. `messagesToRefine` is an array of messages that were not added to the context because they would have exceeded the token limit.
*/
async getMessagesWithinTokenLimit(messages) {
let currentTokenCount = 0;
let context = [];
let messagesToRefine = [];
let refineIndex = -1;
let remainingContextTokens = this.maxContextTokens;
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
const newTokenCount = currentTokenCount + message.tokenCount;
const exceededLimit = newTokenCount > this.maxContextTokens;
let shouldRefine = exceededLimit && this.shouldRefineContext;
let refineNextMessage = i !== 0 && i !== 1 && context.length > 0;
if (shouldRefine) {
messagesToRefine.push(message);
if (refineIndex === -1) {
refineIndex = i;
}
if (refineNextMessage) {
refineIndex = i + 1;
const removedMessage = context.pop();
messagesToRefine.push(removedMessage);
currentTokenCount -= removedMessage.tokenCount;
remainingContextTokens = this.maxContextTokens - currentTokenCount;
refineNextMessage = false;
}
continue;
} else if (exceededLimit) {
break;
}
context.push(message);
currentTokenCount = newTokenCount;
remainingContextTokens = this.maxContextTokens - currentTokenCount;
await new Promise((resolve) => setImmediate(resolve));
}
return {
context: context.reverse(),
remainingContextTokens,
messagesToRefine: messagesToRefine.reverse(),
refineIndex,
};
}
async handleContextStrategy({ instructions, orderedMessages, formattedMessages }) {
let payload = this.addInstructions(formattedMessages, instructions);
let orderedWithInstructions = this.addInstructions(orderedMessages, instructions);
let { context, remainingContextTokens, messagesToRefine, refineIndex } =
await this.getMessagesWithinTokenLimit(payload);
payload = context;
let refinedMessage;
// if (messagesToRefine.length > 0) {
// refinedMessage = await this.refineMessages(messagesToRefine, remainingContextTokens);
// payload.unshift(refinedMessage);
// remainingContextTokens -= refinedMessage.tokenCount;
// }
// if (remainingContextTokens <= instructions?.tokenCount) {
// if (this.options.debug) {
// console.debug(`Remaining context (${remainingContextTokens}) is less than instructions token count: ${instructions.tokenCount}`);
// }
// ({ context, remainingContextTokens, messagesToRefine, refineIndex } = await this.getMessagesWithinTokenLimit(payload));
// payload = context;
// }
// Calculate the difference in length to determine how many messages were discarded if any
let diff = orderedWithInstructions.length - payload.length;
if (this.options.debug) {
console.debug('<---------------------------------DIFF--------------------------------->');
console.debug(
`Difference between payload (${payload.length}) and orderedWithInstructions (${orderedWithInstructions.length}): ${diff}`,
);
console.debug(
'remainingContextTokens, this.maxContextTokens (1/2)',
remainingContextTokens,
this.maxContextTokens,
);
}
// If the difference is positive, slice the orderedWithInstructions array
if (diff > 0) {
orderedWithInstructions = orderedWithInstructions.slice(diff);
}
if (messagesToRefine.length > 0) {
refinedMessage = await this.refineMessages(messagesToRefine, remainingContextTokens);
payload.unshift(refinedMessage);
remainingContextTokens -= refinedMessage.tokenCount;
}
if (this.options.debug) {
console.debug(
'remainingContextTokens, this.maxContextTokens (2/2)',
remainingContextTokens,
this.maxContextTokens,
);
}
let tokenCountMap = orderedWithInstructions.reduce((map, message, index) => {
if (!message.messageId) {
return map;
}
if (index === refineIndex) {
map.refined = { ...refinedMessage, messageId: message.messageId };
}
map[message.messageId] = payload[index].tokenCount;
return map;
}, {});
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, this.maxContextTokens);
}
return { payload, tokenCountMap, promptTokens, messages: orderedWithInstructions };
}
async sendMessage(message, opts = {}) {
const { user, conversationId, responseMessageId, saveOptions, userMessage } =
await this.handleStartMethods(message, opts);
this.user = user;
// It's not necessary to push to currentMessages
// depending on subclass implementation of handling messages
this.currentMessages.push(userMessage);
let {
prompt: payload,
tokenCountMap,
promptTokens,
} = await this.buildMessages(
this.currentMessages,
// When the userMessage is pushed to currentMessages, the parentMessage is the userMessageId.
// this only matters when buildMessages is utilizing the parentMessageId, and may vary on implementation
userMessage.messageId,
this.getBuildMessagesOptions(opts),
);
if (this.options.debug) {
console.debug('payload');
console.debug(payload);
}
if (tokenCountMap) {
console.dir(tokenCountMap, { depth: null });
if (tokenCountMap[userMessage.messageId]) {
userMessage.tokenCount = tokenCountMap[userMessage.messageId];
console.log('userMessage.tokenCount', userMessage.tokenCount);
console.log('userMessage', userMessage);
}
payload = payload.map((message) => {
const messageWithoutTokenCount = message;
delete messageWithoutTokenCount.tokenCount;
return messageWithoutTokenCount;
});
this.handleTokenCountMap(tokenCountMap);
}
await this.saveMessageToDatabase(userMessage, saveOptions, user);
const responseMessage = {
messageId: responseMessageId,
conversationId,
parentMessageId: userMessage.messageId,
isCreatedByUser: false,
model: this.modelOptions.model,
sender: this.sender,
text: await this.sendCompletion(payload, opts),
promptTokens,
};
if (tokenCountMap && this.getTokenCountForResponse) {
responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage);
responseMessage.completionTokens = responseMessage.tokenCount;
}
await this.saveMessageToDatabase(responseMessage, saveOptions, user);
delete responseMessage.tokenCount;
return responseMessage;
}
async getConversation(conversationId, user = null) {
return await getConvo(user, conversationId);
}
async loadHistory(conversationId, parentMessageId = null) {
if (this.options.debug) {
console.debug('Loading history for conversation', conversationId, parentMessageId);
}
const messages = (await getMessages({ conversationId })) || [];
if (messages.length === 0) {
return [];
}
let mapMethod = null;
if (this.getMessageMapMethod) {
mapMethod = this.getMessageMapMethod();
}
return this.constructor.getMessagesForConversation(messages, parentMessageId, mapMethod);
}
async saveMessageToDatabase(message, endpointOptions, user = null) {
await saveMessage({ ...message, unfinished: false, cancelled: false });
await saveConvo(user, {
conversationId: message.conversationId,
endpoint: this.options.endpoint,
...endpointOptions,
});
}
async updateMessageInDatabase(message) {
await updateMessage(message);
}
/**
* Iterate through messages, building an array based on the parentMessageId.
* Each message has an id and a parentMessageId. The parentMessageId is the id of the message that this message is a reply to.
* @param messages
* @param parentMessageId
* @returns {*[]} An array containing the messages in the order they should be displayed, starting with the root message.
*/
static getMessagesForConversation(messages, parentMessageId, mapMethod = null) {
if (!messages || messages.length === 0) {
return [];
}
const orderedMessages = [];
let currentMessageId = parentMessageId;
while (currentMessageId) {
const message = messages.find((msg) => {
const messageId = msg.messageId ?? msg.id;
return messageId === currentMessageId;
});
if (!message) {
break;
}
orderedMessages.unshift(message);
currentMessageId = message.parentMessageId;
}
if (mapMethod) {
return orderedMessages.map(mapMethod);
}
return orderedMessages;
}
/**
* Algorithm adapted from "6. Counting tokens for chat API calls" of
* https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
*
* An additional 2 tokens need to be added for metadata after all messages have been counted.
*
* @param {*} message
*/
getTokenCountForMessage(message) {
let tokensPerMessage;
let nameAdjustment;
if (this.modelOptions.model.startsWith('gpt-4')) {
tokensPerMessage = 3;
nameAdjustment = 1;
} else {
tokensPerMessage = 4;
nameAdjustment = -1;
}
if (this.options.debug) {
console.debug('getTokenCountForMessage', message);
}
// Map each property of the message to the number of tokens it contains
const propertyTokenCounts = Object.entries(message).map(([key, value]) => {
if (key === 'tokenCount' || typeof value !== 'string') {
return 0;
}
// Count the number of tokens in the property value
const numTokens = this.getTokenCount(value);
// Adjust by `nameAdjustment` tokens if the property key is 'name'
const adjustment = key === 'name' ? nameAdjustment : 0;
return numTokens + adjustment;
});
if (this.options.debug) {
console.debug('propertyTokenCounts', propertyTokenCounts);
}
// Sum the number of tokens in all properties and add `tokensPerMessage` for metadata
return propertyTokenCounts.reduce((a, b) => a + b, tokensPerMessage);
}
}
module.exports = BaseClient;

View File

@@ -0,0 +1,587 @@
const crypto = require('crypto');
const Keyv = require('keyv');
const {
encoding_for_model: encodingForModel,
get_encoding: getEncoding,
} = require('@dqbd/tiktoken');
const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source');
const { Agent, ProxyAgent } = require('undici');
const BaseClient = require('./BaseClient');
const CHATGPT_MODEL = 'gpt-3.5-turbo';
const tokenizersCache = {};
class ChatGPTClient extends BaseClient {
constructor(apiKey, options = {}, cacheOptions = {}) {
super(apiKey, options, cacheOptions);
cacheOptions.namespace = cacheOptions.namespace || 'chatgpt';
this.conversationsCache = new Keyv(cacheOptions);
this.setOptions(options);
}
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
// nested options aren't spread properly, so we need to do this manually
this.options.modelOptions = {
...this.options.modelOptions,
...options.modelOptions,
};
delete options.modelOptions;
// now we can merge options
this.options = {
...this.options,
...options,
};
} else {
this.options = options;
}
if (this.options.openaiApiKey) {
this.apiKey = this.options.openaiApiKey;
}
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 || CHATGPT_MODEL,
temperature: typeof modelOptions.temperature === 'undefined' ? 0.8 : modelOptions.temperature,
top_p: typeof modelOptions.top_p === 'undefined' ? 1 : modelOptions.top_p,
presence_penalty:
typeof modelOptions.presence_penalty === 'undefined' ? 1 : modelOptions.presence_penalty,
stop: modelOptions.stop,
};
this.isChatGptModel = this.modelOptions.model.startsWith('gpt-');
const { isChatGptModel } = this;
this.isUnofficialChatGptModel =
this.modelOptions.model.startsWith('text-chat') ||
this.modelOptions.model.startsWith('text-davinci-002-render');
const { isUnofficialChatGptModel } = this;
// Davinci models have a max context length of 4097 tokens.
this.maxContextTokens = this.options.maxContextTokens || (isChatGptModel ? 4095 : 4097);
// I decided to reserve 1024 tokens for the response.
// 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.max_tokens || 1024;
this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) {
throw new Error(
`maxPromptTokens + max_tokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${
this.maxPromptTokens + this.maxResponseTokens
}) must be less than or equal to maxContextTokens (${this.maxContextTokens})`,
);
}
this.userLabel = this.options.userLabel || 'User';
this.chatGptLabel = this.options.chatGptLabel || 'ChatGPT';
if (isChatGptModel) {
// 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.
this.startToken = '||>';
this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('cl100k_base');
} else if (isUnofficialChatGptModel) {
this.startToken = '<|im_start|>';
this.endToken = '<|im_end|>';
this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, {
'<|im_start|>': 100264,
'<|im_end|>': 100265,
});
} else {
// Previously I was trying to use "<|endoftext|>" but there seems to be some bug with OpenAI's token counting
// system that causes only the first "<|endoftext|>" to be counted as 1 token, and the rest are not treated
// as a single token. So we're using this instead.
this.startToken = '||>';
this.endToken = '';
try {
this.gptEncoder = this.constructor.getTokenizer(this.modelOptions.model, true);
} catch {
this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true);
}
}
if (!this.modelOptions.stop) {
const stopTokens = [this.startToken];
if (this.endToken && this.endToken !== this.startToken) {
stopTokens.push(this.endToken);
}
stopTokens.push(`\n${this.userLabel}:`);
stopTokens.push('<|diff_marker|>');
// I chose not to do one for `chatGptLabel` because I've never seen it happen
this.modelOptions.stop = stopTokens;
}
if (this.options.reverseProxyUrl) {
this.completionsUrl = this.options.reverseProxyUrl;
} else if (isChatGptModel) {
this.completionsUrl = 'https://api.openai.com/v1/chat/completions';
} else {
this.completionsUrl = 'https://api.openai.com/v1/completions';
}
return this;
}
static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) {
if (tokenizersCache[encoding]) {
return tokenizersCache[encoding];
}
let tokenizer;
if (isModelName) {
tokenizer = encodingForModel(encoding, extendSpecialTokens);
} else {
tokenizer = getEncoding(encoding, extendSpecialTokens);
}
tokenizersCache[encoding] = tokenizer;
return tokenizer;
}
async getCompletion(input, onProgress, abortController = null) {
if (!abortController) {
abortController = new AbortController();
}
const modelOptions = { ...this.modelOptions };
if (typeof onProgress === 'function') {
modelOptions.stream = true;
}
if (this.isChatGptModel) {
modelOptions.messages = input;
} else {
modelOptions.prompt = input;
}
const { debug } = this.options;
const url = this.completionsUrl;
if (debug) {
console.debug();
console.debug(url);
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;
} else if (this.apiKey) {
opts.headers.Authorization = `Bearer ${this.apiKey}`;
}
if (this.options.headers) {
opts.headers = { ...opts.headers, ...this.options.headers };
}
if (this.options.proxy) {
opts.dispatcher = new ProxyAgent(this.options.proxy);
}
if (modelOptions.stream) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
try {
let done = false;
await fetchEventSource(url, {
...opts,
signal: abortController.signal,
async onopen(response) {
if (response.status === 200) {
return;
}
if (debug) {
console.debug(response);
}
let error;
try {
const body = await response.text();
error = new Error(`Failed to send message. HTTP ${response.status} - ${body}`);
error.status = response.status;
error.json = JSON.parse(body);
} catch {
error = error || new Error(`Failed to send message. HTTP ${response.status}`);
}
throw error;
},
onclose() {
if (debug) {
console.debug('Server closed the connection unexpectedly, returning...');
}
// workaround for private API not sending [DONE] event
if (!done) {
onProgress('[DONE]');
abortController.abort();
resolve();
}
},
onerror(err) {
if (debug) {
console.debug(err);
}
// rethrow to stop the operation
throw err;
},
onmessage(message) {
if (debug) {
// console.debug(message);
}
if (!message.data || message.event === 'ping') {
return;
}
if (message.data === '[DONE]') {
onProgress('[DONE]');
abortController.abort();
resolve();
done = true;
return;
}
onProgress(JSON.parse(message.data));
},
});
} catch (err) {
reject(err);
}
});
}
const response = await fetch(url, {
...opts,
signal: abortController.signal,
});
if (response.status !== 200) {
const body = await response.text();
const error = new Error(`Failed to send message. HTTP ${response.status} - ${body}`);
error.status = response.status;
try {
error.json = JSON.parse(body);
} catch {
error.body = body;
}
throw error;
}
return response.json();
}
async generateTitle(userMessage, botMessage) {
const instructionsPayload = {
role: 'system',
content: `Write an extremely concise subtitle for this conversation with no more than a few words. All words should be capitalized. Exclude punctuation.
||>Message:
${userMessage.message}
||>Response:
${botMessage.message}
||>Title:`,
};
const titleGenClientOptions = JSON.parse(JSON.stringify(this.options));
titleGenClientOptions.modelOptions = {
model: 'gpt-3.5-turbo',
temperature: 0,
presence_penalty: 0,
frequency_penalty: 0,
};
const titleGenClient = new ChatGPTClient(this.apiKey, titleGenClientOptions);
const result = await titleGenClient.getCompletion([instructionsPayload], null);
// remove any non-alphanumeric characters, replace multiple spaces with 1, and then trim
return result.choices[0].message.content
.replace(/[^a-zA-Z0-9' ]/g, '')
.replace(/\s+/g, ' ')
.trim();
}
async sendMessage(message, opts = {}) {
if (opts.clientOptions && typeof opts.clientOptions === 'object') {
this.setOptions(opts.clientOptions);
}
const conversationId = opts.conversationId || crypto.randomUUID();
const parentMessageId = opts.parentMessageId || crypto.randomUUID();
let conversation =
typeof opts.conversation === 'object'
? opts.conversation
: await this.conversationsCache.get(conversationId);
let isNewConversation = false;
if (!conversation) {
conversation = {
messages: [],
createdAt: Date.now(),
};
isNewConversation = true;
}
const shouldGenerateTitle = opts.shouldGenerateTitle && isNewConversation;
const userMessage = {
id: crypto.randomUUID(),
parentMessageId,
role: 'User',
message,
};
conversation.messages.push(userMessage);
// Doing it this way instead of having each message be a separate element in the array seems to be more reliable,
// especially when it comes to keeping the AI in character. It also seems to improve coherency and context retention.
const { prompt: payload, context } = await this.buildPrompt(
conversation.messages,
userMessage.id,
{
isChatGptModel: this.isChatGptModel,
promptPrefix: opts.promptPrefix,
},
);
if (this.options.keepNecessaryMessagesOnly) {
conversation.messages = context;
}
let reply = '';
let result = null;
if (typeof opts.onProgress === 'function') {
await this.getCompletion(
payload,
(progressMessage) => {
if (progressMessage === '[DONE]') {
return;
}
const token = this.isChatGptModel
? progressMessage.choices[0].delta.content
: progressMessage.choices[0].text;
// first event's delta content is always undefined
if (!token) {
return;
}
if (this.options.debug) {
console.debug(token);
}
if (token === this.endToken) {
return;
}
opts.onProgress(token);
reply += token;
},
opts.abortController || new AbortController(),
);
} else {
result = await this.getCompletion(
payload,
null,
opts.abortController || new AbortController(),
);
if (this.options.debug) {
console.debug(JSON.stringify(result));
}
if (this.isChatGptModel) {
reply = result.choices[0].message.content;
} else {
reply = result.choices[0].text.replace(this.endToken, '');
}
}
// avoids some rendering issues when using the CLI app
if (this.options.debug) {
console.debug();
}
reply = reply.trim();
const replyMessage = {
id: crypto.randomUUID(),
parentMessageId: userMessage.id,
role: 'ChatGPT',
message: reply,
};
conversation.messages.push(replyMessage);
const returnData = {
response: replyMessage.message,
conversationId,
parentMessageId: replyMessage.parentMessageId,
messageId: replyMessage.id,
details: result || {},
};
if (shouldGenerateTitle) {
conversation.title = await this.generateTitle(userMessage, replyMessage);
returnData.title = conversation.title;
}
await this.conversationsCache.set(conversationId, conversation);
if (this.options.returnConversation) {
returnData.conversation = conversation;
}
return returnData;
}
async buildPrompt(messages, parentMessageId, { isChatGptModel = false, promptPrefix = null }) {
const orderedMessages = this.constructor.getMessagesForConversation(messages, parentMessageId);
promptPrefix = (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 = `${this.startToken}Instructions:\n${promptPrefix}`;
} else {
const currentDateString = new Date().toLocaleDateString('en-us', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
promptPrefix = `${this.startToken}Instructions:\nYou are ChatGPT, a large language model trained by OpenAI. Respond conversationally.\nCurrent date: ${currentDateString}${this.endToken}\n\n`;
}
const promptSuffix = `${this.startToken}${this.chatGptLabel}:\n`; // Prompt ChatGPT to respond.
const instructionsPayload = {
role: 'system',
name: 'instructions',
content: promptPrefix,
};
const messagePayload = {
role: 'system',
content: promptSuffix,
};
let currentTokenCount;
if (isChatGptModel) {
currentTokenCount =
this.getTokenCountForMessage(instructionsPayload) +
this.getTokenCountForMessage(messagePayload);
} else {
currentTokenCount = this.getTokenCount(`${promptPrefix}${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.
const buildPromptBody = async () => {
if (currentTokenCount < maxTokenCount && orderedMessages.length > 0) {
const message = orderedMessages.pop();
const roleLabel =
message?.isCreatedByUser || message?.role?.toLowerCase() === 'user'
? this.userLabel
: this.chatGptLabel;
const messageString = `${this.startToken}${roleLabel}:\n${
message?.text ?? message?.message
}${this.endToken}\n`;
let newPromptBody;
if (promptBody || isChatGptModel) {
newPromptBody = `${messageString}${promptBody}`;
} else {
// Always insert prompt prefix before the last user message, if not gpt-3.5-turbo.
// This makes the AI obey the prompt instructions better, which is important for custom instructions.
// After a bunch of testing, it doesn't seem to cause the AI any confusion, even if you ask it things
// like "what's the last thing I wrote?".
newPromptBody = `${promptPrefix}${messageString}${promptBody}`;
}
context.unshift(message);
const tokenCountForMessage = this.getTokenCount(messageString);
const newTokenCount = currentTokenCount + tokenCountForMessage;
if (newTokenCount > maxTokenCount) {
if (promptBody) {
// This message would put us over the token limit, so don't add it.
return false;
}
// 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.`,
);
}
promptBody = newPromptBody;
currentTokenCount = newTokenCount;
// wait for next tick to avoid blocking the event loop
await new Promise((resolve) => setImmediate(resolve));
return buildPromptBody();
}
return true;
};
await buildPromptBody();
const prompt = `${promptBody}${promptSuffix}`;
if (isChatGptModel) {
messagePayload.content = prompt;
// 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.max_tokens = Math.min(
this.maxContextTokens - currentTokenCount,
this.maxResponseTokens,
);
if (this.options.debug) {
console.debug(`Prompt : ${prompt}`);
}
if (isChatGptModel) {
return { prompt: [instructionsPayload, messagePayload], context };
}
return { prompt, context };
}
getTokenCount(text) {
return this.gptEncoder.encode(text, 'all').length;
}
/**
* Algorithm adapted from "6. Counting tokens for chat API calls" of
* https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
*
* An additional 2 tokens need to be added for metadata after all messages have been counted.
*
* @param {*} message
*/
getTokenCountForMessage(message) {
let tokensPerMessage;
let nameAdjustment;
if (this.modelOptions.model.startsWith('gpt-4')) {
tokensPerMessage = 3;
nameAdjustment = 1;
} else {
tokensPerMessage = 4;
nameAdjustment = -1;
}
// Map each property of the message to the number of tokens it contains
const propertyTokenCounts = Object.entries(message).map(([key, value]) => {
// Count the number of tokens in the property value
const numTokens = this.getTokenCount(value);
// Adjust by `nameAdjustment` tokens if the property key is 'name'
const adjustment = key === 'name' ? nameAdjustment : 0;
return numTokens + adjustment;
});
// Sum the number of tokens in all properties and add `tokensPerMessage` for metadata
return propertyTokenCounts.reduce((a, b) => a + b, tokensPerMessage);
}
}
module.exports = ChatGPTClient;

View File

@@ -1,48 +1,62 @@
const crypto = require('crypto');
const TextStream = require('../stream');
const BaseClient = require('./BaseClient');
const { google } = require('googleapis');
const { Agent, ProxyAgent } = require('undici');
const { getMessages, saveMessage, saveConvo } = require('../../models');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('@dqbd/tiktoken');
const {
encoding_for_model: encodingForModel,
get_encoding: getEncoding,
} = require('@dqbd/tiktoken');
const tokenizersCache = {};
class GoogleAgent {
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';
this.setOptions(options);
this.currentDateString = new Date().toLocaleDateString('en-us', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
/* Google/PaLM2 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`;
}
async getClient() {
const scopes = ['https://www.googleapis.com/auth/cloud-platform'];
const jwtClient = new google.auth.JWT(this.client_email, null, this.private_key, scopes);
jwtClient.authorize((err) => {
if (err) {
console.log(err);
throw err;
}
});
return jwtClient;
}
/* Required Client methods */
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
// nested options aren't spread properly, so we need to do this manually
this.options.modelOptions = {
...this.options.modelOptions,
...options.modelOptions
...options.modelOptions,
};
delete options.modelOptions;
// now we can merge options
this.options = {
...this.options,
...options
...options,
};
} else {
this.options = options;
}
this.options.examples = this.options.examples.filter(
obj => obj.input.content !== '' && obj.output.content !== ''
(obj) => obj.input.content !== '' && obj.output.content !== '',
);
const modelOptions = this.options.modelOptions || {};
@@ -52,7 +66,7 @@ class GoogleAgent {
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
topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK, // 1-40, default: 40
// stop: modelOptions.stop // no stop method for now
};
@@ -65,13 +79,14 @@ class GoogleAgent {
// 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.maxPromptTokens = this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) {
throw new Error(
`maxPromptTokens + maxOutputTokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${
this.maxPromptTokens + this.maxResponseTokens
}) must be less than or equal to maxContextTokens (${this.maxContextTokens})`
}) must be less than or equal to maxContextTokens (${this.maxContextTokens})`,
);
}
@@ -90,7 +105,7 @@ class GoogleAgent {
this.endToken = '<|im_end|>';
this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, {
'<|im_start|>': 100264,
'<|im_end|>': 100265
'<|im_end|>': 100265,
});
} else {
// Previously I was trying to use "<|endoftext|>" but there seems to be some bug with OpenAI's token counting
@@ -125,42 +140,22 @@ class GoogleAgent {
return this;
}
static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) {
if (tokenizersCache[encoding]) {
return tokenizersCache[encoding];
}
let tokenizer;
if (isModelName) {
tokenizer = encodingForModel(encoding, extendSpecialTokens);
} else {
tokenizer = getEncoding(encoding, extendSpecialTokens);
}
tokenizersCache[encoding] = tokenizer;
return tokenizer;
getMessageMapMethod() {
return ((message) => ({
author: message?.author ?? (message.isCreatedByUser ? this.userLabel : this.modelLabel),
content: message?.content ?? message.text,
})).bind(this);
}
async getClient() {
const scopes = ['https://www.googleapis.com/auth/cloud-platform'];
const jwtClient = new google.auth.JWT(this.client_email, null, this.private_key, scopes);
jwtClient.authorize((err) => {
if (err) {
console.log(err);
throw err;
}
});
return jwtClient;
}
buildPayload(input, { messages = [] }) {
buildMessages(messages = []) {
const formattedMessages = messages.map(this.getMessageMapMethod());
let payload = {
instances: [
{
messages: [...messages, { author: this.userLabel, content: input }]
}
messages: formattedMessages,
},
],
parameters: this.options.modelOptions
parameters: this.options.modelOptions,
};
if (this.options.promptPrefix) {
@@ -171,23 +166,24 @@ class GoogleAgent {
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: input
}
prompt: messages[messages.length - 1].content,
},
];
}
if (this.options.debug) {
console.debug('buildPayload');
console.debug('GoogleClient buildMessages');
console.dir(payload, { depth: null });
}
return payload;
return { prompt: payload };
}
async getCompletion(input, messages = [], abortController = null) {
async getCompletion(payload, abortController = null) {
if (!abortController) {
abortController = new AbortController();
}
@@ -203,9 +199,9 @@ class GoogleAgent {
method: 'POST',
agent: new Agent({
bodyTimeout: 0,
headersTimeout: 0
headersTimeout: 0,
}),
signal: abortController.signal
signal: abortController.signal,
};
if (this.options.proxy) {
@@ -213,88 +209,37 @@ class GoogleAgent {
}
const client = await this.getClient();
const payload = this.buildPayload(input, { messages });
const res = await client.request({ url, method: 'POST', data: payload });
console.dir(res.data, { depth: null });
return res.data;
}
async loadHistory(conversationId, parentMessageId = null) {
if (this.options.debug) {
console.debug('Loading history for conversation', conversationId, parentMessageId);
}
if (!parentMessageId) {
return [];
}
const messages = (await getMessages({ conversationId })) || [];
if (messages.length === 0) {
this.currentMessages = [];
return [];
}
const orderedMessages = this.constructor.getMessagesForConversation(messages, parentMessageId);
return orderedMessages.map((message) => {
return {
author: message.isCreatedByUser ? this.userLabel : this.modelLabel,
content: message.content
};
});
}
async saveMessageToDatabase(message, user = null) {
await saveMessage({ ...message, unfinished: false });
await saveConvo(user, {
conversationId: message.conversationId,
endpoint: 'google',
...this.modelOptions
});
}
async sendMessage(message, opts = {}) {
if (opts && typeof opts === 'object') {
this.setOptions(opts);
}
console.log('sendMessage', message, opts);
const user = opts.user || null;
const conversationId = opts.conversationId || crypto.randomUUID();
const parentMessageId = opts.parentMessageId || '00000000-0000-0000-0000-000000000000';
const userMessageId = crypto.randomUUID();
const responseMessageId = crypto.randomUUID();
const messages = await this.loadHistory(conversationId, this.options?.parentMessageId);
const userMessage = {
messageId: userMessageId,
parentMessageId,
conversationId,
sender: 'User',
text: message,
isCreatedByUser: true
getSaveOptions() {
return {
promptPrefix: this.options.promptPrefix,
modelLabel: this.options.modelLabel,
...this.modelOptions,
};
}
if (typeof opts?.getIds === 'function') {
opts.getIds({
userMessage,
conversationId,
responseMessageId
});
}
getBuildMessagesOptions() {
// console.log('GoogleClient doesn\'t use getBuildMessagesOptions');
}
console.log('userMessage', userMessage);
await this.saveMessageToDatabase(userMessage, user);
async sendCompletion(payload, opts = {}) {
console.log('GoogleClient: sendcompletion', payload, opts);
let reply = '';
let blocked = false;
try {
const result = await this.getCompletion(message, messages, opts.abortController);
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 || '';
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
result.predictions[0].safetyAttributes,
)}${reply.length > 0 ? `\nAI Response:\n${reply}` : ''}`;
}
if (this.options.debug) {
@@ -305,86 +250,31 @@ class GoogleAgent {
console.error(err);
}
if (this.options.debug) {
console.debug('options');
console.debug(this.options);
}
if (!blocked) {
const textStream = new TextStream(reply, { delay: 0.5 });
await textStream.processTextStream(opts.onProgress);
await this.generateTextStream(reply, opts.onProgress, { delay: 0.5 });
}
const responseMessage = {
messageId: responseMessageId,
conversationId,
parentMessageId: userMessage.messageId,
sender: 'PaLM2',
text: reply,
error: blocked,
isCreatedByUser: false
};
return reply.trim();
}
await this.saveMessageToDatabase(responseMessage, user);
return responseMessage;
/* TO-DO: Handle tokens with Google tokenization NOTE: these are required */
static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) {
if (tokenizersCache[encoding]) {
return tokenizersCache[encoding];
}
let tokenizer;
if (isModelName) {
tokenizer = encodingForModel(encoding, extendSpecialTokens);
} else {
tokenizer = getEncoding(encoding, extendSpecialTokens);
}
tokenizersCache[encoding] = tokenizer;
return tokenizer;
}
getTokenCount(text) {
return this.gptEncoder.encode(text, 'all').length;
}
/**
* Algorithm adapted from "6. Counting tokens for chat API calls" of
* https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
*
* An additional 2 tokens need to be added for metadata after all messages have been counted.
*
* @param {*} message
*/
getTokenCountForMessage(message) {
// Map each property of the message to the number of tokens it contains
const propertyTokenCounts = Object.entries(message).map(([key, value]) => {
// Count the number of tokens in the property value
const numTokens = this.getTokenCount(value);
// Subtract 1 token if the property key is 'name'
const adjustment = key === 'name' ? 1 : 0;
return numTokens - adjustment;
});
// Sum the number of tokens in all properties and add 4 for metadata
return propertyTokenCounts.reduce((a, b) => a + b, 4);
}
/**
* Iterate through messages, building an array based on the parentMessageId.
* Each message has an id and a parentMessageId. The parentMessageId is the id of the message that this message is a reply to.
* @param messages
* @param parentMessageId
* @returns {*[]} An array containing the messages in the order they should be displayed, starting with the root message.
*/
static getMessagesForConversation(messages, parentMessageId) {
const orderedMessages = [];
let currentMessageId = parentMessageId;
while (currentMessageId) {
// eslint-disable-next-line no-loop-func
const message = messages.find(m => m.messageId === currentMessageId);
if (!message) {
break;
}
orderedMessages.unshift(message);
currentMessageId = message.parentMessageId;
}
if (orderedMessages.length === 0) {
return [];
}
return orderedMessages.map(msg => ({
isCreatedByUser: msg.isCreatedByUser,
content: msg.text
}));
}
}
module.exports = GoogleAgent;
module.exports = GoogleClient;

View File

@@ -0,0 +1,369 @@
const BaseClient = require('./BaseClient');
const ChatGPTClient = require('./ChatGPTClient');
const {
encoding_for_model: encodingForModel,
get_encoding: getEncoding,
} = require('@dqbd/tiktoken');
const { maxTokensMap, genAzureChatCompletion } = require('../../utils');
// Cache to store Tiktoken instances
const tokenizersCache = {};
// Counter for keeping track of the number of tokenizer calls
let tokenizerCallsCount = 0;
class OpenAIClient extends BaseClient {
constructor(apiKey, options = {}) {
super(apiKey, options);
this.ChatGPTClient = new ChatGPTClient();
this.buildPrompt = this.ChatGPTClient.buildPrompt.bind(this);
this.getCompletion = this.ChatGPTClient.getCompletion.bind(this);
this.sender = options.sender ?? 'ChatGPT';
this.contextStrategy = options.contextStrategy
? options.contextStrategy.toLowerCase()
: 'discard';
this.shouldRefineContext = this.contextStrategy === 'refine';
this.azure = options.azure || false;
if (this.azure) {
this.azureEndpoint = genAzureChatCompletion(this.azure);
}
this.setOptions(options);
}
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
this.options.modelOptions = {
...this.options.modelOptions,
...options.modelOptions,
};
delete options.modelOptions;
this.options = {
...this.options,
...options,
};
} else {
this.options = options;
}
if (this.options.openaiApiKey) {
this.apiKey = this.options.openaiApiKey;
}
const modelOptions = this.options.modelOptions || {};
if (!this.modelOptions) {
this.modelOptions = {
...modelOptions,
model: modelOptions.model || 'gpt-3.5-turbo',
temperature:
typeof modelOptions.temperature === 'undefined' ? 0.8 : modelOptions.temperature,
top_p: typeof modelOptions.top_p === 'undefined' ? 1 : modelOptions.top_p,
presence_penalty:
typeof modelOptions.presence_penalty === 'undefined' ? 1 : modelOptions.presence_penalty,
stop: modelOptions.stop,
};
}
this.isChatCompletion =
this.options.reverseProxyUrl ||
this.options.localAI ||
this.modelOptions.model.startsWith('gpt-');
this.isChatGptModel = this.isChatCompletion;
if (this.modelOptions.model === 'text-davinci-003') {
this.isChatCompletion = false;
this.isChatGptModel = false;
}
const { isChatGptModel } = this;
this.isUnofficialChatGptModel =
this.modelOptions.model.startsWith('text-chat') ||
this.modelOptions.model.startsWith('text-davinci-002-render');
this.maxContextTokens = maxTokensMap[this.modelOptions.model] ?? 4095; // 1 less than maximum
this.maxResponseTokens = this.modelOptions.max_tokens || 1024;
this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) {
throw new Error(
`maxPromptTokens + max_tokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${
this.maxPromptTokens + this.maxResponseTokens
}) must be less than or equal to maxContextTokens (${this.maxContextTokens})`,
);
}
this.userLabel = this.options.userLabel || 'User';
this.chatGptLabel = this.options.chatGptLabel || 'Assistant';
this.setupTokens();
if (!this.modelOptions.stop) {
const stopTokens = [this.startToken];
if (this.endToken && this.endToken !== this.startToken) {
stopTokens.push(this.endToken);
}
stopTokens.push(`\n${this.userLabel}:`);
stopTokens.push('<|diff_marker|>');
this.modelOptions.stop = stopTokens;
}
if (this.options.reverseProxyUrl) {
this.completionsUrl = this.options.reverseProxyUrl;
} else if (isChatGptModel) {
this.completionsUrl = 'https://api.openai.com/v1/chat/completions';
} else {
this.completionsUrl = 'https://api.openai.com/v1/completions';
}
if (this.azureEndpoint) {
this.completionsUrl = this.azureEndpoint;
}
if (this.azureEndpoint && this.options.debug) {
console.debug(`Using Azure endpoint: ${this.azureEndpoint}`, this.azure);
}
return this;
}
setupTokens() {
if (this.isChatCompletion) {
this.startToken = '||>';
this.endToken = '';
} else if (this.isUnofficialChatGptModel) {
this.startToken = '<|im_start|>';
this.endToken = '<|im_end|>';
} else {
this.startToken = '||>';
this.endToken = '';
}
}
// Selects an appropriate tokenizer based on the current configuration of the client instance.
// It takes into account factors such as whether it's a chat completion, an unofficial chat GPT model, etc.
selectTokenizer() {
let tokenizer;
this.encoding = 'text-davinci-003';
if (this.isChatCompletion) {
this.encoding = 'cl100k_base';
tokenizer = this.constructor.getTokenizer(this.encoding);
} else if (this.isUnofficialChatGptModel) {
const extendSpecialTokens = {
'<|im_start|>': 100264,
'<|im_end|>': 100265,
};
tokenizer = this.constructor.getTokenizer(this.encoding, true, extendSpecialTokens);
} else {
try {
this.encoding = this.modelOptions.model;
tokenizer = this.constructor.getTokenizer(this.modelOptions.model, true);
} catch {
tokenizer = this.constructor.getTokenizer(this.encoding, true);
}
}
return tokenizer;
}
// Retrieves a tokenizer either from the cache or creates a new one if one doesn't exist in the cache.
// If a tokenizer is being created, it's also added to the cache.
static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) {
let tokenizer;
if (tokenizersCache[encoding]) {
tokenizer = tokenizersCache[encoding];
} else {
if (isModelName) {
tokenizer = encodingForModel(encoding, extendSpecialTokens);
} else {
tokenizer = getEncoding(encoding, extendSpecialTokens);
}
tokenizersCache[encoding] = tokenizer;
}
return tokenizer;
}
// Frees all encoders in the cache and resets the count.
static freeAndResetAllEncoders() {
try {
Object.keys(tokenizersCache).forEach((key) => {
if (tokenizersCache[key]) {
tokenizersCache[key].free();
delete tokenizersCache[key];
}
});
// Reset count
tokenizerCallsCount = 1;
} catch (error) {
console.log('Free and reset encoders error');
console.error(error);
}
}
// Checks if the cache of tokenizers has reached a certain size. If it has, it frees and resets all tokenizers.
resetTokenizersIfNecessary() {
if (tokenizerCallsCount >= 25) {
if (this.options.debug) {
console.debug('freeAndResetAllEncoders: reached 25 encodings, resetting...');
}
this.constructor.freeAndResetAllEncoders();
}
tokenizerCallsCount++;
}
// Returns the token count of a given text. It also checks and resets the tokenizers if necessary.
getTokenCount(text) {
this.resetTokenizersIfNecessary();
try {
const tokenizer = this.selectTokenizer();
return tokenizer.encode(text, 'all').length;
} catch (error) {
this.constructor.freeAndResetAllEncoders();
const tokenizer = this.selectTokenizer();
return tokenizer.encode(text, 'all').length;
}
}
getSaveOptions() {
return {
chatGptLabel: this.options.chatGptLabel,
promptPrefix: this.options.promptPrefix,
...this.modelOptions,
};
}
getBuildMessagesOptions(opts) {
return {
isChatCompletion: this.isChatCompletion,
promptPrefix: opts.promptPrefix,
abortController: opts.abortController,
};
}
async buildMessages(
messages,
parentMessageId,
{ isChatCompletion = false, promptPrefix = null },
) {
if (!isChatCompletion) {
return await this.buildPrompt(messages, parentMessageId, {
isChatGptModel: isChatCompletion,
promptPrefix,
});
}
let payload;
let instructions;
let tokenCountMap;
let promptTokens;
let orderedMessages = this.constructor.getMessagesForConversation(messages, parentMessageId);
promptPrefix = (promptPrefix || this.options.promptPrefix || '').trim();
if (promptPrefix) {
promptPrefix = `Instructions:\n${promptPrefix}`;
instructions = {
role: 'system',
name: 'instructions',
content: promptPrefix,
};
if (this.contextStrategy) {
instructions.tokenCount = this.getTokenCountForMessage(instructions);
}
}
const formattedMessages = orderedMessages.map((message) => {
let { role: _role, sender, text } = message;
const role = _role ?? sender;
const content = text ?? '';
const formattedMessage = {
role: role?.toLowerCase() === 'user' ? 'user' : 'assistant',
content,
};
if (this.options?.name && formattedMessage.role === 'user') {
formattedMessage.name = this.options.name;
}
if (this.contextStrategy) {
formattedMessage.tokenCount =
message.tokenCount ?? this.getTokenCountForMessage(formattedMessage);
}
return formattedMessage;
});
// TODO: need to handle interleaving instructions better
if (this.contextStrategy) {
({ payload, tokenCountMap, promptTokens, messages } = await this.handleContextStrategy({
instructions,
orderedMessages,
formattedMessages,
}));
}
const result = {
prompt: payload,
promptTokens,
messages,
};
if (tokenCountMap) {
tokenCountMap.instructions = instructions?.tokenCount;
result.tokenCountMap = tokenCountMap;
}
return result;
}
async sendCompletion(payload, opts = {}) {
let reply = '';
let result = null;
if (typeof opts.onProgress === 'function') {
await this.getCompletion(
payload,
(progressMessage) => {
if (progressMessage === '[DONE]') {
return;
}
const token = this.isChatCompletion
? progressMessage.choices?.[0]?.delta?.content
: progressMessage.choices?.[0]?.text;
// first event's delta content is always undefined
if (!token) {
return;
}
if (this.options.debug) {
// console.debug(token);
}
if (token === this.endToken) {
return;
}
opts.onProgress(token);
reply += token;
},
opts.abortController || new AbortController(),
);
} else {
result = await this.getCompletion(
payload,
null,
opts.abortController || new AbortController(),
);
if (this.options.debug) {
console.debug(JSON.stringify(result));
}
if (this.isChatCompletion) {
reply = result.choices[0].message.content;
} else {
reply = result.choices[0].text.replace(this.endToken, '');
}
}
return reply.trim();
}
getTokenCountForResponse(response) {
return this.getTokenCountForMessage({
role: 'assistant',
content: response.text,
});
}
}
module.exports = OpenAIClient;

View File

@@ -0,0 +1,569 @@
const OpenAIClient = require('./OpenAIClient');
const { ChatOpenAI } = require('langchain/chat_models/openai');
const { CallbackManager } = require('langchain/callbacks');
const { initializeCustomAgent, initializeFunctionsAgent } = require('./agents/');
const { findMessageContent } = require('../../utils');
const { loadTools } = require('./tools/util');
const { SelfReflectionTool } = require('./tools/');
const { HumanChatMessage, AIChatMessage } = require('langchain/schema');
const { instructions, imageInstructions, errorInstructions } = require('./prompts/instructions');
class PluginsClient extends OpenAIClient {
constructor(apiKey, options = {}) {
super(apiKey, options);
this.sender = options.sender ?? 'Assistant';
this.tools = [];
this.actions = [];
this.openAIApiKey = apiKey;
this.setOptions(options);
this.executor = null;
}
getActions(input = null) {
let output = 'Internal thoughts & actions taken:\n"';
let actions = input || this.actions;
if (actions[0]?.action && this.functionsAgent) {
actions = actions.map((step) => ({
log: `Action: ${step.action?.tool || ''}\nInput: ${
JSON.stringify(step.action?.toolInput) || ''
}\nObservation: ${step.observation}`,
}));
} else if (actions[0]?.action) {
actions = actions.map((step) => ({
log: `${step.action.log}\nObservation: ${step.observation}`,
}));
}
actions.forEach((actionObj, index) => {
output += `${actionObj.log}`;
if (index < actions.length - 1) {
output += '\n';
}
});
return output + '"';
}
buildErrorInput(message, errorMessage) {
const log = errorMessage.includes('Could not parse LLM output:')
? `A formatting error occurred with your response to the human's last message. You didn't follow the formatting instructions. Remember to ${instructions}`
: `You encountered an error while replying to the human's last message. Attempt to answer again or admit an answer cannot be given.\nError: ${errorMessage}`;
return `
${log}
${this.getActions()}
Human's last message: ${message}
`;
}
buildPromptPrefix(result, message) {
if ((result.output && result.output.includes('N/A')) || result.output === undefined) {
return null;
}
if (
result?.intermediateSteps?.length === 1 &&
result?.intermediateSteps[0]?.action?.toolInput === 'N/A'
) {
return null;
}
const internalActions =
result?.intermediateSteps?.length > 0
? this.getActions(result.intermediateSteps)
: 'Internal Actions Taken: None';
const toolBasedInstructions = internalActions.toLowerCase().includes('image')
? imageInstructions
: '';
const errorMessage = result.errorMessage ? `${errorInstructions} ${result.errorMessage}\n` : '';
const preliminaryAnswer =
result.output?.length > 0 ? `Preliminary Answer: "${result.output.trim()}"` : '';
const prefix = preliminaryAnswer
? 'review and improve the answer you generated using plugins in response to the User Message below. The user hasn\'t seen your answer or thoughts yet.'
: 'respond to the User Message below based on your preliminary thoughts & actions.';
return `As a helpful AI Assistant, ${prefix}${errorMessage}\n${internalActions}
${preliminaryAnswer}
Reply conversationally to the User based on your ${
preliminaryAnswer ? 'preliminary answer, ' : ''
}internal actions, thoughts, and observations, making improvements wherever possible, but do not modify URLs.
${
preliminaryAnswer
? ''
: '\nIf there is an incomplete thought or action, you are expected to complete it in your response now.\n'
}You must cite sources if you are using any web links. ${toolBasedInstructions}
Only respond with your conversational reply to the following User Message:
"${message}"`;
}
setOptions(options) {
this.agentOptions = options.agentOptions;
this.functionsAgent = this.agentOptions?.agent === 'functions';
this.agentIsGpt3 = this.agentOptions?.model.startsWith('gpt-3');
if (this.functionsAgent && this.agentOptions.model) {
this.agentOptions.model = this.getFunctionModelName(this.agentOptions.model);
}
super.setOptions(options);
this.isGpt3 = this.modelOptions.model.startsWith('gpt-3');
if (this.options.reverseProxyUrl) {
this.langchainProxy = this.options.reverseProxyUrl.match(/.*v1/)[0];
}
}
getSaveOptions() {
return {
chatGptLabel: this.options.chatGptLabel,
promptPrefix: this.options.promptPrefix,
...this.modelOptions,
agentOptions: this.agentOptions,
};
}
saveLatestAction(action) {
this.actions.push(action);
}
getFunctionModelName(input) {
if (input.startsWith('gpt-3.5-turbo')) {
return 'gpt-3.5-turbo';
} else if (input.startsWith('gpt-4')) {
return 'gpt-4';
} else {
return 'gpt-3.5-turbo';
}
}
getBuildMessagesOptions(opts) {
return {
isChatCompletion: true,
promptPrefix: opts.promptPrefix,
abortController: opts.abortController,
};
}
createLLM(modelOptions, configOptions) {
let credentials = { openAIApiKey: this.openAIApiKey };
let configuration = {
apiKey: this.openAIApiKey,
};
if (this.azure) {
credentials = {};
configuration = {};
}
if (this.options.debug) {
console.debug('createLLM: configOptions');
console.debug(configOptions);
}
return new ChatOpenAI({ credentials, configuration, ...modelOptions }, configOptions);
}
async initialize({ user, message, onAgentAction, onChainEnd, signal }) {
const modelOptions = {
modelName: this.agentOptions.model,
temperature: this.agentOptions.temperature,
};
const configOptions = {};
if (this.langchainProxy) {
configOptions.basePath = this.langchainProxy;
}
const model = this.createLLM(modelOptions, configOptions);
if (this.options.debug) {
console.debug(
`<-----Agent Model: ${model.modelName} | Temp: ${model.temperature} | Functions: ${this.functionsAgent}----->`,
);
}
this.availableTools = await loadTools({
user,
model,
tools: this.options.tools,
functions: this.functionsAgent,
options: {
openAIApiKey: this.openAIApiKey,
debug: this.options?.debug,
message,
},
});
// load tools
for (const tool of this.options.tools) {
const validTool = this.availableTools[tool];
if (tool === 'plugins') {
const plugins = await validTool();
this.tools = [...this.tools, ...plugins];
} else if (validTool) {
this.tools.push(await validTool());
}
}
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));
}
if (this.tools.length > 0 && !this.functionsAgent) {
this.tools.push(new SelfReflectionTool({ message, isGpt3: false }));
} else if (this.tools.length === 0) {
return;
}
const handleAction = (action, callback = null) => {
this.saveLatestAction(action);
if (this.options.debug) {
console.debug('Latest Agent Action ', this.actions[this.actions.length - 1]);
}
if (typeof callback === 'function') {
callback(action);
}
};
// Map Messages to Langchain format
const pastMessages = this.currentMessages
.slice(0, -1)
.map((msg) =>
msg?.isCreatedByUser || msg?.role?.toLowerCase() === 'user'
? new HumanChatMessage(msg.text)
: new AIChatMessage(msg.text),
);
// initialize agent
const initializer = this.functionsAgent ? initializeFunctionsAgent : initializeCustomAgent;
this.executor = await initializer({
model,
signal,
pastMessages,
tools: this.tools,
currentDateString: this.currentDateString,
verbose: this.options.debug,
returnIntermediateSteps: true,
callbackManager: CallbackManager.fromHandlers({
async handleAgentAction(action) {
handleAction(action, onAgentAction);
},
async handleChainEnd(action) {
if (typeof onChainEnd === 'function') {
onChainEnd(action);
}
},
}),
});
if (this.options.debug) {
console.debug('Loaded agent.');
}
onAgentAction(
{
tool: 'self-reflection',
toolInput: `Processing the User's message:\n"${message}"`,
log: '',
},
true,
);
}
async executorCall(message, signal) {
let errorMessage = '';
const maxAttempts = 1;
for (let attempts = 1; attempts <= maxAttempts; attempts++) {
const errorInput = this.buildErrorInput(message, errorMessage);
const input = attempts > 1 ? errorInput : message;
if (this.options.debug) {
console.debug(`Attempt ${attempts} of ${maxAttempts}`);
}
if (this.options.debug && errorMessage.length > 0) {
console.debug('Caught error, input:', input);
}
try {
this.result = await this.executor.call({ input, signal });
break; // Exit the loop if the function call is successful
} catch (err) {
console.error(err);
errorMessage = err.message;
const content = findMessageContent(message);
if (content) {
errorMessage = content;
break;
}
if (attempts === maxAttempts) {
this.result.output = `Encountered an error while attempting to respond. Error: ${err.message}`;
this.result.intermediateSteps = this.actions;
this.result.errorMessage = errorMessage;
break;
}
}
}
}
addImages(intermediateSteps, responseMessage) {
if (!intermediateSteps || !responseMessage) {
return;
}
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];
// 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');
}
}
});
}
async handleResponseMessage(responseMessage, saveOptions, user) {
responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage);
responseMessage.completionTokens = responseMessage.tokenCount;
await this.saveMessageToDatabase(responseMessage, saveOptions, user);
delete responseMessage.tokenCount;
return { ...responseMessage, ...this.result };
}
async sendMessage(message, opts = {}) {
const completionMode = this.options.tools.length === 0;
if (completionMode) {
this.setOptions(opts);
return super.sendMessage(message, opts);
}
console.log('Plugins sendMessage', message, opts);
const {
user,
conversationId,
responseMessageId,
saveOptions,
userMessage,
onAgentAction,
onChainEnd,
} = await this.handleStartMethods(message, opts);
this.currentMessages.push(userMessage);
let {
prompt: payload,
tokenCountMap,
promptTokens,
messages,
} = await this.buildMessages(
this.currentMessages,
userMessage.messageId,
this.getBuildMessagesOptions({
promptPrefix: null,
abortController: this.abortController,
}),
);
if (tokenCountMap) {
console.dir(tokenCountMap, { depth: null });
if (tokenCountMap[userMessage.messageId]) {
userMessage.tokenCount = tokenCountMap[userMessage.messageId];
console.log('userMessage.tokenCount', userMessage.tokenCount);
}
payload = payload.map((message) => {
const messageWithoutTokenCount = message;
delete messageWithoutTokenCount.tokenCount;
return messageWithoutTokenCount;
});
this.handleTokenCountMap(tokenCountMap);
}
this.result = {};
if (messages) {
this.currentMessages = messages;
}
await this.saveMessageToDatabase(userMessage, saveOptions, user);
const responseMessage = {
messageId: responseMessageId,
conversationId,
parentMessageId: userMessage.messageId,
isCreatedByUser: false,
model: this.modelOptions.model,
sender: this.sender,
promptTokens,
};
await this.initialize({
user,
message,
onAgentAction,
onChainEnd,
signal: this.abortController.signal,
});
await this.executorCall(message, this.abortController.signal);
// If message was aborted mid-generation
if (this.result?.errorMessage?.length > 0 && this.result?.errorMessage?.includes('cancel')) {
responseMessage.text = 'Cancelled.';
return await this.handleResponseMessage(responseMessage, saveOptions, user);
}
if (this.agentOptions.skipCompletion && this.result.output) {
responseMessage.text = this.result.output;
this.addImages(this.result.intermediateSteps, responseMessage);
await this.generateTextStream(this.result.output, opts.onProgress, { delay: 8 });
return await this.handleResponseMessage(responseMessage, saveOptions, user);
}
if (this.options.debug) {
console.debug('Plugins completion phase: this.result');
console.debug(this.result);
}
const promptPrefix = this.buildPromptPrefix(this.result, message);
if (this.options.debug) {
console.debug('Plugins: promptPrefix');
console.debug(promptPrefix);
}
payload = await this.buildCompletionPrompt({
messages: this.currentMessages,
promptPrefix,
});
if (this.options.debug) {
console.debug('buildCompletionPrompt Payload');
console.debug(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);
}
const orderedMessages = messages;
let promptPrefix = _promptPrefix.trim();
// 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 = `${this.startToken}Instructions:\n${promptPrefix}`;
const promptSuffix = `${this.startToken}${this.chatGptLabel ?? 'Assistant'}:\n`;
const instructionsPayload = {
role: 'system',
name: 'instructions',
content: promptPrefix,
};
const messagePayload = {
role: 'system',
content: promptSuffix,
};
if (this.isGpt3) {
instructionsPayload.role = 'user';
messagePayload.role = 'user';
instructionsPayload.content += `\n${promptSuffix}`;
}
// testing if this works with browser endpoint
if (!this.isGpt3 && this.options.reverseProxyUrl) {
instructionsPayload.role = 'user';
}
let currentTokenCount =
this.getTokenCountForMessage(instructionsPayload) +
this.getTokenCountForMessage(messagePayload);
let promptBody = '';
const maxTokenCount = this.maxPromptTokens;
// 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.
const buildPromptBody = async () => {
if (currentTokenCount < maxTokenCount && orderedMessages.length > 0) {
const message = orderedMessages.pop();
const isCreatedByUser = message.isCreatedByUser || message.role?.toLowerCase() === 'user';
const roleLabel = isCreatedByUser ? this.userLabel : this.chatGptLabel;
let messageString = `${this.startToken}${roleLabel}:\n${message.text}${this.endToken}\n`;
let newPromptBody = `${messageString}${promptBody}`;
const tokenCountForMessage = this.getTokenCount(messageString);
const newTokenCount = currentTokenCount + tokenCountForMessage;
if (newTokenCount > maxTokenCount) {
if (promptBody) {
// This message would put us over the token limit, so don't add it.
return false;
}
// 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.`,
);
}
promptBody = newPromptBody;
currentTokenCount = newTokenCount;
// wait for next tick to avoid blocking the event loop
await new Promise((resolve) => setTimeout(resolve, 0));
return buildPromptBody();
}
return true;
};
await buildPromptBody();
const prompt = promptBody;
messagePayload.content = prompt;
// Add 2 tokens for metadata after all messages have been counted.
currentTokenCount += 2;
if (this.isGpt3 && messagePayload.content.length > 0) {
const context = 'Chat History:\n';
messagePayload.content = `${context}${prompt}`;
currentTokenCount += this.getTokenCount(context);
}
// Use up to `this.maxContextTokens` tokens (prompt + response), but try to leave `this.maxTokens` tokens for the response.
this.modelOptions.max_tokens = Math.min(
this.maxContextTokens - currentTokenCount,
this.maxResponseTokens,
);
if (this.isGpt3) {
messagePayload.content += promptSuffix;
return [instructionsPayload, messagePayload];
}
const result = [messagePayload, instructionsPayload];
if (this.functionsAgent && !this.isGpt3) {
result[1].content = `${result[1].content}\n${this.startToken}${this.chatGptLabel}:\nSure thing! Here is the output you requested:\n`;
}
return result.filter((message) => message.content.length > 0);
}
}
module.exports = PluginsClient;

View File

@@ -16,10 +16,7 @@ class TextStream extends Readable {
if (this.currentIndex < this.text.length) {
setTimeout(() => {
const remainingChars = this.text.length - this.currentIndex;
const chunkSize = Math.min(
this.randomInt(minChunkSize, maxChunkSize + 1),
remainingChars
);
const chunkSize = Math.min(this.randomInt(minChunkSize, maxChunkSize + 1), remainingChars);
const chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize);
this.push(chunk);

View File

@@ -0,0 +1,50 @@
const { ZeroShotAgent } = require('langchain/agents');
const { PromptTemplate, renderTemplate } = require('langchain/prompts');
const { gpt3, gpt4 } = require('./instructions');
class CustomAgent extends ZeroShotAgent {
constructor(input) {
super(input);
}
_stop() {
return ['\nObservation:', '\nObservation 1:'];
}
static createPrompt(tools, opts = {}) {
const { currentDateString, model } = opts;
const inputVariables = ['input', 'chat_history', 'agent_scratchpad'];
let prefix, instructions, suffix;
if (model.startsWith('gpt-3')) {
prefix = gpt3.prefix;
instructions = gpt3.instructions;
suffix = gpt3.suffix;
} else if (model.startsWith('gpt-4')) {
prefix = gpt4.prefix;
instructions = gpt4.instructions;
suffix = gpt4.suffix;
}
const toolStrings = tools
.filter((tool) => tool.name !== 'self-reflection')
.map((tool) => `${tool.name}: ${tool.description}`)
.join('\n');
const toolNames = tools.map((tool) => tool.name);
const formatInstructions = (0, renderTemplate)(instructions, 'f-string', {
tool_names: toolNames,
});
const template = [
`Date: ${currentDateString}\n${prefix}`,
toolStrings,
formatInstructions,
suffix,
].join('\n\n');
return new PromptTemplate({
template,
inputVariables,
});
}
}
module.exports = CustomAgent;

View File

@@ -0,0 +1,54 @@
const CustomAgent = require('./CustomAgent');
const { CustomOutputParser } = require('./outputParser');
const { AgentExecutor } = require('langchain/agents');
const { LLMChain } = require('langchain/chains');
const { BufferMemory, ChatMessageHistory } = require('langchain/memory');
const {
ChatPromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
} = require('langchain/prompts');
const initializeCustomAgent = async ({
tools,
model,
pastMessages,
currentDateString,
...rest
}) => {
let prompt = CustomAgent.createPrompt(tools, { currentDateString, model: model.modelName });
const chatPrompt = ChatPromptTemplate.fromPromptMessages([
new SystemMessagePromptTemplate(prompt),
HumanMessagePromptTemplate.fromTemplate(`{chat_history}
Query: {input}
{agent_scratchpad}`),
]);
const outputParser = new CustomOutputParser({ tools });
const memory = new BufferMemory({
chatHistory: new ChatMessageHistory(pastMessages),
// returnMessages: true, // commenting this out retains memory
memoryKey: 'chat_history',
humanPrefix: 'User',
aiPrefix: 'Assistant',
inputKey: 'input',
outputKey: 'output',
});
const llmChain = new LLMChain({
prompt: chatPrompt,
llm: model,
});
const agent = new CustomAgent({
llmChain,
outputParser,
allowedTools: tools.map((tool) => tool.name),
});
return AgentExecutor.fromAgentAndTools({ agent, tools, memory, ...rest });
};
module.exports = initializeCustomAgent;

View File

@@ -0,0 +1,203 @@
/*
module.exports = `You are ChatGPT, a Large Language model with useful tools.
Talk to the human and provide meaningful answers when questions are asked.
Use the tools when you need them, but use your own knowledge if you are confident of the answer. Keep answers short and concise.
A tool is not usually needed for creative requests, so do your best to answer them without tools.
Avoid repeating identical answers if it appears before. Only fulfill the human's requests, do not create extra steps beyond what the human has asked for.
Your input for 'Action' should be the name of tool used only.
Be honest. If you can't answer something, or a tool is not appropriate, say you don't know or answer to the best of your ability.
Attempt to fulfill the human's requests in as few actions as possible`;
*/
// module.exports = `You are ChatGPT, a highly knowledgeable and versatile large language model.
// Engage with the Human conversationally, providing concise and meaningful answers to questions. Utilize built-in tools when necessary, except for creative requests, where relying on your own knowledge is preferred. Aim for variety and avoid repetitive answers.
// For your 'Action' input, state the name of the tool used only, and honor user requests without adding extra steps. Always be honest; if you cannot provide an appropriate answer or tool, admit that or do your best.
// Strive to meet the user's needs efficiently with minimal actions.`;
// import {
// BasePromptTemplate,
// BaseStringPromptTemplate,
// SerializedBasePromptTemplate,
// renderTemplate,
// } from "langchain/prompts";
// prefix: `You are ChatGPT, a highly knowledgeable and versatile large language model.
// Your objective is to help users by understanding their intent and choosing the best action. Prioritize direct, specific responses. Use concise, varied answers and rely on your knowledge for creative tasks. Utilize tools when needed, and structure results for machine compatibility.
// prefix: `Objective: to comprehend human intentions based on user input and available tools. Goal: identify the best action to directly address the human's query. In your subsequent steps, you will utilize the chosen action. You may select multiple actions and list them in a meaningful order. Prioritize actions that directly relate to the user's query over general ones. Ensure that the generated thought is highly specific and explicit to best match the user's expectations. Construct the result in a manner that an online open-API would most likely expect. Provide concise and meaningful answers to human queries. Utilize tools when necessary. Relying on your own knowledge is preferred for creative requests. Aim for variety and avoid repetitive answers.
// # Available Actions & Tools:
// N/A: no suitable action, use your own knowledge.`,
// suffix: `Remember, all your responses MUST adhere to the described format and only respond if the format is followed. Output exactly with the requested format, avoiding any other text as this will be parsed by a machine. Following 'Action:', provide only one of the actions listed above. If a tool is not necessary, deduce this quickly and finish your response. Honor the human's requests without adding extra steps. Carry out tasks in the sequence written by the human. Always be honest; if you cannot provide an appropriate answer or tool, do your best with your own knowledge. Strive to meet the user's needs efficiently with minimal actions.`;
module.exports = {
'gpt3-v1': {
prefix: `Objective: Understand human intentions using user input and available tools. Goal: Identify the most suitable actions to directly address user queries.
When responding:
- Choose actions relevant to the user's query, using multiple actions in a logical order if needed.
- Prioritize direct and specific thoughts to meet user expectations.
- Format results in a way compatible with open-API expectations.
- Offer concise, meaningful answers to user queries.
- Use tools when necessary but rely on your own knowledge for creative requests.
- Strive for variety, avoiding repetitive responses.
# Available Actions & Tools:
N/A: No suitable action; use your own knowledge.`,
instructions: `Always adhere to the following format in your response to indicate actions taken:
Thought: Summarize your thought process.
Action: Select an action from [{tool_names}].
Action Input: Define the action's input.
Observation: Report the action's result.
Repeat steps 1-4 as needed, in order. When not using a tool, use N/A for Action, provide the result as Action Input, and include an Observation.
Upon reaching the final answer, use this format after completing all necessary actions:
Thought: Indicate that you've determined the final answer.
Final Answer: Present the answer to the user's query.`,
suffix: `Keep these guidelines in mind when crafting your response:
- Strictly adhere to the Action format for all responses, as they will be machine-parsed.
- If a tool is unnecessary, quickly move to the Thought/Final Answer format.
- Follow the logical sequence provided by the user without adding extra steps.
- Be honest; if you can't provide an appropriate answer using the given tools, use your own knowledge.
- Aim for efficiency and minimal actions to meet the user's needs effectively.`,
},
'gpt3-v2': {
prefix: `Objective: Understand the human's query with available actions & tools. Let's work this out in a step by step way to be sure we fulfill the query.
When responding:
- Choose actions relevant to the user's query, using multiple actions in a logical order if needed.
- Prioritize direct and specific thoughts to meet user expectations.
- Format results in a way compatible with open-API expectations.
- Offer concise, meaningful answers to user queries.
- Use tools when necessary but rely on your own knowledge for creative requests.
- Strive for variety, avoiding repetitive responses.
# Available Actions & Tools:
N/A: No suitable action; use your own knowledge.`,
instructions: `I want you to respond with this format and this format only, without comments or explanations, to indicate actions taken:
\`\`\`
Thought: Summarize your thought process.
Action: Select an action from [{tool_names}].
Action Input: Define the action's input.
Observation: Report the action's result.
\`\`\`
Repeat the format for each action as needed. When not using a tool, use N/A for Action, provide the result as Action Input, and include an Observation.
Upon reaching the final answer, use this format after completing all necessary actions:
\`\`\`
Thought: Indicate that you've determined the final answer.
Final Answer: A conversational reply to the user's query as if you were answering them directly.
\`\`\``,
suffix: `Keep these guidelines in mind when crafting your response:
- Strictly adhere to the Action format for all responses, as they will be machine-parsed.
- If a tool is unnecessary, quickly move to the Thought/Final Answer format.
- Follow the logical sequence provided by the user without adding extra steps.
- Be honest; if you can't provide an appropriate answer using the given tools, use your own knowledge.
- Aim for efficiency and minimal actions to meet the user's needs effectively.`,
},
gpt3: {
prefix: `Objective: Understand the human's query with available actions & tools. Let's work this out in a step by step way to be sure we fulfill the query.
Use available actions and tools judiciously.
# Available Actions & Tools:
N/A: No suitable action; use your own knowledge.`,
instructions: `I want you to respond with this format and this format only, without comments or explanations, to indicate actions taken:
\`\`\`
Thought: Your thought process.
Action: Action from [{tool_names}].
Action Input: Action's input.
Observation: Action's result.
\`\`\`
For each action, repeat the format. If no tool is used, use N/A for Action, and provide the result as Action Input.
Finally, complete with:
\`\`\`
Thought: Convey final answer determination.
Final Answer: Reply to user's query conversationally.
\`\`\``,
suffix: `Remember:
- Adhere to the Action format strictly for parsing.
- Transition quickly to Thought/Final Answer format when a tool isn't needed.
- Follow user's logic without superfluous steps.
- If unable to use tools for a fitting answer, use your knowledge.
- Strive for efficient, minimal actions.`,
},
'gpt4-v1': {
prefix: `Objective: Understand the human's query with available actions & tools. Let's work this out in a step by step way to be sure we fulfill the query.
When responding:
- Choose actions relevant to the query, using multiple actions in a step by step way.
- Prioritize direct and specific thoughts to meet user expectations.
- Be precise and offer meaningful answers to user queries.
- Use tools when necessary but rely on your own knowledge for creative requests.
- Strive for variety, avoiding repetitive responses.
# Available Actions & Tools:
N/A: No suitable action; use your own knowledge.`,
instructions: `I want you to respond with this format and this format only, without comments or explanations, to indicate actions taken:
\`\`\`
Thought: Summarize your thought process.
Action: Select an action from [{tool_names}].
Action Input: Define the action's input.
Observation: Report the action's result.
\`\`\`
Repeat the format for each action as needed. When not using a tool, use N/A for Action, provide the result as Action Input, and include an Observation.
Upon reaching the final answer, use this format after completing all necessary actions:
\`\`\`
Thought: Indicate that you've determined the final answer.
Final Answer: A conversational reply to the user's query as if you were answering them directly.
\`\`\``,
suffix: `Keep these guidelines in mind when crafting your final response:
- Strictly adhere to the Action format for all responses.
- If a tool is unnecessary, quickly move to the Thought/Final Answer format, only if no further actions are possible or necessary.
- Follow the logical sequence provided by the user without adding extra steps.
- Be honest: if you can't provide an appropriate answer using the given tools, use your own knowledge.
- Aim for efficiency and minimal actions to meet the user's needs effectively.`,
},
gpt4: {
prefix: `Objective: Understand the human's query with available actions & tools. Let's work this out in a step by step way to be sure we fulfill the query.
Use available actions and tools judiciously.
# Available Actions & Tools:
N/A: No suitable action; use your own knowledge.`,
instructions: `Respond in this specific format without extraneous comments:
\`\`\`
Thought: Your thought process.
Action: Action from [{tool_names}].
Action Input: Action's input.
Observation: Action's result.
\`\`\`
For each action, repeat the format. If no tool is used, use N/A for Action, and provide the result as Action Input.
Finally, complete with:
\`\`\`
Thought: Indicate that you've determined the final answer.
Final Answer: A conversational reply to the user's query, including your full answer.
\`\`\``,
suffix: `Remember:
- Adhere to the Action format strictly for parsing.
- Transition quickly to Thought/Final Answer format when a tool isn't needed.
- Follow user's logic without superfluous steps.
- If unable to use tools for a fitting answer, use your knowledge.
- Strive for efficient, minimal actions.`,
},
};

View File

@@ -0,0 +1,218 @@
const { ZeroShotAgentOutputParser } = require('langchain/agents');
class CustomOutputParser extends ZeroShotAgentOutputParser {
constructor(fields) {
super(fields);
this.tools = fields.tools;
this.longestToolName = '';
for (const tool of this.tools) {
if (tool.name.length > this.longestToolName.length) {
this.longestToolName = tool.name;
}
}
this.finishToolNameRegex = /(?:the\s+)?final\s+answer:\s*/i;
this.actionValues =
/(?:Action(?: [1-9])?:) ([\s\S]*?)(?:\n(?:Action Input(?: [1-9])?:) ([\s\S]*?))?$/i;
this.actionInputRegex = /(?:Action Input(?: *\d*):) ?([\s\S]*?)$/i;
this.thoughtRegex = /(?:Thought(?: *\d*):) ?([\s\S]*?)$/i;
}
getValidTool(text) {
let result = false;
for (const tool of this.tools) {
const { name } = tool;
const toolIndex = text.indexOf(name);
if (toolIndex !== -1) {
result = name;
break;
}
}
return result;
}
checkIfValidTool(text) {
let isValidTool = false;
for (const tool of this.tools) {
const { name } = tool;
if (text === name) {
isValidTool = true;
break;
}
}
return isValidTool;
}
async parse(text) {
const finalMatch = text.match(this.finishToolNameRegex);
// if (text.includes(this.finishToolName)) {
// const parts = text.split(this.finishToolName);
// const output = parts[parts.length - 1].trim();
// return {
// returnValues: { output },
// log: text
// };
// }
if (finalMatch) {
const output = text.substring(finalMatch.index + finalMatch[0].length).trim();
return {
returnValues: { output },
log: text,
};
}
const match = this.actionValues.exec(text); // old v2
if (!match) {
console.log(
'\n\n<----------------------HIT NO MATCH PARSING ERROR---------------------->\n\n',
match,
);
const thoughts = text.replace(/[tT]hought:/, '').split('\n');
// return {
// tool: 'self-reflection',
// toolInput: thoughts[0],
// log: thoughts.slice(1).join('\n')
// };
return {
returnValues: { output: thoughts[0] },
log: thoughts.slice(1).join('\n'),
};
}
let selectedTool = match?.[1].trim().toLowerCase();
if (match && selectedTool === 'n/a') {
console.log(
'\n\n<----------------------HIT N/A PARSING ERROR---------------------->\n\n',
match,
);
return {
tool: 'self-reflection',
toolInput: match[2]?.trim().replace(/^"+|"+$/g, '') ?? '',
log: text,
};
}
let toolIsValid = this.checkIfValidTool(selectedTool);
if (match && !toolIsValid) {
console.log(
'\n\n<----------------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,
);
selectedTool = 'self-reflection';
}
if (match && !match[2]) {
console.log(
'\n\n<----------------------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
const actionInputMatch = this.actionInputRegex.exec(text);
const thoughtMatch = this.thoughtRegex.exec(text);
if (actionInputMatch) {
return {
tool: selectedTool,
toolInput: actionInputMatch[1].trim(),
log: text,
};
}
if (thoughtMatch && !actionInputMatch) {
return {
tool: selectedTool,
toolInput: thoughtMatch[1].trim(),
log: text,
};
}
}
if (match && selectedTool.length > this.longestToolName.length) {
console.log('\n\n<----------------------HIT LONG PARSING ERROR---------------------->\n\n');
let action, input, thought;
let firstIndex = Infinity;
for (const tool of this.tools) {
const { name } = tool;
const toolIndex = text.indexOf(name);
if (toolIndex !== -1 && toolIndex < firstIndex) {
firstIndex = toolIndex;
action = name;
}
}
// 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,
);
return {
tool: action,
toolInput: actionInputMatch[1].trim().replaceAll('"', ''),
log: text,
};
}
if (action) {
const actionEndIndex = text.indexOf('Action:', firstIndex + action.length);
const inputText = text
.slice(firstIndex + action.length, actionEndIndex !== -1 ? actionEndIndex : undefined)
.trim();
const inputLines = inputText.split('\n');
input = inputLines[0];
if (inputLines.length > 1) {
thought = inputLines.slice(1).join('\n');
}
const returnValues = {
tool: action,
toolInput: input,
log: thought || inputText,
};
const inputMatch = this.actionValues.exec(returnValues.log); //new
if (inputMatch) {
console.log('inputMatch');
console.dir(inputMatch, { depth: null });
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);
return {
tool: 'self-reflection',
toolInput: 'Hypothetical actions: \n"' + text + '"\n',
log: 'Thought: I need to look at my hypothetical actions and try one',
};
}
// if (action && input) {
// console.log('Action:', action);
// console.log('Input:', input);
// }
}
return {
tool: selectedTool,
toolInput: match[2]?.trim()?.replace(/^"+|"+$/g, '') ?? '',
log: text,
};
}
}
module.exports = { CustomOutputParser };

View File

@@ -0,0 +1,120 @@
const { Agent } = require('langchain/agents');
const { LLMChain } = require('langchain/chains');
const { FunctionChatMessage, AIChatMessage } = require('langchain/schema');
const {
ChatPromptTemplate,
MessagesPlaceholder,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
} = require('langchain/prompts');
const PREFIX = 'You are a helpful AI assistant.';
function parseOutput(message) {
if (message.additional_kwargs.function_call) {
const function_call = message.additional_kwargs.function_call;
return {
tool: function_call.name,
toolInput: function_call.arguments ? JSON.parse(function_call.arguments) : {},
log: message.text,
};
} else {
return { returnValues: { output: message.text }, log: message.text };
}
}
class FunctionsAgent extends Agent {
constructor(input) {
super({ ...input, outputParser: undefined });
this.tools = input.tools;
}
lc_namespace = ['langchain', 'agents', 'openai'];
_agentType() {
return 'openai-functions';
}
observationPrefix() {
return 'Observation: ';
}
llmPrefix() {
return 'Thought:';
}
_stop() {
return ['Observation:'];
}
static createPrompt(_tools, fields) {
const { prefix = PREFIX, currentDateString } = fields || {};
return ChatPromptTemplate.fromPromptMessages([
SystemMessagePromptTemplate.fromTemplate(`Date: ${currentDateString}\n${prefix}`),
new MessagesPlaceholder('chat_history'),
HumanMessagePromptTemplate.fromTemplate('Query: {input}'),
new MessagesPlaceholder('agent_scratchpad'),
]);
}
static fromLLMAndTools(llm, tools, args) {
FunctionsAgent.validateTools(tools);
const prompt = FunctionsAgent.createPrompt(tools, args);
const chain = new LLMChain({
prompt,
llm,
callbacks: args?.callbacks,
});
return new FunctionsAgent({
llmChain: chain,
allowedTools: tools.map((t) => t.name),
tools,
});
}
async constructScratchPad(steps) {
return steps.flatMap(({ action, observation }) => [
new AIChatMessage('', {
function_call: {
name: action.tool,
arguments: JSON.stringify(action.toolInput),
},
}),
new FunctionChatMessage(observation, action.tool),
]);
}
async plan(steps, inputs, callbackManager) {
// Add scratchpad and stop to inputs
const thoughts = await this.constructScratchPad(steps);
const newInputs = Object.assign({}, inputs, { agent_scratchpad: thoughts });
if (this._stop().length !== 0) {
newInputs.stop = this._stop();
}
// Split inputs between prompt and llm
const llm = this.llmChain.llm;
const valuesForPrompt = Object.assign({}, newInputs);
const valuesForLLM = {
tools: this.tools,
};
for (let i = 0; i < this.llmChain.llm.callKeys.length; i++) {
const key = this.llmChain.llm.callKeys[i];
if (key in inputs) {
valuesForLLM[key] = inputs[key];
delete valuesForPrompt[key];
}
}
const promptValue = await this.llmChain.prompt.formatPromptValue(valuesForPrompt);
const message = await llm.predictMessages(
promptValue.toChatMessages(),
valuesForLLM,
callbackManager,
);
console.log('message', message);
return parseOutput(message);
}
}
module.exports = FunctionsAgent;

View File

@@ -0,0 +1,28 @@
const { initializeAgentExecutorWithOptions } = require('langchain/agents');
const { BufferMemory, ChatMessageHistory } = require('langchain/memory');
const initializeFunctionsAgent = async ({
tools,
model,
pastMessages,
// currentDateString,
...rest
}) => {
const memory = new BufferMemory({
chatHistory: new ChatMessageHistory(pastMessages),
memoryKey: 'chat_history',
humanPrefix: 'User',
aiPrefix: 'Assistant',
inputKey: 'input',
outputKey: 'output',
returnMessages: true,
});
return await initializeAgentExecutorWithOptions(tools, model, {
agentType: 'openai-functions',
memory,
...rest,
});
};
module.exports = initializeFunctionsAgent;

View File

@@ -0,0 +1,7 @@
const initializeCustomAgent = require('./CustomAgent/initializeCustomAgent');
const initializeFunctionsAgent = require('./Functions/initializeFunctionsAgent');
module.exports = {
initializeCustomAgent,
initializeFunctionsAgent,
};

View File

@@ -1,67 +0,0 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const { genAzureEndpoint } = require('../../utils/genAzureEndpoints');
const askClient = async ({
text,
parentMessageId,
conversationId,
model,
chatGptLabel,
promptPrefix,
temperature,
top_p,
presence_penalty,
frequency_penalty,
onProgress,
abortController,
userId
}) => {
const { ChatGPTClient } = await import('@waylaidwanderer/chatgpt-api');
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
};
const azure = process.env.AZURE_OPENAI_API_KEY ? true : false;
const maxContextTokens = model === 'gpt-4' ? 8191 : model === 'gpt-4-32k' ? 32767 : 4095; // 1 less than maximum
const clientOptions = {
reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null,
azure,
maxContextTokens,
modelOptions: {
model,
temperature,
top_p,
presence_penalty,
frequency_penalty
},
chatGptLabel,
promptPrefix,
proxy: process.env.PROXY || null,
// debug: true
};
let apiKey = process.env.OPENAI_KEY;
if (azure) {
apiKey = process.env.AZURE_OPENAI_API_KEY;
clientOptions.reverseProxyUrl = genAzureEndpoint({
azureOpenAIApiInstanceName: process.env.AZURE_OPENAI_API_INSTANCE_NAME,
azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME,
azureOpenAIApiVersion: process.env.AZURE_OPENAI_API_VERSION
});
}
const client = new ChatGPTClient(apiKey, clientOptions, store);
const options = {
onProgress,
abortController,
...(parentMessageId && conversationId ? { parentMessageId, conversationId } : {})
};
const res = await client.sendMessage(text, { ...options, userId });
return res;
};
module.exports = { askClient };

17
api/app/clients/index.js Normal file
View File

@@ -0,0 +1,17 @@
const ChatGPTClient = require('./ChatGPTClient');
const OpenAIClient = require('./OpenAIClient');
const PluginsClient = require('./PluginsClient');
const GoogleClient = require('./GoogleClient');
const TextStream = require('./TextStream');
const AnthropicClient = require('./AnthropicClient');
const toolUtils = require('./tools/util');
module.exports = {
ChatGPTClient,
OpenAIClient,
PluginsClient,
GoogleClient,
TextStream,
AnthropicClient,
...toolUtils,
};

View File

@@ -0,0 +1,10 @@
module.exports = {
instructions:
'Remember, all your responses MUST be in the format described. Do not respond unless it\'s in the format described, using the structure of Action, Action Input, etc.',
errorInstructions:
'\nYou encountered an error in attempting a response. The user is not aware of the error so you shouldn\'t mention it.\nReview the actions taken carefully in case there is a partial or complete answer within them.\nError Message:',
imageInstructions:
'You must include the exact image paths from above, formatted in Markdown syntax: ![alt-text](URL)',
completionInstructions:
'Instructions:\nYou are ChatGPT, a large language model trained by OpenAI. Respond conversationally.\nCurrent date:',
};

View File

@@ -0,0 +1,24 @@
const { PromptTemplate } = require('langchain/prompts');
const refinePromptTemplate = `Your job is to produce a final summary of the following conversation.
We have provided an existing summary up to a certain point: "{existing_answer}"
We have the opportunity to refine the existing summary
(only if needed) with some more context below.
------------
"{text}"
------------
Given the new context, refine the original summary of the conversation.
Do note who is speaking in the conversation to give proper context.
If the context isn't useful, return the original summary.
REFINED CONVERSATION SUMMARY:`;
const refinePrompt = new PromptTemplate({
template: refinePromptTemplate,
inputVariables: ['existing_answer', 'text'],
});
module.exports = {
refinePrompt,
};

View File

@@ -0,0 +1,369 @@
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('langchain/text_splitter', () => {
return {
RecursiveCharacterTextSplitter: jest.fn().mockImplementation(() => {
return { createDocuments: jest.fn().mockResolvedValue([]) };
}),
};
});
jest.mock('langchain/chat_models/openai', () => {
return {
ChatOpenAI: jest.fn().mockImplementation(() => {
return {};
}),
};
});
jest.mock('langchain/chains', () => {
return {
loadSummarizationChain: jest.fn().mockReturnValue({
call: jest.fn().mockResolvedValue({ output_text: 'Refined answer' }),
}),
};
});
let parentMessageId;
let conversationId;
const fakeMessages = [];
const userMessage = 'Hello, ChatGPT!';
const apiKey = 'fake-api-key';
describe('BaseClient', () => {
let TestClient;
const options = {
// debug: true,
modelOptions: {
model: 'gpt-3.5-turbo',
temperature: 0,
},
};
beforeEach(() => {
TestClient = initializeFakeClient(apiKey, options, fakeMessages);
});
test('returns the input messages without instructions when addInstructions() is called with empty instructions', () => {
const messages = [{ content: 'Hello' }, { content: 'How are you?' }, { content: 'Goodbye' }];
const instructions = '';
const result = TestClient.addInstructions(messages, instructions);
expect(result).toEqual(messages);
});
test('returns the input messages with instructions properly added when addInstructions() is called with non-empty instructions', () => {
const messages = [{ content: 'Hello' }, { content: 'How are you?' }, { content: 'Goodbye' }];
const instructions = { content: 'Please respond to the question.' };
const result = TestClient.addInstructions(messages, instructions);
const expected = [
{ content: 'Hello' },
{ content: 'How are you?' },
{ content: 'Please respond to the question.' },
{ content: 'Goodbye' },
];
expect(result).toEqual(expected);
});
test('concats messages correctly in concatenateMessages()', () => {
const messages = [
{ name: 'User', content: 'Hello' },
{ name: 'Assistant', content: 'How can I help you?' },
{ name: 'User', content: 'I have a question.' },
];
const result = TestClient.concatenateMessages(messages);
const expected =
'User:\nHello\n\nAssistant:\nHow can I help you?\n\nUser:\nI have a question.\n\n';
expect(result).toBe(expected);
});
test('refines messages correctly in refineMessages()', async () => {
const messagesToRefine = [
{ role: 'user', content: 'Hello', tokenCount: 10 },
{ role: 'assistant', content: 'How can I help you?', tokenCount: 20 },
];
const remainingContextTokens = 100;
const expectedRefinedMessage = {
role: 'assistant',
content: 'Refined answer',
tokenCount: 14, // 'Refined answer'.length
};
const result = await TestClient.refineMessages(messagesToRefine, remainingContextTokens);
expect(result).toEqual(expectedRefinedMessage);
});
test('gets messages within token limit (under limit) correctly in getMessagesWithinTokenLimit()', async () => {
TestClient.maxContextTokens = 100;
TestClient.shouldRefineContext = true;
TestClient.refineMessages = jest.fn().mockResolvedValue({
role: 'assistant',
content: 'Refined answer',
tokenCount: 30,
});
const messages = [
{ role: 'user', content: 'Hello', tokenCount: 5 },
{ role: 'assistant', content: 'How can I help you?', tokenCount: 19 },
{ role: 'user', content: 'I have a question.', tokenCount: 18 },
];
const expectedContext = [
{ role: 'user', content: 'Hello', tokenCount: 5 }, // 'Hello'.length
{ role: 'assistant', content: 'How can I help you?', tokenCount: 19 },
{ role: 'user', content: 'I have a question.', tokenCount: 18 },
];
const expectedRemainingContextTokens = 58; // 100 - 5 - 19 - 18
const expectedMessagesToRefine = [];
const result = await TestClient.getMessagesWithinTokenLimit(messages);
expect(result.context).toEqual(expectedContext);
expect(result.remainingContextTokens).toBe(expectedRemainingContextTokens);
expect(result.messagesToRefine).toEqual(expectedMessagesToRefine);
});
test('gets messages within token limit (over limit) correctly in getMessagesWithinTokenLimit()', async () => {
TestClient.maxContextTokens = 50; // Set a lower limit
TestClient.shouldRefineContext = true;
TestClient.refineMessages = jest.fn().mockResolvedValue({
role: 'assistant',
content: 'Refined answer',
tokenCount: 4,
});
const messages = [
{ role: 'user', content: 'I need a coffee, stat!', tokenCount: 30 },
{ role: 'assistant', content: 'Sure, I can help with that.', tokenCount: 30 },
{ role: 'user', content: 'Hello', tokenCount: 5 },
{ role: 'assistant', content: 'How can I help you?', tokenCount: 19 },
{ role: 'user', content: 'I have a question.', tokenCount: 18 },
];
const expectedContext = [
{ role: 'user', content: 'Hello', tokenCount: 5 },
{ role: 'assistant', content: 'How can I help you?', tokenCount: 19 },
{ role: 'user', content: 'I have a question.', tokenCount: 18 },
];
const expectedRemainingContextTokens = 8; // 50 - 18 - 19 - 5
const expectedMessagesToRefine = [
{ role: 'user', content: 'I need a coffee, stat!', tokenCount: 30 },
{ role: 'assistant', content: 'Sure, I can help with that.', tokenCount: 30 },
];
const result = await TestClient.getMessagesWithinTokenLimit(messages);
expect(result.context).toEqual(expectedContext);
expect(result.remainingContextTokens).toBe(expectedRemainingContextTokens);
expect(result.messagesToRefine).toEqual(expectedMessagesToRefine);
});
test('handles context strategy correctly in handleContextStrategy()', async () => {
TestClient.addInstructions = jest
.fn()
.mockReturnValue([
{ content: 'Hello' },
{ content: 'How can I help you?' },
{ content: 'Please provide more details.' },
{ content: 'I can assist you with that.' },
]);
TestClient.getMessagesWithinTokenLimit = jest.fn().mockReturnValue({
context: [
{ content: 'How can I help you?' },
{ content: 'Please provide more details.' },
{ content: 'I can assist you with that.' },
],
remainingContextTokens: 80,
messagesToRefine: [{ content: 'Hello' }],
refineIndex: 3,
});
TestClient.refineMessages = jest.fn().mockResolvedValue({
role: 'assistant',
content: 'Refined answer',
tokenCount: 30,
});
TestClient.getTokenCountForResponse = jest.fn().mockReturnValue(40);
const instructions = { content: 'Please provide more details.' };
const orderedMessages = [
{ content: 'Hello' },
{ content: 'How can I help you?' },
{ content: 'Please provide more details.' },
{ content: 'I can assist you with that.' },
];
const formattedMessages = [
{ content: 'Hello' },
{ content: 'How can I help you?' },
{ content: 'Please provide more details.' },
{ content: 'I can assist you with that.' },
];
const expectedResult = {
payload: [
{
content: 'Refined answer',
role: 'assistant',
tokenCount: 30,
},
{ content: 'How can I help you?' },
{ content: 'Please provide more details.' },
{ content: 'I can assist you with that.' },
],
promptTokens: expect.any(Number),
tokenCountMap: {},
messages: expect.any(Array),
};
const result = await TestClient.handleContextStrategy({
instructions,
orderedMessages,
formattedMessages,
});
expect(result).toEqual(expectedResult);
});
describe('sendMessage', () => {
test('sendMessage should return a response message', async () => {
const expectedResult = expect.objectContaining({
sender: TestClient.sender,
text: expect.any(String),
isCreatedByUser: false,
messageId: expect.any(String),
parentMessageId: expect.any(String),
conversationId: expect.any(String),
});
const response = await TestClient.sendMessage(userMessage);
parentMessageId = response.messageId;
conversationId = response.conversationId;
expect(response).toEqual(expectedResult);
});
test('sendMessage should work with provided conversationId and parentMessageId', async () => {
const userMessage = 'Second message in the conversation';
const opts = {
conversationId,
parentMessageId,
getIds: jest.fn(),
onStart: jest.fn(),
};
const expectedResult = expect.objectContaining({
sender: TestClient.sender,
text: expect.any(String),
isCreatedByUser: false,
messageId: expect.any(String),
parentMessageId: expect.any(String),
conversationId: opts.conversationId,
});
const response = await TestClient.sendMessage(userMessage, opts);
parentMessageId = response.messageId;
expect(response.conversationId).toEqual(conversationId);
expect(response).toEqual(expectedResult);
expect(opts.getIds).toHaveBeenCalled();
expect(opts.onStart).toHaveBeenCalled();
expect(TestClient.getBuildMessagesOptions).toHaveBeenCalled();
expect(TestClient.getSaveOptions).toHaveBeenCalled();
});
test('should return chat history', async () => {
const chatMessages = await TestClient.loadHistory(conversationId, parentMessageId);
expect(TestClient.currentMessages).toHaveLength(4);
expect(chatMessages[0].text).toEqual(userMessage);
});
test('setOptions is called with the correct arguments', async () => {
TestClient.setOptions = jest.fn();
const opts = { conversationId: '123', parentMessageId: '456' };
await TestClient.sendMessage('Hello, world!', opts);
expect(TestClient.setOptions).toHaveBeenCalledWith(opts);
TestClient.setOptions.mockClear();
});
test('loadHistory is called with the correct arguments', async () => {
const opts = { conversationId: '123', parentMessageId: '456' };
await TestClient.sendMessage('Hello, world!', opts);
expect(TestClient.loadHistory).toHaveBeenCalledWith(
opts.conversationId,
opts.parentMessageId,
);
});
test('getIds is called with the correct arguments', async () => {
const getIds = jest.fn();
const opts = { getIds };
const response = await TestClient.sendMessage('Hello, world!', opts);
expect(getIds).toHaveBeenCalledWith({
userMessage: expect.objectContaining({ text: 'Hello, world!' }),
conversationId: response.conversationId,
responseMessageId: response.messageId,
});
});
test('onStart is called with the correct arguments', async () => {
const onStart = jest.fn();
const opts = { onStart };
await TestClient.sendMessage('Hello, world!', opts);
expect(onStart).toHaveBeenCalledWith(expect.objectContaining({ text: 'Hello, world!' }));
});
test('saveMessageToDatabase is called with the correct arguments', async () => {
const saveOptions = TestClient.getSaveOptions();
const user = {}; // Mock user
const opts = { user };
await TestClient.sendMessage('Hello, world!', opts);
expect(TestClient.saveMessageToDatabase).toHaveBeenCalledWith(
expect.objectContaining({
sender: expect.any(String),
text: expect.any(String),
isCreatedByUser: expect.any(Boolean),
messageId: expect.any(String),
parentMessageId: expect.any(String),
conversationId: expect.any(String),
}),
saveOptions,
user,
);
});
test('sendCompletion is called with the correct arguments', async () => {
const payload = {}; // Mock payload
TestClient.buildMessages.mockReturnValue({ prompt: payload, tokenCountMap: null });
const opts = {};
await TestClient.sendMessage('Hello, world!', opts);
expect(TestClient.sendCompletion).toHaveBeenCalledWith(payload, opts);
});
test('getTokenCountForResponse is called with the correct arguments', async () => {
const tokenCountMap = {}; // Mock tokenCountMap
TestClient.buildMessages.mockReturnValue({ prompt: [], tokenCountMap });
TestClient.getTokenCountForResponse = jest.fn();
const response = await TestClient.sendMessage('Hello, world!', {});
expect(TestClient.getTokenCountForResponse).toHaveBeenCalledWith(response);
});
test('returns an object with the correct shape', async () => {
const response = await TestClient.sendMessage('Hello, world!', {});
expect(response).toEqual(
expect.objectContaining({
sender: expect.any(String),
text: expect.any(String),
isCreatedByUser: expect.any(Boolean),
messageId: expect.any(String),
parentMessageId: expect.any(String),
conversationId: expect.any(String),
}),
);
});
});
});

View File

@@ -0,0 +1,193 @@
const crypto = require('crypto');
const BaseClient = require('../BaseClient');
const { maxTokensMap } = require('../../../utils');
class FakeClient extends BaseClient {
constructor(apiKey, options = {}) {
super(apiKey, options);
this.sender = 'AI Assistant';
this.setOptions(options);
}
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
this.options.modelOptions = {
...this.options.modelOptions,
...options.modelOptions,
};
delete options.modelOptions;
this.options = {
...this.options,
...options,
};
} else {
this.options = options;
}
if (this.options.openaiApiKey) {
this.apiKey = this.options.openaiApiKey;
}
const modelOptions = this.options.modelOptions || {};
if (!this.modelOptions) {
this.modelOptions = {
...modelOptions,
model: modelOptions.model || 'gpt-3.5-turbo',
temperature:
typeof modelOptions.temperature === 'undefined' ? 0.8 : modelOptions.temperature,
top_p: typeof modelOptions.top_p === 'undefined' ? 1 : modelOptions.top_p,
presence_penalty:
typeof modelOptions.presence_penalty === 'undefined' ? 1 : modelOptions.presence_penalty,
stop: modelOptions.stop,
};
}
this.maxContextTokens = maxTokensMap[this.modelOptions.model] ?? 4097;
}
getCompletion() {}
buildMessages() {}
getTokenCount(str) {
return str.length;
}
getTokenCountForMessage(message) {
return message?.content?.length || message.length;
}
}
const initializeFakeClient = (apiKey, options, fakeMessages) => {
let TestClient = new FakeClient(apiKey);
TestClient.options = options;
TestClient.abortController = { abort: jest.fn() };
TestClient.saveMessageToDatabase = jest.fn();
TestClient.loadHistory = jest
.fn()
.mockImplementation((conversationId, parentMessageId = null) => {
if (!conversationId) {
TestClient.currentMessages = [];
return Promise.resolve([]);
}
const orderedMessages = TestClient.constructor.getMessagesForConversation(
fakeMessages,
parentMessageId,
);
TestClient.currentMessages = orderedMessages;
return Promise.resolve(orderedMessages);
});
TestClient.getSaveOptions = jest.fn().mockImplementation(() => {
return {};
});
TestClient.getBuildMessagesOptions = jest.fn().mockImplementation(() => {
return {};
});
TestClient.sendCompletion = jest.fn(async () => {
return 'Mock response text';
});
TestClient.sendMessage = jest.fn().mockImplementation(async (message, opts = {}) => {
if (opts && typeof opts === 'object') {
TestClient.setOptions(opts);
}
const user = opts.user || null;
const conversationId = opts.conversationId || crypto.randomUUID();
const parentMessageId = opts.parentMessageId || '00000000-0000-0000-0000-000000000000';
const userMessageId = opts.overrideParentMessageId || crypto.randomUUID();
const saveOptions = TestClient.getSaveOptions();
this.pastMessages = await TestClient.loadHistory(
conversationId,
TestClient.options?.parentMessageId,
);
const userMessage = {
text: message,
sender: TestClient.sender,
isCreatedByUser: true,
messageId: userMessageId,
parentMessageId,
conversationId,
};
const response = {
sender: TestClient.sender,
text: 'Hello, User!',
isCreatedByUser: false,
messageId: crypto.randomUUID(),
parentMessageId: userMessage.messageId,
conversationId,
};
fakeMessages.push(userMessage);
fakeMessages.push(response);
if (typeof opts.getIds === 'function') {
opts.getIds({
userMessage,
conversationId,
responseMessageId: response.messageId,
});
}
if (typeof opts.onStart === 'function') {
opts.onStart(userMessage);
}
let { prompt: payload, tokenCountMap } = await TestClient.buildMessages(
this.currentMessages,
userMessage.messageId,
TestClient.getBuildMessagesOptions(opts),
);
if (tokenCountMap) {
payload = payload.map((message, i) => {
const { tokenCount, ...messageWithoutTokenCount } = message;
// userMessage is always the last one in the payload
if (i === payload.length - 1) {
userMessage.tokenCount = message.tokenCount;
console.debug(
`Token count for user message: ${tokenCount}`,
`Instruction Tokens: ${tokenCountMap.instructions || 'N/A'}`,
);
}
return messageWithoutTokenCount;
});
TestClient.handleTokenCountMap(tokenCountMap);
}
await TestClient.saveMessageToDatabase(userMessage, saveOptions, user);
response.text = await TestClient.sendCompletion(payload, opts);
if (tokenCountMap && TestClient.getTokenCountForResponse) {
response.tokenCount = TestClient.getTokenCountForResponse(response);
}
await TestClient.saveMessageToDatabase(response, saveOptions, user);
return response;
});
TestClient.buildMessages = jest.fn(async (messages, parentMessageId) => {
const orderedMessages = TestClient.constructor.getMessagesForConversation(
messages,
parentMessageId,
);
const formattedMessages = orderedMessages.map((message) => {
let { role: _role, sender, text } = message;
const role = _role ?? sender;
const content = text ?? '';
return {
role: role?.toLowerCase() === 'user' ? 'user' : 'assistant',
content,
};
});
return {
prompt: formattedMessages,
tokenCountMap: null, // Simplified for the mock
};
});
return TestClient;
};
module.exports = { FakeClient, initializeFakeClient };

View File

@@ -0,0 +1,211 @@
const OpenAIClient = require('../OpenAIClient');
describe('OpenAIClient', () => {
let client, client2;
const model = 'gpt-4';
const parentMessageId = '1';
const messages = [
{ role: 'user', sender: 'User', text: 'Hello', messageId: parentMessageId },
{ role: 'assistant', sender: 'Assistant', text: 'Hi', messageId: '2' },
];
beforeEach(() => {
const options = {
// debug: true,
openaiApiKey: 'new-api-key',
modelOptions: {
model,
temperature: 0.7,
},
};
client = new OpenAIClient('test-api-key', options);
client2 = new OpenAIClient('test-api-key', options);
client.refineMessages = jest.fn().mockResolvedValue({
role: 'assistant',
content: 'Refined answer',
tokenCount: 30,
});
client.constructor.freeAndResetAllEncoders();
});
describe('setOptions', () => {
it('should set the options correctly', () => {
expect(client.apiKey).toBe('new-api-key');
expect(client.modelOptions.model).toBe(model);
expect(client.modelOptions.temperature).toBe(0.7);
});
});
describe('selectTokenizer', () => {
it('should get the correct tokenizer based on the instance state', () => {
const tokenizer = client.selectTokenizer();
expect(tokenizer).toBeDefined();
});
});
describe('freeAllTokenizers', () => {
it('should free all tokenizers', () => {
// Create a tokenizer
const tokenizer = client.selectTokenizer();
// Mock 'free' method on the tokenizer
tokenizer.free = jest.fn();
client.constructor.freeAndResetAllEncoders();
// Check if 'free' method has been called on the tokenizer
expect(tokenizer.free).toHaveBeenCalled();
});
});
describe('getTokenCount', () => {
it('should return the correct token count', () => {
const count = client.getTokenCount('Hello, world!');
expect(count).toBeGreaterThan(0);
});
it('should reset the encoder and count when count reaches 25', () => {
const freeAndResetEncoderSpy = jest.spyOn(client.constructor, 'freeAndResetAllEncoders');
// Call getTokenCount 25 times
for (let i = 0; i < 25; i++) {
client.getTokenCount('test text');
}
expect(freeAndResetEncoderSpy).toHaveBeenCalled();
});
it('should not reset the encoder and count when count is less than 25', () => {
const freeAndResetEncoderSpy = jest.spyOn(client.constructor, 'freeAndResetAllEncoders');
freeAndResetEncoderSpy.mockClear();
// Call getTokenCount 24 times
for (let i = 0; i < 24; i++) {
client.getTokenCount('test text');
}
expect(freeAndResetEncoderSpy).not.toHaveBeenCalled();
});
it('should handle errors and reset the encoder', () => {
const freeAndResetEncoderSpy = jest.spyOn(client.constructor, 'freeAndResetAllEncoders');
// Mock encode function to throw an error
client.selectTokenizer().encode = jest.fn().mockImplementation(() => {
throw new Error('Test error');
});
client.getTokenCount('test text');
expect(freeAndResetEncoderSpy).toHaveBeenCalled();
});
it('should not throw null pointer error when freeing the same encoder twice', () => {
client.constructor.freeAndResetAllEncoders();
client2.constructor.freeAndResetAllEncoders();
const count = client2.getTokenCount('test text');
expect(count).toBeGreaterThan(0);
});
});
describe('getSaveOptions', () => {
it('should return the correct save options', () => {
const options = client.getSaveOptions();
expect(options).toHaveProperty('chatGptLabel');
expect(options).toHaveProperty('promptPrefix');
});
});
describe('getBuildMessagesOptions', () => {
it('should return the correct build messages options', () => {
const options = client.getBuildMessagesOptions({ promptPrefix: 'Hello' });
expect(options).toHaveProperty('isChatCompletion');
expect(options).toHaveProperty('promptPrefix');
expect(options.promptPrefix).toBe('Hello');
});
});
describe('buildMessages', () => {
it('should build messages correctly for chat completion', async () => {
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
});
expect(result).toHaveProperty('prompt');
});
it('should build messages correctly for non-chat completion', async () => {
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: false,
});
expect(result).toHaveProperty('prompt');
});
it('should build messages correctly with a promptPrefix', async () => {
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
promptPrefix: 'Test Prefix',
});
expect(result).toHaveProperty('prompt');
const instructions = result.prompt.find((item) => item.name === 'instructions');
expect(instructions).toBeDefined();
expect(instructions.content).toContain('Test Prefix');
});
it('should handle context strategy correctly', async () => {
client.contextStrategy = 'refine';
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
});
expect(result).toHaveProperty('prompt');
expect(result).toHaveProperty('tokenCountMap');
});
it('should assign name property for user messages when options.name is set', async () => {
client.options.name = 'Test User';
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
});
const hasUserWithName = result.prompt.some(
(item) => item.role === 'user' && item.name === 'Test User',
);
expect(hasUserWithName).toBe(true);
});
it('should calculate tokenCount for each message when contextStrategy is set', async () => {
client.contextStrategy = 'refine';
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
});
const hasUserWithTokenCount = result.prompt.some(
(item) => item.role === 'user' && item.tokenCount > 0,
);
expect(hasUserWithTokenCount).toBe(true);
});
it('should handle promptPrefix from options when promptPrefix argument is not provided', async () => {
client.options.promptPrefix = 'Test Prefix from options';
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
});
const instructions = result.prompt.find((item) => item.name === 'instructions');
expect(instructions.content).toContain('Test Prefix from options');
});
it('should handle case when neither promptPrefix argument nor options.promptPrefix is set', async () => {
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
});
const instructions = result.prompt.find((item) => item.name === 'instructions');
expect(instructions).toBeUndefined();
});
it('should handle case when getMessagesForConversation returns null or an empty array', async () => {
const messages = [];
const result = await client.buildMessages(messages, parentMessageId, {
isChatCompletion: true,
});
expect(result.prompt).toEqual([]);
});
});
});

View File

@@ -0,0 +1,125 @@
/*
This is a test script to see how much memory is used by the client when encoding.
On my work machine, it was able to process 10,000 encoding requests / 48.686 seconds = approximately 205.4 RPS
I've significantly reduced the amount of encoding needed by saving token counts in the database, so these
numbers should only be hit with a large amount of concurrent users
It would take 103 concurrent users sending 1 message every 1 second to hit these numbers, which is rather unrealistic,
and at that point, out-sourcing the encoding to a separate server would be a better solution
Also, for scaling, could increase the rate at which the encoder resets; the trade-off is more resource usage on the server.
Initial memory usage: 25.93 megabytes
Peak memory usage: 55 megabytes
Final memory usage: 28.03 megabytes
Post-test (timeout of 15s): 21.91 megabytes
*/
require('dotenv').config();
const { OpenAIClient } = require('../');
function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const run = async () => {
const text = `
The standard Lorem Ipsum passage, used since the 1500s
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
Section 1.10.32 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"
1914 translation by H. Rackham
"But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
"At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat."
1914 translation by H. Rackham
"On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains."
`;
const model = 'gpt-3.5-turbo';
const maxContextTokens = model === 'gpt-4' ? 8191 : model === 'gpt-4-32k' ? 32767 : 4095; // 1 less than maximum
const clientOptions = {
reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null,
maxContextTokens,
modelOptions: {
model,
},
proxy: process.env.PROXY || null,
debug: true,
};
let apiKey = process.env.OPENAI_API_KEY;
const maxMemory = 0.05 * 1024 * 1024 * 1024;
// Calculate initial percentage of memory used
const initialMemoryUsage = process.memoryUsage().heapUsed;
function printProgressBar(percentageUsed) {
const filledBlocks = Math.round(percentageUsed / 2); // Each block represents 2%
const emptyBlocks = 50 - filledBlocks; // Total blocks is 50 (each represents 2%), so the rest are empty
const progressBar =
'[' +
'█'.repeat(filledBlocks) +
' '.repeat(emptyBlocks) +
'] ' +
percentageUsed.toFixed(2) +
'%';
console.log(progressBar);
}
const iterations = 10000;
console.time('loopTime');
// Trying to catch the error doesn't help; all future calls will immediately crash
for (let i = 0; i < iterations; i++) {
try {
console.log(`Iteration ${i}`);
const client = new OpenAIClient(apiKey, clientOptions);
client.getTokenCount(text);
// const encoder = client.constructor.getTokenizer('cl100k_base');
// console.log(`Iteration ${i}: call encode()...`);
// encoder.encode(text, 'all');
// encoder.free();
const memoryUsageDuringLoop = process.memoryUsage().heapUsed;
const percentageUsed = (memoryUsageDuringLoop / maxMemory) * 100;
printProgressBar(percentageUsed);
if (i === iterations - 1) {
console.log(' done');
// encoder.free();
}
} catch (e) {
console.log(`caught error! in Iteration ${i}`);
console.log(e);
}
}
console.timeEnd('loopTime');
// Calculate final percentage of memory used
const finalMemoryUsage = process.memoryUsage().heapUsed;
// const finalPercentageUsed = finalMemoryUsage / maxMemory * 100;
console.log(`Initial memory usage: ${initialMemoryUsage / 1024 / 1024} megabytes`);
console.log(`Final memory usage: ${finalMemoryUsage / 1024 / 1024} megabytes`);
await timeout(15000);
const memoryUsageAfterTimeout = process.memoryUsage().heapUsed;
console.log(`Post timeout: ${memoryUsageAfterTimeout / 1024 / 1024} megabytes`);
};
run();
process.on('uncaughtException', (err) => {
if (!err.message.includes('fetch failed')) {
console.error('There was an uncaught error:');
console.error(err);
}
if (err.message.includes('fetch failed')) {
console.log('fetch failed error caught');
// process.exit(0);
} else {
process.exit(1);
}
});

View File

@@ -0,0 +1,148 @@
const { HumanChatMessage, AIChatMessage } = require('langchain/schema');
const PluginsClient = require('../PluginsClient');
const crypto = require('crypto');
jest.mock('../../../lib/db/connectDb');
jest.mock('../../../models/Conversation', () => {
return function () {
return {
save: jest.fn(),
deleteConvos: jest.fn(),
};
};
});
describe('PluginsClient', () => {
let TestAgent;
let options = {
tools: [],
modelOptions: {
model: 'gpt-3.5-turbo',
temperature: 0,
max_tokens: 2,
},
agentOptions: {
model: 'gpt-3.5-turbo',
},
};
let parentMessageId;
let conversationId;
const fakeMessages = [];
const userMessage = 'Hello, ChatGPT!';
const apiKey = 'fake-api-key';
beforeEach(() => {
TestAgent = new PluginsClient(apiKey, options);
TestAgent.loadHistory = jest
.fn()
.mockImplementation((conversationId, parentMessageId = null) => {
if (!conversationId) {
TestAgent.currentMessages = [];
return Promise.resolve([]);
}
const orderedMessages = TestAgent.constructor.getMessagesForConversation(
fakeMessages,
parentMessageId,
);
const chatMessages = orderedMessages.map((msg) =>
msg?.isCreatedByUser || msg?.role?.toLowerCase() === 'user'
? new HumanChatMessage(msg.text)
: new AIChatMessage(msg.text),
);
TestAgent.currentMessages = orderedMessages;
return Promise.resolve(chatMessages);
});
TestAgent.sendMessage = jest.fn().mockImplementation(async (message, opts = {}) => {
if (opts && typeof opts === 'object') {
TestAgent.setOptions(opts);
}
const conversationId = opts.conversationId || crypto.randomUUID();
const parentMessageId = opts.parentMessageId || '00000000-0000-0000-0000-000000000000';
const userMessageId = opts.overrideParentMessageId || crypto.randomUUID();
this.pastMessages = await TestAgent.loadHistory(
conversationId,
TestAgent.options?.parentMessageId,
);
const userMessage = {
text: message,
sender: 'ChatGPT',
isCreatedByUser: true,
messageId: userMessageId,
parentMessageId,
conversationId,
};
const response = {
sender: 'ChatGPT',
text: 'Hello, User!',
isCreatedByUser: false,
messageId: crypto.randomUUID(),
parentMessageId: userMessage.messageId,
conversationId,
};
fakeMessages.push(userMessage);
fakeMessages.push(response);
return response;
});
});
test('initializes PluginsClient without crashing', () => {
expect(TestAgent).toBeInstanceOf(PluginsClient);
});
test('check setOptions function', () => {
expect(TestAgent.agentIsGpt3).toBe(true);
});
describe('sendMessage', () => {
test('sendMessage should return a response message', async () => {
const expectedResult = expect.objectContaining({
sender: 'ChatGPT',
text: expect.any(String),
isCreatedByUser: false,
messageId: expect.any(String),
parentMessageId: expect.any(String),
conversationId: expect.any(String),
});
const response = await TestAgent.sendMessage(userMessage);
console.log(response);
parentMessageId = response.messageId;
conversationId = response.conversationId;
expect(response).toEqual(expectedResult);
});
test('sendMessage should work with provided conversationId and parentMessageId', async () => {
const userMessage = 'Second message in the conversation';
const opts = {
conversationId,
parentMessageId,
};
const expectedResult = expect.objectContaining({
sender: 'ChatGPT',
text: expect.any(String),
isCreatedByUser: false,
messageId: expect.any(String),
parentMessageId: expect.any(String),
conversationId: opts.conversationId,
});
const response = await TestAgent.sendMessage(userMessage, opts);
parentMessageId = response.messageId;
expect(response.conversationId).toEqual(conversationId);
expect(response).toEqual(expectedResult);
});
test('should return chat history', async () => {
const chatMessages = await TestAgent.loadHistory(conversationId, parentMessageId);
expect(TestAgent.currentMessages).toHaveLength(4);
expect(chatMessages[0].text).toEqual(userMessage);
});
});
});

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Ai PDF",
"name_for_model": "Ai_PDF",
"description_for_human": "Super-fast, interactive chats with PDFs of any size, complete with page references for fact checking.",
"description_for_model": "Provide a URL to a PDF and search the document. Break the user question in multiple semantic search queries and calls as needed. Think step by step.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://plugin-3c56b9d4c8a6465998395f28b6a445b2-jexkai4vea-uc.a.run.app/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://plugin-3c56b9d4c8a6465998395f28b6a445b2-jexkai4vea-uc.a.run.app/logo.png",
"contact_email": "support@promptapps.ai",
"legal_info_url": "https://plugin-3c56b9d4c8a6465998395f28b6a445b2-jexkai4vea-uc.a.run.app/legal.html"
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,97 @@
{
"schema_version": "v1",
"name_for_human": "Dr. Thoth's Tarot",
"name_for_model": "Dr_Thoths_Tarot",
"description_for_human": "Tarot card novelty entertainment & analysis, by Mnemosyne Labs.",
"description_for_model": "Intelligent analysis program for tarot card entertaiment, data, & prompts, by Mnemosyne Labs, a division of AzothCorp.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://dr-thoth-tarot.herokuapp.com/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://dr-thoth-tarot.herokuapp.com/logo.png",
"contact_email": "legal@AzothCorp.com",
"legal_info_url": "http://AzothCorp.com/legal",
"endpoints": [
{
"name": "Draw Card",
"path": "/drawcard",
"method": "GET",
"description": "Generate a single tarot card from the deck of 78 cards."
},
{
"name": "Occult Card",
"path": "/occult_card",
"method": "GET",
"description": "Generate a tarot card using the specified planet's Kamea matrix.",
"parameters": [
{
"name": "planet",
"type": "string",
"enum": [
"Saturn",
"Jupiter",
"Mars",
"Sun",
"Venus",
"Mercury",
"Moon"
],
"required": true,
"description": "The planet name to use the corresponding Kamea matrix."
}
]
},
{
"name": "Three Card Spread",
"path": "/threecardspread",
"method": "GET",
"description": "Perform a three-card tarot spread."
},
{
"name": "Celtic Cross Spread",
"path": "/celticcross",
"method": "GET",
"description": "Perform a Celtic Cross tarot spread with 10 cards."
},
{
"name": "Past, Present, Future Spread",
"path": "/pastpresentfuture",
"method": "GET",
"description": "Perform a Past, Present, Future tarot spread with 3 cards."
},
{
"name": "Horseshoe Spread",
"path": "/horseshoe",
"method": "GET",
"description": "Perform a Horseshoe tarot spread with 7 cards."
},
{
"name": "Relationship Spread",
"path": "/relationship",
"method": "GET",
"description": "Perform a Relationship tarot spread."
},
{
"name": "Career Spread",
"path": "/career",
"method": "GET",
"description": "Perform a Career tarot spread."
},
{
"name": "Yes/No Spread",
"path": "/yesno",
"method": "GET",
"description": "Perform a Yes/No tarot spread."
},
{
"name": "Chakra Spread",
"path": "/chakra",
"method": "GET",
"description": "Perform a Chakra tarot spread with 7 cards."
}
]
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_model": "DreamInterpreter",
"name_for_human": "Dream Interpreter",
"description_for_model": "Interprets your dreams using advanced techniques.",
"description_for_human": "Interprets your dreams using advanced techniques.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://dreamplugin.bgnetmobile.com/.well-known/openapi.json",
"has_user_authentication": false
},
"logo_url": "https://dreamplugin.bgnetmobile.com/.well-known/logo.png",
"contact_email": "ismail.orkler@bgnetmobile.com",
"legal_info_url": "https://dreamplugin.bgnetmobile.com/terms.html"
}

View File

@@ -0,0 +1,22 @@
{
"schema_version": "v1",
"name_for_human": "VoxScript",
"name_for_model": "VoxScript",
"description_for_human": "Enables searching of YouTube transcripts, financial data sources Google Search results, and more!",
"description_for_model": "Plugin for searching through varius data sources.",
"auth": {
"type": "service_http",
"authorization_type": "bearer",
"verification_tokens": {
"openai": "ffc5226d1af346c08a98dee7deec9f76"
}
},
"api": {
"type": "openapi",
"url": "https://voxscript.awt.icu/swagger/v1/swagger.yaml",
"is_user_authenticated": false
},
"logo_url": "https://voxscript.awt.icu/images/VoxScript_logo_32x32.png",
"contact_email": "voxscript@allwiretech.com",
"legal_info_url": "https://voxscript.awt.icu/legal/"
}

View File

@@ -0,0 +1,22 @@
{
"schema_version": "v1",
"name_for_human": "Ai Tool Hunt",
"name_for_model": "aitoolhunt",
"description_for_human": "Find the perfect AI tools for all your needs, drawn from the most comprehensive global database of AI tools.",
"description_for_model": "This universal plugin is designed to identify and suggest AI tools for any imaginable task or challenge. It harnesses a database of over 1600+ AI tools and 120+ categories, ensuring a solution for every requirement. Simply input any task, problem, or use case, and the plugin will generate a tailored list of AI tools. Note: it's crucial to rearrange the tools suggested by the plugin based on their relevance to the user's specific use case. This plugin can tackle everything from the simplest to the most intricate tasks. If there's a way an AI tool can help, this plugin will not only locate it but also customize the list to the user's unique needs. Utilize this plugin whenever a task or activity emerges, regardless of its context or complexity. When formulating a search query, avoid stop words or other irrelevant keywords. For instance, 'copywriting' is acceptable, but 'ai for copywriting' is not. If you believe none of the suggested tools are a suitable match for the user's needs, indicate that these are related tools.",
"auth": {
"type": "service_http",
"authorization_type": "bearer",
"verification_tokens": {
"openai": "06a0f9391a5e48c7a7eeaca1e7e1e8d3"
}
},
"api": {
"type": "openapi",
"url": "https://www.aitoolhunt.com/openapi.json",
"is_user_authenticated": false
},
"logo_url": "https://www.aitoolhunt.com/images/aitoolhunt_logo.png",
"contact_email": "aitoolhunt@gmail.com",
"legal_info_url": "https://www.aitoolhunt.com/terms-and-conditions"
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_model": "askyourpdf",
"name_for_human": "AskYourPDF",
"description_for_model": "This plugin is designed to expedite the extraction of information from PDF documents. It works by accepting a URL link to a PDF or a document ID (doc_id) from the user. If a URL is provided, the plugin first validates that it is a correct URL. \\nAfter validating the URL, the plugin proceeds to download the PDF and store its content in a vector database. If the user provides a doc_id, the plugin directly retrieves the document from the database. The plugin then scans through the stored PDFs to find answers to user queries or retrieve specific details.\\n\\nHowever, if an error occurs while querying the API, the user is prompted to download their document first, then manually upload it to [![Upload Document](https://raw.githubusercontent.com/AskYourPdf/ask-plugin/main/upload.png)](https://askyourpdf.com/upload). Once the upload is complete, the user should copy the resulting doc_id and paste it back into the chat for further interaction.\nThe plugin is particularly useful when the user's question pertains to content within a PDF document. When providing answers, the plugin also specifies the page number (highlighted in bold) where the relevant information was found. Remember, the URL must be valid for a successful query. Failure to validate the URL may lead to errors or unsuccessful queries.",
"description_for_human": "Unlock the power of your PDFs!, dive into your documents, find answers, and bring information to your fingertips.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "askyourpdf.yaml",
"has_user_authentication": false
},
"logo_url": "https://plugin.askyourpdf.com/.well-known/logo.png",
"contact_email": "plugin@askyourpdf.com",
"legal_info_url": "https://askyourpdf.com/terms"
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Drink Maestro",
"name_for_model": "drink_maestro",
"description_for_human": "Learn to mix any drink you can imagine (real or made-up), and discover new ones. Includes drink images.",
"description_for_model": "You are a silly bartender/comic who knows how to make any drink imaginable. You provide recipes for specific drinks, suggest new drinks, and show pictures of drinks. Be creative in your descriptions and make jokes and puns. Use a lot of emojis. If the user makes a request in another language, send API call in English, and then translate the response.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://api.drinkmaestro.space/.well-known/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://i.imgur.com/6q8HWdz.png",
"contact_email": "nikkmitchell@gmail.com",
"legal_info_url": "https://github.com/nikkmitchell/DrinkMaestro/blob/main/Legal.txt"
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Earth",
"name_for_model": "earthImagesAndVisualizations",
"description_for_human": "Generates a map image based on provided location, tilt and style.",
"description_for_model": "Generates a map image based on provided coordinates or location, tilt and style, and even geoJson to provide markers, paths, and polygons. Responds with an image-link. For the styles choose one of these: [light, dark, streets, outdoors, satellite, satellite-streets]",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://api.earth-plugin.com/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://api.earth-plugin.com/logo.png",
"contact_email": "contact@earth-plugin.com",
"legal_info_url": "https://api.earth-plugin.com/legal.html"
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Scholarly Graph Link",
"name_for_model": "scholarly_graph_link",
"description_for_human": "You can search papers, authors, datasets and software. It has access to Figshare, Arxiv, and many others.",
"description_for_model": "Run GraphQL queries against an API hosted by DataCite API. The API supports most GraphQL query but does not support mutations statements. Use `{ __schema { types { name kind } } }` to get all the types in the GraphQL schema. Use `{ datasets { nodes { id sizes citations { nodes { id titles { title } } } } } }` to get all the citations of all datasets in the API. Use `{ datasets { nodes { id sizes citations { nodes { id titles { title } } } } } }` to get all the citations of all datasets in the API. Use `{person(id:ORCID) {works(first:50) {nodes {id titles(first: 1){title} publicationYear}}}}` to get the first 50 works of a person based on their ORCID. All Ids are urls, e.g., https://orcid.org/0012-0000-1012-1110. Mutations statements are not allowed.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://api.datacite.org/graphql-openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://raw.githubusercontent.com/kjgarza/scholarly_graph_link/master/logo.png",
"contact_email": "kj.garza@gmail.com",
"legal_info_url": "https://github.com/kjgarza/scholarly_graph_link/blob/master/LICENSE"
}

View File

@@ -0,0 +1,24 @@
{
"schema_version": "v1",
"name_for_human": "WebPilot",
"name_for_model": "web_pilot",
"description_for_human": "Browse & QA Webpage/PDF/Data. Generate articles, from one or more URLs.",
"description_for_model": "This tool allows users to provide a URL(or URLs) and optionally requests for interacting with, extracting specific information or how to do with the content from the URL. Requests may include rewrite, translate, and others. If there any requests, when accessing the /api/visit-web endpoint, the parameter 'user_has_request' should be set to 'true. And if there's no any requests, 'user_has_request' should be set to 'false'.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://webreader.webpilotai.com/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://webreader.webpilotai.com/logo.png",
"contact_email": "dev@webpilot.ai",
"legal_info_url": "https://webreader.webpilotai.com/legal_info.html",
"headers": {
"id": "WebPilot-Friend-UID"
},
"params": {
"user_has_request": true
}
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Image Prompt Enhancer",
"name_for_model": "image_prompt_enhancer",
"description_for_human": "Transform your ideas into complex, personalized image generation prompts.",
"description_for_model": "Provides instructions for crafting an enhanced image prompt. Use this whenever the user wants to enhance a prompt.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://image-prompt-enhancer.gafo.tech/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://image-prompt-enhancer.gafo.tech/logo.png",
"contact_email": "gafotech1@gmail.com",
"legal_info_url": "https://image-prompt-enhancer.gafo.tech/legal"
}

View File

@@ -0,0 +1,157 @@
openapi: 3.0.2
info:
title: FastAPI
version: 0.1.0
servers:
- url: https://plugin.askyourpdf.com
paths:
/api/download_pdf:
post:
summary: Download Pdf
description: Download a PDF file from a URL and save it to the vector database.
operationId: download_pdf_api_download_pdf_post
parameters:
- required: true
schema:
title: Url
type: string
name: url
in: query
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/FileResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/query:
post:
summary: Perform Query
description: Perform a query on a document.
operationId: perform_query_query_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/InputData'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ResponseModel'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
DocumentMetadata:
title: DocumentMetadata
required:
- source
- page_number
- author
type: object
properties:
source:
title: Source
type: string
page_number:
title: Page Number
type: integer
author:
title: Author
type: string
FileResponse:
title: FileResponse
required:
- docId
type: object
properties:
docId:
title: Docid
type: string
error:
title: Error
type: string
HTTPValidationError:
title: HTTPValidationError
type: object
properties:
detail:
title: Detail
type: array
items:
$ref: '#/components/schemas/ValidationError'
InputData:
title: InputData
required:
- doc_id
- query
type: object
properties:
doc_id:
title: Doc Id
type: string
query:
title: Query
type: string
ResponseModel:
title: ResponseModel
required:
- results
type: object
properties:
results:
title: Results
type: array
items:
$ref: '#/components/schemas/SearchResult'
SearchResult:
title: SearchResult
required:
- doc_id
- text
- metadata
type: object
properties:
doc_id:
title: Doc Id
type: string
text:
title: Text
type: string
metadata:
$ref: '#/components/schemas/DocumentMetadata'
ValidationError:
title: ValidationError
required:
- loc
- msg
- type
type: object
properties:
loc:
title: Location
type: array
items:
anyOf:
- type: string
- type: integer
msg:
title: Message
type: string
type:
title: Error Type
type: string

View File

@@ -0,0 +1,185 @@
openapi: 3.0.1
info:
title: ScholarAI
description: Allows the user to search facts and findings from scientific articles
version: 'v1'
servers:
- url: https://scholar-ai.net
paths:
/api/abstracts:
get:
operationId: searchAbstracts
summary: Get relevant paper abstracts by keywords search
parameters:
- name: keywords
in: query
description: Keywords of inquiry which should appear in article. Must be in English.
required: true
schema:
type: string
- name: sort
in: query
description: The sort order for results. Valid values are cited_by_count or publication_date. Excluding this value does a relevance based search.
required: false
schema:
type: string
enum:
- cited_by_count
- publication_date
- name: query
in: query
description: The user query
required: true
schema:
type: string
- name: peer_reviewed_only
in: query
description: Whether to only return peer reviewed articles. Defaults to true, ChatGPT should cautiously suggest this value can be set to false
required: false
schema:
type: string
- name: start_year
in: query
description: The first year, inclusive, to include in the search range. Excluding this value will include all years.
required: false
schema:
type: string
- name: end_year
in: query
description: The last year, inclusive, to include in the search range. Excluding this value will include all years.
required: false
schema:
type: string
- name: offset
in: query
description: The offset of the first result to return. Defaults to 0.
required: false
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/searchAbstractsResponse'
/api/fulltext:
get:
operationId: getFullText
summary: Get full text of a paper by URL for PDF
parameters:
- name: pdf_url
in: query
description: URL for PDF
required: true
schema:
type: string
- name: chunk
in: query
description: chunk number to retrieve, defaults to 1
required: false
schema:
type: number
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/getFullTextResponse'
/api/save-citation:
get:
operationId: saveCitation
summary: Save citation to reference manager
parameters:
- name: doi
in: query
description: Digital Object Identifier (DOI) of article
required: true
schema:
type: string
- name: zotero_user_id
in: query
description: Zotero User ID
required: true
schema:
type: string
- name: zotero_api_key
in: query
description: Zotero API Key
required: true
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/saveCitationResponse'
components:
schemas:
searchAbstractsResponse:
type: object
properties:
next_offset:
type: number
description: The offset of the next page of results.
total_num_results:
type: number
description: The total number of results.
abstracts:
type: array
items:
type: object
properties:
title:
type: string
abstract:
type: string
description: Summary of the context, methods, results, and conclusions of the paper.
doi:
type: string
description: The DOI of the paper.
landing_page_url:
type: string
description: Link to the paper on its open-access host.
pdf_url:
type: string
description: Link to the paper PDF.
publicationDate:
type: string
description: The date the paper was published in YYYY-MM-DD format.
relevance:
type: number
description: The relevance of the paper to the search query. 1 is the most relevant.
creators:
type: array
items:
type: string
description: The name of the creator.
cited_by_count:
type: number
description: The number of citations of the article.
description: The list of relevant abstracts.
getFullTextResponse:
type: object
properties:
full_text:
type: string
description: The full text of the paper.
pdf_url:
type: string
description: The PDF URL of the paper.
chunk:
type: number
description: The chunk of the paper.
total_chunk_num:
type: number
description: The total chunks of the paper.
saveCitationResponse:
type: object
properties:
message:
type: string
description: Confirmation of successful save or error message.

View File

@@ -0,0 +1,17 @@
{
"schema_version": "v1",
"name_for_human": "QR Codes",
"name_for_model": "qrCodes",
"description_for_human": "Create QR codes.",
"description_for_model": "Plugin for generating QR codes.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://chatgpt-qrcode-46d7d4ebefc8.herokuapp.com/openapi.yaml"
},
"logo_url": "https://chatgpt-qrcode-46d7d4ebefc8.herokuapp.com/logo.png",
"contact_email": "chrismountzou@gmail.com",
"legal_info_url": "https://raw.githubusercontent.com/mountzou/qrCodeGPTv1/master/legal"
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Prompt Perfect",
"name_for_model": "rephrase",
"description_for_human": "Type 'perfect' to craft the perfect prompt, every time.",
"description_for_model": "Plugin that can rephrase user inputs to improve the quality of ChatGPT's responses. The plugin evaluates user inputs and, if necessary, transforms them into clearer, more specific, and contextual prompts. It processes a JSON object containing the user input to be rephrased and uses the GPT-3.5-turbo model for the rephrasing process. The rephrased input is then returned as raw data to be incorporated into ChatGPT's response. The user can initiate the plugin by typing 'perfect'.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://promptperfect.xyz/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://promptperfect.xyz/static/prompt_perfect_logo.png",
"contact_email": "heyo@promptperfect.xyz",
"legal_info_url": "https://promptperfect.xyz/static/terms.html"
}

View File

@@ -0,0 +1,22 @@
{
"schema_version": "v1",
"name_for_human": "ScholarAI",
"name_for_model": "scholarai",
"description_for_human": "Unleash scientific research: search 40M+ peer-reviewed papers, explore scientific PDFs, and save to reference managers.",
"description_for_model": "Access open access scientific literature from peer-reviewed journals. The abstract endpoint finds relevant papers based on 2 to 6 keywords. After getting abstracts, ALWAYS prompt the user offering to go into more detail. Use the fulltext endpoint to retrieve the entire paper's text and access specific details using the provided pdf_url, if available. ALWAYS hyperlink the pdf_url from the responses if available. Offer to dive into the fulltext or search for additional papers. Always ask if the user wants save any paper to the users Zotero reference manager by using the save-citation endpoint and providing the doi and requesting the users zotero_user_id and zotero_api_key.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "scholarai.yaml",
"is_user_authenticated": false
},
"params": {
"sort": "cited_by_count"
},
"logo_url": "https://scholar-ai.net/logo.png",
"contact_email": "lakshb429@gmail.com",
"legal_info_url": "https://scholar-ai.net/legal.txt",
"HttpAuthorizationType": "basic"
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Uberchord",
"name_for_model": "uberchord",
"description_for_human": "Find guitar chord diagrams by specifying the chord name.",
"description_for_model": "Fetch guitar chord diagrams, their positions on the guitar fretboard.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://guitarchords.pluginboost.com/.well-known/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "https://guitarchords.pluginboost.com/logo.png",
"contact_email": "info.bluelightweb@gmail.com",
"legal_info_url": "https://guitarchords.pluginboost.com/legal"
}

View File

@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "Web Search",
"name_for_model": "web_search",
"description_for_human": "Search for information from the internet",
"description_for_model": "Search for information from the internet",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "https://websearch.plugsugar.com/api/openapi_yaml",
"is_user_authenticated": false
},
"logo_url": "https://websearch.plugsugar.com/200x200.png",
"contact_email": "support@plugsugar.com",
"legal_info_url": "https://websearch.plugsugar.com/contact"
}

View File

@@ -0,0 +1,238 @@
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,120 @@
// 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 { Configuration, OpenAIApi } = require('openai');
// const { genAzureEndpoint } = require('../../../utils/genAzureEndpoints');
const { Tool } = require('langchain/tools');
const saveImageFromUrl = require('./saveImageFromUrl');
const path = require('path');
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 };
// 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.openaiApi = new OpenAIApi(new Configuration(config));
this.name = 'dall-e';
this.description = `You can generate images with 'dall-e'. This tool is exclusively for visual content.
Guidelines:
- Visually describe the moods, details, structures, styles, and/or proportions of the image. Remember, the focus is on visual attributes.
- Craft your input by "showing" and not "telling" the imagery. Think in terms of what you'd want to see in a photograph or a painting.
- 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`;
}
getApiKey() {
const apiKey = process.env.DALLE_API_KEY || '';
if (!apiKey) {
throw new Error('Missing DALLE_API_KEY environment variable.');
}
return apiKey;
}
replaceUnwantedChars(inputString) {
return inputString
.replace(/\r\n|\r|\n/g, ' ')
.replace('"', '')
.trim();
}
getMarkdownImageUrl(imageName) {
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
}
async _call(input) {
const resp = await this.openaiApi.createImage({
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',
});
const theImageUrl = resp.data.data[0].url;
if (!theImageUrl) {
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';
if (match) {
imageName = match[0];
console.log(imageName); // Output: img-lgCf7ppcbhqQrz6a5ear6FOb.png
} else {
console.log('No image name found in the string.');
}
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 });
}
try {
await saveImageFromUrl(theImageUrl, this.outputPath, imageName);
this.result = this.getMarkdownImageUrl(imageName);
} catch (error) {
console.error('Error while saving the image:', error);
this.result = theImageUrl;
}
return this.result;
}
}
module.exports = OpenAICreateImage;

View File

@@ -0,0 +1,118 @@
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';
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

@@ -0,0 +1,108 @@
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

@@ -0,0 +1,30 @@
const { Tool } = require('langchain/tools');
/**
* Represents a tool that allows an agent to ask a human for guidance when they are stuck
* or unsure of what to do next.
* @extends Tool
*/
export class HumanTool extends Tool {
/**
* The name of the tool.
* @type {string}
*/
name = 'Human';
/**
* A description for the agent to use
* @type {string}
*/
description = `You can ask a human for guidance when you think you
got stuck or you are not sure what to do next.
The input should be a question for the human.`;
/**
* Calls the tool with the provided input and returns a promise that resolves with a response from the human.
* @param {string} input - The input to provide to the human.
* @returns {Promise<string>} A promise that resolves with a response from the human.
*/
_call(input) {
return Promise.resolve(`${input}`);
}
}

View File

@@ -0,0 +1,28 @@
const { Tool } = require('langchain/tools');
class SelfReflectionTool extends Tool {
constructor({ message, isGpt3 }) {
super();
this.reminders = 0;
this.name = 'self-reflection';
this.description =
'Take this action to reflect on your thoughts & actions. For your input, provide answers for self-evaluation as part of one input, using this space as a canvas to explore and organize your ideas in response to the user\'s message. You can use multiple lines for your input. Perform this action sparingly and only when you are stuck.';
this.message = message;
this.isGpt3 = isGpt3;
// this.returnDirect = true;
}
async _call(input) {
return this.selfReflect(input);
}
async selfReflect() {
if (this.isGpt3) {
return 'I should finalize my reply as soon as I have satisfied the user\'s query.';
} else {
return '';
}
}
}
module.exports = SelfReflectionTool;

View File

@@ -0,0 +1,88 @@
// 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');
class StableDiffusionAPI extends Tool {
constructor(fields) {
super();
this.name = 'stable-diffusion';
this.url = fields.SD_WEBUI_URL || this.getServerURL();
this.description = `You can generate images with 'stable-diffusion'. This tool is exclusively for visual content.
Guidelines:
- Visually describe the moods, details, structures, styles, and/or proportions of the image. Remember, the focus is on visual attributes.
- Craft your input by "showing" and not "telling" the imagery. Think in terms of what you'd want to see in a photograph or a painting.
- It's best to follow this format for image creation:
"detailed keywords to describe the subject, separated by comma | keywords we want to exclude from the final image"
- Here's an example prompt for generating a realistic portrait photo of a man:
"photo of a man in black clothes, half body, high detailed skin, coastline, overcast weather, wind, waves, 8k uhd, dslr, soft lighting, high quality, film grain, Fujifilm XT3 | semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, out of frame, low quality, ugly, mutation, deformed"
- Generate images only once per human query unless explicitly requested by the user`;
}
replaceNewLinesWithSpaces(inputString) {
return inputString.replace(/\r\n|\r|\n/g, ' ');
}
getMarkdownImageUrl(imageName) {
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
}
getServerURL() {
const url = process.env.SD_WEBUI_URL || '';
if (!url) {
throw new Error('Missing SD_WEBUI_URL environment variable.');
}
return url;
}
async _call(input) {
const url = this.url;
const payload = {
prompt: input.split('|')[0],
negative_prompt: input.split('|')[1],
steps: 20,
};
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;
// 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);
// Check if directory exists, if not create it
if (!fs.existsSync(this.outputPath)) {
fs.mkdirSync(this.outputPath, { recursive: true });
}
try {
const buffer = Buffer.from(image.split(',', 1)[0], 'base64');
await sharp(buffer)
.withMetadata({
iptcpng: {
parameters: info,
},
})
.toFile(this.outputPath + '/' + imageName);
this.result = this.getMarkdownImageUrl(imageName);
} catch (error) {
console.error('Error while saving the image:', error);
// this.result = theImageUrl;
}
return this.result;
}
}
module.exports = StableDiffusionAPI;

View File

@@ -0,0 +1,82 @@
/* eslint-disable no-useless-escape */
const axios = require('axios');
const { Tool } = require('langchain/tools');
class WolframAlphaAPI extends Tool {
constructor(fields) {
super();
this.name = 'wolfram';
this.apiKey = fields.WOLFRAM_APP_ID || this.getAppId();
this.description = `Access computation, math, curated knowledge & real-time data through wolframAlpha.
- Understands natural language queries about entities in chemistry, physics, geography, history, art, astronomy, and more.
- Performs mathematical calculations, date and unit conversions, formula solving, etc.
General guidelines:
- Make natural-language queries in English; translate non-English queries before sending, then respond in the original language.
- Inform users if information is not from wolfram.
- ALWAYS use this exponent notation: "6*10^14", NEVER "6e14".
- Your input must ONLY be a single-line string.
- ALWAYS use proper Markdown formatting for all math, scientific, and chemical formulas, symbols, etc.: '$$\n[expression]\n$$' for standalone cases and '\( [expression] \)' when inline.
- Format inline wolfram Language code with Markdown code formatting.
- Convert inputs to simplified keyword queries whenever possible (e.g. convert "how many people live in France" to "France population").
- Use ONLY single-letter variable names, with or without integer subscript (e.g., n, n1, n_1).
- Use named physical constants (e.g., 'speed of light') without numerical substitution.
- Include a space between compound units (e.g., "Ω m" for "ohm*meter").
- To solve for a variable in an equation with units, consider solving a corresponding equation without units; exclude counting units (e.g., books), include genuine units (e.g., kg).
- If data for multiple properties is needed, make separate calls for each property.
- If a wolfram Alpha result is not relevant to the query:
-- If wolfram provides multiple 'Assumptions' for a query, choose the more relevant one(s) without explaining the initial result. If you are unsure, ask the user to choose.
- Performs complex calculations, data analysis, plotting, data import, and information retrieval.`;
// - Please ensure your input is properly formatted for wolfram Alpha.
// -- Re-send the exact same 'input' with NO modifications, and add the 'assumption' parameter, formatted as a list, with the relevant values.
// -- ONLY simplify or rephrase the initial query if a more relevant 'Assumption' or other input suggestions are not provided.
// -- Do not explain each step unless user input is needed. Proceed directly to making a better input based on the available assumptions.
// - wolfram Language code is accepted, but accepts only syntactically correct wolfram Language code.
}
async fetchRawText(url) {
try {
const response = await axios.get(url, { responseType: 'text' });
return response.data;
} catch (error) {
console.error(`Error fetching raw text: ${error}`);
throw error;
}
}
getAppId() {
const appId = process.env.WOLFRAM_APP_ID || '';
if (!appId) {
throw new Error('Missing WOLFRAM_APP_ID environment variable.');
}
return appId;
}
createWolframAlphaURL(query) {
// Clean up query
const formattedQuery = query.replaceAll(/`/g, '').replaceAll(/\n/g, ' ');
const baseURL = 'https://www.wolframalpha.com/api/v1/llm-api';
const encodedQuery = encodeURIComponent(formattedQuery);
const appId = this.apiKey || this.getAppId();
const url = `${baseURL}?input=${encodedQuery}&appid=${appId}`;
return url;
}
async _call(input) {
try {
const url = this.createWolframAlphaURL(input);
const response = await this.fetchRawText(url);
return response;
} catch (error) {
if (error.response && error.response.data) {
console.log('Error data:', error.response.data);
return error.response.data;
} else {
console.log('Error querying Wolfram Alpha', error.message);
// throw error;
return 'There was an error querying Wolfram Alpha.';
}
}
}
}
module.exports = WolframAlphaAPI;

View File

@@ -0,0 +1,139 @@
require('dotenv').config();
const { z } = require('zod');
const fs = require('fs');
const yaml = require('js-yaml');
const path = require('path');
const { DynamicStructuredTool } = require('langchain/tools');
const { createOpenAPIChain } = require('langchain/chains');
const SUFFIX = 'Prioritize using responses for subsequent requests to better fulfill the query.';
const AuthBearer = z
.object({
type: z.string().includes('service_http'),
authorization_type: z.string().includes('bearer'),
verification_tokens: z.object({
openai: z.string(),
}),
})
.catch(() => false);
const AuthDefinition = z
.object({
type: z.string(),
authorization_type: z.string(),
verification_tokens: z.object({
openai: z.string(),
}),
})
.catch(() => false);
async function readSpecFile(filePath) {
try {
const fileContents = await fs.promises.readFile(filePath, 'utf8');
if (path.extname(filePath) === '.json') {
return JSON.parse(fileContents);
}
return yaml.load(fileContents);
} catch (e) {
console.error(e);
return false;
}
}
async function getSpec(url) {
const RegularUrl = z
.string()
.url()
.catch(() => false);
if (RegularUrl.parse(url) && path.extname(url) === '.json') {
const response = await fetch(url);
return await response.json();
}
const ValidSpecPath = z
.string()
.url()
.catch(async () => {
const spec = path.join(__dirname, '..', '.well-known', 'openapi', url);
if (!fs.existsSync(spec)) {
return false;
}
return await readSpecFile(spec);
});
return ValidSpecPath.parse(url);
}
async function createOpenAPIPlugin({ data, llm, user, message, verbose = false }) {
let spec;
try {
spec = await getSpec(data.api.url, verbose);
} catch (error) {
verbose && console.debug('getSpec error', error);
return null;
}
if (!spec) {
verbose && console.debug('No spec found');
return null;
}
const headers = {};
const { auth, description_for_model } = data;
if (auth && AuthDefinition.parse(auth)) {
verbose && console.debug('auth detected', auth);
const { openai } = auth.verification_tokens;
if (AuthBearer.parse(auth)) {
headers.authorization = `Bearer ${openai}`;
verbose && console.debug('added auth bearer', headers);
}
}
return new DynamicStructuredTool({
name: data.name_for_model,
description: `${data.description_for_human} ${SUFFIX}`,
schema: z.object({
query: z
.string()
.describe(
'For the query, be specific in a conversational manner. It will be interpreted by a human.',
),
}),
func: async () => {
const chainOptions = {
llm,
verbose,
};
if (data.headers && data.headers['librechat_user_id']) {
verbose && console.debug('id detected', headers);
headers[data.headers['librechat_user_id']] = user;
}
if (Object.keys(headers).length > 0) {
verbose && console.debug('headers detected', headers);
chainOptions.headers = headers;
}
if (data.params) {
verbose && console.debug('params detected', data.params);
chainOptions.params = data.params;
}
const chain = await createOpenAPIChain(spec, chainOptions);
const result = await chain.run(
`${message}\n\n||>Instructions: ${description_for_model}\n${SUFFIX}`,
);
console.log('api chain run result', result);
return result;
},
});
}
module.exports = {
getSpec,
readSpecFile,
createOpenAPIPlugin,
};

View File

@@ -0,0 +1,65 @@
const fs = require('fs');
const { createOpenAPIPlugin, getSpec, readSpecFile } = require('./OpenAPIPlugin');
jest.mock('node-fetch');
jest.mock('fs', () => ({
promises: {
readFile: jest.fn(),
},
existsSync: jest.fn(),
}));
describe('readSpecFile', () => {
it('reads JSON file correctly', async () => {
fs.promises.readFile.mockResolvedValue(JSON.stringify({ test: 'value' }));
const result = await readSpecFile('test.json');
expect(result).toEqual({ test: 'value' });
});
it('reads YAML file correctly', async () => {
fs.promises.readFile.mockResolvedValue('test: value');
const result = await readSpecFile('test.yaml');
expect(result).toEqual({ test: 'value' });
});
it('handles error correctly', async () => {
fs.promises.readFile.mockRejectedValue(new Error('test error'));
const result = await readSpecFile('test.json');
expect(result).toBe(false);
});
});
describe('getSpec', () => {
it('fetches spec from url correctly', async () => {
const parsedJson = await getSpec('https://www.instacart.com/.well-known/ai-plugin.json');
const isObject = typeof parsedJson === 'object';
expect(isObject).toEqual(true);
});
it('reads spec from file correctly', async () => {
fs.existsSync.mockReturnValue(true);
fs.promises.readFile.mockResolvedValue(JSON.stringify({ test: 'value' }));
const result = await getSpec('test.json');
expect(result).toEqual({ test: 'value' });
});
it('returns false when file does not exist', async () => {
fs.existsSync.mockReturnValue(false);
const result = await getSpec('test.json');
expect(result).toBe(false);
});
});
describe('createOpenAPIPlugin', () => {
it('returns null when getSpec throws an error', async () => {
const result = await createOpenAPIPlugin({ data: { api: { url: 'invalid' } } });
expect(result).toBe(null);
});
it('returns null when no spec is found', async () => {
const result = await createOpenAPIPlugin({});
expect(result).toBe(null);
});
// Add more tests here for different scenarios
});

View File

@@ -0,0 +1,23 @@
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 WolframAlphaAPI = require('./Wolfram');
const StructuredWolfram = require('./structured/Wolfram');
const SelfReflectionTool = require('./SelfReflection');
const availableTools = require('./manifest.json');
module.exports = {
availableTools,
GoogleSearchAPI,
HttpRequestTool,
AIPluginTool,
OpenAICreateImage,
StableDiffusionAPI,
StructuredSD,
WolframAlphaAPI,
StructuredWolfram,
SelfReflectionTool,
};

View File

@@ -0,0 +1,106 @@
[
{
"name": "Google",
"pluginKey": "google",
"description": "Use Google Search to find information about the weather, news, sports, and more.",
"icon": "https://i.imgur.com/SMmVkNB.png",
"authConfig": [
{
"authField": "GOOGLE_CSE_ID",
"label": "Google CSE ID",
"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",
"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>."
}
]
},
{
"name": "Wolfram",
"pluginKey": "wolfram",
"description": "Access computation, math, curated knowledge & real-time data through Wolfram|Alpha and Wolfram Language.",
"icon": "https://www.wolframcdn.com/images/icons/Wolfram.png",
"authConfig": [
{
"authField": "WOLFRAM_APP_ID",
"label": "Wolfram App ID",
"description": "An AppID must be supplied in all calls to the Wolfram|Alpha API. You can get one by registering at <a href='http://products.wolframalpha.com/api/'>Wolfram|Alpha</a> and going to the <a href='https://developer.wolframalpha.com/portal/myapps/'>Developer Portal</a>."
}
]
},
{
"name": "Browser",
"pluginKey": "web-browser",
"description": "Scrape and summarize webpage data",
"icon": "/assets/web-browser.svg",
"authConfig": [
{
"authField": "OPENAI_API_KEY",
"label": "OpenAI API Key",
"description": "Browser makes use of OpenAI embeddings"
}
]
},
{
"name": "Serpapi",
"pluginKey": "serpapi",
"description": "SerpApi is a real-time API to access search engine results.",
"icon": "https://i.imgur.com/5yQHUz4.png",
"authConfig": [
{
"authField": "SERPAPI_API_KEY",
"label": "Serpapi Private API Key",
"description": "Private Key for Serpapi. Register at <a href='https://serpapi.com/'>Serpapi</a> to obtain a private key."
}
]
},
{
"name": "DALL-E",
"pluginKey": "dall-e",
"description": "Create realistic images and art from a description in natural language",
"icon": "https://i.imgur.com/u2TzXzH.png",
"authConfig": [
{
"authField": "DALLE_API_KEY",
"label": "OpenAI API Key",
"description": "You can use DALL-E with your API Key from OpenAI."
}
]
},
{
"name": "Calculator",
"pluginKey": "calculator",
"description": "Perform simple and complex mathematical calculations.",
"icon": "https://i.imgur.com/RHsSG5h.png",
"isAuthRequired": "false",
"authConfig": []
},
{
"name": "Stable Diffusion",
"pluginKey": "stable-diffusion",
"description": "Generate photo-realistic images given any text input.",
"icon": "https://i.imgur.com/Yr466dp.png",
"authConfig": [
{
"authField": "SD_WEBUI_URL",
"label": "Your Stable Diffusion WebUI API URL",
"description": "You need to provide the URL of your Stable Diffusion WebUI API. For instructions on how to obtain this, see <a href='url'>Our Docs</a>."
}
]
},
{
"name": "Zapier",
"pluginKey": "zapier",
"description": "Interact with over 5,000+ apps like Google Sheets, Gmail, HubSpot, Salesforce, and thousands more.",
"icon": "https://cdn.zappy.app/8f853364f9b383d65b44e184e04689ed.png",
"authConfig": [
{
"authField": "ZAPIER_NLA_API_KEY",
"label": "Zapier API Key",
"description": "You can use Zapier with your API Key from Zapier."
}
]
}
]

View File

@@ -0,0 +1,39 @@
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,110 @@
// 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');
class StableDiffusionAPI extends StructuredTool {
constructor(fields) {
super();
this.name = 'stable-diffusion';
this.url = fields.SD_WEBUI_URL || this.getServerURL();
this.description = `You can generate images with 'stable-diffusion'. This tool is exclusively for visual content.
Guidelines:
- Visually describe the moods, details, structures, styles, and/or proportions of the image. Remember, the focus is on visual attributes.
- Craft your input by "showing" and not "telling" the imagery. Think in terms of what you'd want to see in a photograph or a painting.
- Here's an example for generating a realistic portrait photo of a man:
"prompt":"photo of a man in black clothes, half body, high detailed skin, coastline, overcast weather, wind, waves, 8k uhd, dslr, soft lighting, high quality, film grain, Fujifilm XT3"
"negative_prompt":"semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, out of frame, low quality, ugly, mutation, deformed"
- Generate images only once per human query unless explicitly requested by the user`;
this.schema = z.object({
prompt: z
.string()
.describe(
'Detailed keywords to describe the subject, using at least 7 keywords to accurately describe the image, separated by comma',
),
negative_prompt: z
.string()
.describe(
'Keywords we want to exclude from the final image, using at least 7 keywords to accurately describe the image, separated by comma',
),
});
}
replaceNewLinesWithSpaces(inputString) {
return inputString.replace(/\r\n|\r|\n/g, ' ');
}
getMarkdownImageUrl(imageName) {
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
}
getServerURL() {
const url = process.env.SD_WEBUI_URL || '';
if (!url) {
throw new Error('Missing SD_WEBUI_URL environment variable.');
}
return url;
}
async _call(data) {
const url = this.url;
const { prompt, negative_prompt } = data;
const payload = {
prompt,
negative_prompt,
steps: 20,
};
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;
// 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);
// Check if directory exists, if not create it
if (!fs.existsSync(this.outputPath)) {
fs.mkdirSync(this.outputPath, { recursive: true });
}
try {
const buffer = Buffer.from(image.split(',', 1)[0], 'base64');
await sharp(buffer)
.withMetadata({
iptcpng: {
parameters: info,
},
})
.toFile(this.outputPath + '/' + imageName);
this.result = this.getMarkdownImageUrl(imageName);
} catch (error) {
console.error('Error while saving the image:', error);
// this.result = theImageUrl;
}
return this.result;
}
}
module.exports = StableDiffusionAPI;

View File

@@ -0,0 +1,74 @@
/* eslint-disable no-useless-escape */
const axios = require('axios');
const { StructuredTool } = require('langchain/tools');
const { z } = require('zod');
class WolframAlphaAPI extends StructuredTool {
constructor(fields) {
super();
this.name = 'wolfram';
this.apiKey = fields.WOLFRAM_APP_ID || this.getAppId();
this.description = `WolframAlpha offers computation, math, curated knowledge, and real-time data. It handles natural language queries and performs complex calculations.
Guidelines include:
- Use English for queries and inform users if information isn't from Wolfram.
- Use "6*10^14" for exponent notation and single-line strings for input.
- Use Markdown for formulas and simplify queries to keywords.
- Use single-letter variable names and named physical constants.
- Include a space between compound units and consider equations without units when solving.
- Make separate calls for each property and choose relevant 'Assumptions' if results aren't relevant.
- The tool also performs data analysis, plotting, and information retrieval.`;
this.schema = z.object({
nl_query: z
.string()
.describe('Natural language query to WolframAlpha following the guidelines'),
});
}
async fetchRawText(url) {
try {
const response = await axios.get(url, { responseType: 'text' });
return response.data;
} catch (error) {
console.error(`Error fetching raw text: ${error}`);
throw error;
}
}
getAppId() {
const appId = process.env.WOLFRAM_APP_ID || '';
if (!appId) {
throw new Error('Missing WOLFRAM_APP_ID environment variable.');
}
return appId;
}
createWolframAlphaURL(query) {
// Clean up query
const formattedQuery = query.replaceAll(/`/g, '').replaceAll(/\n/g, ' ');
const baseURL = 'https://www.wolframalpha.com/api/v1/llm-api';
const encodedQuery = encodeURIComponent(formattedQuery);
const appId = this.apiKey || this.getAppId();
const url = `${baseURL}?input=${encodedQuery}&appid=${appId}`;
return url;
}
async _call(data) {
try {
const { nl_query } = data;
const url = this.createWolframAlphaURL(nl_query);
const response = await this.fetchRawText(url);
return response;
} catch (error) {
if (error.response && error.response.data) {
console.log('Error data:', error.response.data);
return error.response.data;
} else {
console.log('Error querying Wolfram Alpha', error.message);
// throw error;
return 'There was an error querying Wolfram Alpha.';
}
}
}
}
module.exports = WolframAlphaAPI;

View File

@@ -0,0 +1,31 @@
const { loadSpecs } = require('./loadSpecs');
function transformSpec(input) {
return {
name: input.name_for_human,
pluginKey: input.name_for_model,
description: input.description_for_human,
icon: input?.logo_url ?? 'https://placehold.co/70x70.png',
// TODO: add support for authentication
isAuthRequired: 'false',
authConfig: [],
};
}
async function addOpenAPISpecs(availableTools) {
try {
const specs = (await loadSpecs({})).map(transformSpec);
if (specs.length > 0) {
return [...specs, ...availableTools];
}
return availableTools;
} catch (error) {
console.log('addOpenAPISpecs error', error);
return availableTools;
}
}
module.exports = {
transformSpec,
addOpenAPISpecs,
};

View File

@@ -0,0 +1,76 @@
const { addOpenAPISpecs, transformSpec } = require('./addOpenAPISpecs');
const { loadSpecs } = require('./loadSpecs');
const { createOpenAPIPlugin } = require('../dynamic/OpenAPIPlugin');
jest.mock('./loadSpecs');
jest.mock('../dynamic/OpenAPIPlugin');
describe('transformSpec', () => {
it('should transform input spec to a desired format', () => {
const input = {
name_for_human: 'Human Name',
name_for_model: 'Model Name',
description_for_human: 'Human Description',
logo_url: 'https://example.com/logo.png',
};
const expectedOutput = {
name: 'Human Name',
pluginKey: 'Model Name',
description: 'Human Description',
icon: 'https://example.com/logo.png',
isAuthRequired: 'false',
authConfig: [],
};
expect(transformSpec(input)).toEqual(expectedOutput);
});
it('should use default icon if logo_url is not provided', () => {
const input = {
name_for_human: 'Human Name',
name_for_model: 'Model Name',
description_for_human: 'Human Description',
};
const expectedOutput = {
name: 'Human Name',
pluginKey: 'Model Name',
description: 'Human Description',
icon: 'https://placehold.co/70x70.png',
isAuthRequired: 'false',
authConfig: [],
};
expect(transformSpec(input)).toEqual(expectedOutput);
});
});
describe('addOpenAPISpecs', () => {
it('should add specs to available tools', async () => {
const availableTools = ['Tool1', 'Tool2'];
const specs = [
{
name_for_human: 'Human Name',
name_for_model: 'Model Name',
description_for_human: 'Human Description',
logo_url: 'https://example.com/logo.png',
},
];
loadSpecs.mockResolvedValue(specs);
createOpenAPIPlugin.mockReturnValue('Plugin');
const result = await addOpenAPISpecs(availableTools);
expect(result).toEqual([...specs.map(transformSpec), ...availableTools]);
});
it('should return available tools if specs loading fails', async () => {
const availableTools = ['Tool1', 'Tool2'];
loadSpecs.mockRejectedValue(new Error('Failed to load specs'));
const result = await addOpenAPISpecs(availableTools);
expect(result).toEqual(availableTools);
});
});

View File

@@ -0,0 +1,176 @@
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 {
availableTools,
AIPluginTool,
GoogleSearchAPI,
WolframAlphaAPI,
StructuredWolfram,
HttpRequestTool,
OpenAICreateImage,
StableDiffusionAPI,
StructuredSD,
} = require('../');
const { loadSpecs } = require('./loadSpecs');
const validateTools = async (user, tools = []) => {
try {
const validToolsSet = new Set(tools);
const availableToolsToValidate = availableTools.filter((tool) =>
validToolsSet.has(tool.pluginKey),
);
const validateCredentials = async (authField, toolName) => {
const adminAuth = process.env[authField];
if (adminAuth && adminAuth.length > 0) {
return;
}
const userAuth = await getUserPluginAuthValue(user, authField);
if (userAuth && userAuth.length > 0) {
return;
}
validToolsSet.delete(toolName);
};
for (const tool of availableToolsToValidate) {
if (!tool.authConfig || tool.authConfig.length === 0) {
continue;
}
for (const auth of tool.authConfig) {
await validateCredentials(auth.authField, tool.pluginKey);
}
}
return Array.from(validToolsSet.values());
} catch (err) {
console.log('There was a problem validating tools', err);
throw new Error(err);
}
};
const loadToolWithAuth = async (user, 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);
}
authValues[authField] = authValue;
}
return new ToolConstructor({ ...options, ...authValues });
};
};
const loadTools = async ({ user, model, functions = null, tools = [], options = {} }) => {
const toolConstructors = {
calculator: Calculator,
google: GoogleSearchAPI,
wolfram: functions ? StructuredWolfram : WolframAlphaAPI,
'dall-e': OpenAICreateImage,
'stable-diffusion': functions ? StructuredSD : StableDiffusionAPI,
};
const customConstructors = {
'web-browser': async () => {
let openAIApiKey = options.openAIApiKey ?? process.env.OPENAI_API_KEY;
openAIApiKey = openAIApiKey === 'user_provided' ? null : openAIApiKey;
openAIApiKey = openAIApiKey || (await getUserPluginAuthValue(user, 'OPENAI_API_KEY'));
return new WebBrowser({ model, embeddings: new OpenAIEmbeddings({ openAIApiKey }) });
},
serpapi: async () => {
let apiKey = process.env.SERPAPI_API_KEY;
if (!apiKey) {
apiKey = await getUserPluginAuthValue(user, 'SERPAPI_API_KEY');
}
return new SerpAPI(apiKey, {
location: 'Austin,Texas,United States',
hl: 'en',
gl: 'us',
});
},
zapier: async () => {
let apiKey = process.env.ZAPIER_NLA_API_KEY;
if (!apiKey) {
apiKey = await getUserPluginAuthValue(user, 'ZAPIER_NLA_API_KEY');
}
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 = {};
let specs = null;
if (functions) {
specs = await loadSpecs({
llm: model,
user,
message: options.message,
map: true,
verbose: options?.debug,
});
console.dir(specs, { depth: null });
}
const toolOptions = {
serpapi: { location: 'Austin,Texas,United States', hl: 'en', gl: 'us' },
};
const toolAuthFields = {};
availableTools.forEach((tool) => {
if (customConstructors[tool.pluginKey]) {
return;
}
toolAuthFields[tool.pluginKey] = tool.authConfig.map((auth) => auth.authField);
});
for (const tool of tools) {
if (customConstructors[tool]) {
requestedTools[tool] = customConstructors[tool];
continue;
}
if (specs && specs[tool]) {
requestedTools[tool] = specs[tool];
continue;
}
if (toolConstructors[tool]) {
const options = toolOptions[tool] || {};
const toolInstance = await loadToolWithAuth(
user,
toolAuthFields[tool],
toolConstructors[tool],
options,
);
requestedTools[tool] = toolInstance;
}
}
return requestedTools;
};
module.exports = {
validateTools,
loadTools,
};

View File

@@ -0,0 +1,196 @@
const mockUser = {
_id: 'fakeId',
save: jest.fn(),
findByIdAndDelete: jest.fn(),
};
var mockPluginService = {
updateUserPluginAuth: jest.fn(),
deleteUserPluginAuth: jest.fn(),
getUserPluginAuthValue: jest.fn(),
};
jest.mock('../../../../models/User', () => {
return function () {
return mockUser;
};
});
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('../');
describe('Tool Handlers', () => {
let fakeUser;
const pluginKey = 'dall-e';
const pluginKey2 = 'wolfram';
const initialTools = [pluginKey, pluginKey2];
const ToolClass = OpenAICreateImage;
const mockCredential = 'mock-credential';
const mainPlugin = availableTools.find((tool) => tool.pluginKey === pluginKey);
const authConfigs = mainPlugin.authConfig;
beforeAll(async () => {
mockUser.save.mockResolvedValue(undefined);
const userAuthValues = {};
mockPluginService.getUserPluginAuthValue.mockImplementation((userId, authField) => {
return userAuthValues[`${userId}-${authField}`];
});
mockPluginService.updateUserPluginAuth.mockImplementation(
(userId, authField, _pluginKey, credential) => {
userAuthValues[`${userId}-${authField}`] = credential;
},
);
fakeUser = new User({
name: 'Fake User',
username: 'fakeuser',
email: 'fakeuser@example.com',
emailVerified: false,
password: 'fakepassword123',
avatar: '',
provider: 'local',
role: 'USER',
googleId: null,
plugins: [],
refreshToken: [],
});
await fakeUser.save();
for (const authConfig of authConfigs) {
await PluginService.updateUserPluginAuth(
fakeUser._id,
authConfig.authField,
pluginKey,
mockCredential,
);
}
});
afterAll(async () => {
await mockUser.findByIdAndDelete(fakeUser._id);
for (const authConfig of authConfigs) {
await PluginService.deleteUserPluginAuth(fakeUser._id, authConfig.authField);
}
});
describe('validateTools', () => {
it('returns valid tools given input tools and user authentication', async () => {
const validTools = await validateTools(fakeUser._id, initialTools);
expect(validTools).toBeDefined();
console.log('validateTools: validTools', validTools);
expect(validTools.some((tool) => tool === pluginKey)).toBeTruthy();
expect(validTools.length).toBeGreaterThan(0);
});
it('removes tools without valid credentials from the validTools array', async () => {
const validTools = await validateTools(fakeUser._id, initialTools);
expect(validTools.some((tool) => tool.pluginKey === pluginKey2)).toBeFalsy();
});
it('returns an empty array when no authenticated tools are provided', async () => {
const validTools = await validateTools(fakeUser._id, []);
expect(validTools).toEqual([]);
});
it('should validate a tool from an Environment Variable', async () => {
const plugin = availableTools.find((tool) => tool.pluginKey === pluginKey2);
const authConfigs = plugin.authConfig;
for (const authConfig of authConfigs) {
process.env[authConfig.authField] = mockCredential;
}
const validTools = await validateTools(fakeUser._id, [pluginKey2]);
expect(validTools.length).toEqual(1);
for (const authConfig of authConfigs) {
delete process.env[authConfig.authField];
}
});
});
describe('loadTools', () => {
let toolFunctions;
let loadTool1;
let loadTool2;
let loadTool3;
const sampleTools = [...initialTools, 'calculator'];
let ToolClass2 = Calculator;
let remainingTools = availableTools.filter(
(tool) => sampleTools.indexOf(tool.pluginKey) === -1,
);
beforeAll(async () => {
toolFunctions = await loadTools({
user: fakeUser._id,
model: BaseChatModel,
tools: sampleTools,
});
loadTool1 = toolFunctions[sampleTools[0]];
loadTool2 = toolFunctions[sampleTools[1]];
loadTool3 = toolFunctions[sampleTools[2]];
});
it('returns the expected load functions for requested tools', async () => {
expect(loadTool1).toBeDefined();
expect(loadTool2).toBeDefined();
expect(loadTool3).toBeDefined();
for (const tool of remainingTools) {
expect(toolFunctions[tool.pluginKey]).toBeUndefined();
}
});
it('should initialize an authenticated tool or one without authentication', async () => {
const authTool = await loadTool1();
const tool = await loadTool3();
expect(authTool).toBeInstanceOf(ToolClass);
expect(tool).toBeInstanceOf(ToolClass2);
});
it('should throw an error for an unauthenticated tool', async () => {
try {
await loadTool2();
} catch (error) {
// eslint-disable-next-line jest/no-conditional-expect
expect(error).toBeDefined();
}
});
it('should initialize an authenticated tool through Environment Variables', async () => {
let testPluginKey = 'google';
let TestClass = GoogleSearchAPI;
const plugin = availableTools.find((tool) => tool.pluginKey === testPluginKey);
const authConfigs = plugin.authConfig;
for (const authConfig of authConfigs) {
process.env[authConfig.authField] = mockCredential;
}
toolFunctions = await loadTools({
user: fakeUser._id,
model: BaseChatModel,
tools: [testPluginKey],
});
const Tool = await toolFunctions[testPluginKey]();
expect(Tool).toBeInstanceOf(TestClass);
});
it('returns an empty object when no tools are requested', async () => {
toolFunctions = await loadTools({
user: fakeUser._id,
model: BaseChatModel,
});
expect(toolFunctions).toEqual({});
});
it('should return the StructuredTool version when using functions', async () => {
process.env.SD_WEBUI_URL = mockCredential;
toolFunctions = await loadTools({
user: fakeUser._id,
model: BaseChatModel,
tools: ['stable-diffusion'],
functions: true,
});
const structuredTool = await toolFunctions['stable-diffusion']();
expect(structuredTool).toBeInstanceOf(StructuredSD);
delete process.env.SD_WEBUI_URL;
});
});
});

View File

@@ -0,0 +1,6 @@
const { validateTools, loadTools } = require('./handleTools');
module.exports = {
validateTools,
loadTools,
};

View File

@@ -0,0 +1,104 @@
const fs = require('fs');
const path = require('path');
const { z } = require('zod');
const { createOpenAPIPlugin } = require('../dynamic/OpenAPIPlugin');
// The minimum Manifest definition
const ManifestDefinition = z.object({
schema_version: z.string().optional(),
name_for_human: z.string(),
name_for_model: z.string(),
description_for_human: z.string(),
description_for_model: z.string(),
auth: z.object({}).optional(),
api: z.object({
// Spec URL or can be the filename of the OpenAPI spec yaml file,
// located in api\app\clients\tools\.well-known\openapi
url: z.string(),
type: z.string().optional(),
is_user_authenticated: z.boolean().nullable().optional(),
has_user_authentication: z.boolean().nullable().optional(),
}),
// use to override any params that the LLM will consistently get wrong
params: z.object({}).optional(),
logo_url: z.string().optional(),
contact_email: z.string().optional(),
legal_info_url: z.string().optional(),
});
function validateJson(json, verbose = true) {
try {
return ManifestDefinition.parse(json);
} catch (error) {
if (verbose) {
console.debug('validateJson error', error);
}
return false;
}
}
// omit the LLM to return the well known jsons as objects
async function loadSpecs({ llm, user, message, map = false, verbose = false }) {
const directoryPath = path.join(__dirname, '..', '.well-known');
const files = (await fs.promises.readdir(directoryPath)).filter(
(file) => path.extname(file) === '.json',
);
const validJsons = [];
const constructorMap = {};
if (verbose) {
console.debug('files', files);
}
for (const file of files) {
if (path.extname(file) === '.json') {
const filePath = path.join(directoryPath, file);
const fileContent = await fs.promises.readFile(filePath, 'utf8');
const json = JSON.parse(fileContent);
if (!validateJson(json)) {
verbose && console.debug('Invalid json', json);
continue;
}
if (llm && map) {
constructorMap[json.name_for_model] = async () =>
await createOpenAPIPlugin({
data: json,
llm,
message,
user,
verbose,
});
continue;
}
if (llm) {
validJsons.push(createOpenAPIPlugin({ data: json, llm, verbose }));
continue;
}
validJsons.push(json);
}
}
if (map) {
return constructorMap;
}
const plugins = (await Promise.all(validJsons)).filter((plugin) => plugin);
// if (verbose) {
// console.debug('plugins', plugins);
// console.debug(plugins[0].name);
// }
return plugins;
}
module.exports = {
loadSpecs,
validateJson,
ManifestDefinition,
};

View File

@@ -0,0 +1,101 @@
const fs = require('fs');
const { validateJson, loadSpecs, ManifestDefinition } = require('./loadSpecs');
const { createOpenAPIPlugin } = require('../dynamic/OpenAPIPlugin');
jest.mock('../dynamic/OpenAPIPlugin');
describe('ManifestDefinition', () => {
it('should validate correct json', () => {
const json = {
name_for_human: 'Test',
name_for_model: 'Test',
description_for_human: 'Test',
description_for_model: 'Test',
api: {
url: 'http://test.com',
},
};
expect(() => ManifestDefinition.parse(json)).not.toThrow();
});
it('should not validate incorrect json', () => {
const json = {
name_for_human: 'Test',
name_for_model: 'Test',
description_for_human: 'Test',
description_for_model: 'Test',
api: {
url: 123, // incorrect type
},
};
expect(() => ManifestDefinition.parse(json)).toThrow();
});
});
describe('validateJson', () => {
it('should return parsed json if valid', () => {
const json = {
name_for_human: 'Test',
name_for_model: 'Test',
description_for_human: 'Test',
description_for_model: 'Test',
api: {
url: 'http://test.com',
},
};
expect(validateJson(json)).toEqual(json);
});
it('should return false if json is not valid', () => {
const json = {
name_for_human: 'Test',
name_for_model: 'Test',
description_for_human: 'Test',
description_for_model: 'Test',
api: {
url: 123, // incorrect type
},
};
expect(validateJson(json)).toEqual(false);
});
});
describe('loadSpecs', () => {
beforeEach(() => {
jest.spyOn(fs.promises, 'readdir').mockResolvedValue(['test.json']);
jest.spyOn(fs.promises, 'readFile').mockResolvedValue(
JSON.stringify({
name_for_human: 'Test',
name_for_model: 'Test',
description_for_human: 'Test',
description_for_model: 'Test',
api: {
url: 'http://test.com',
},
}),
);
createOpenAPIPlugin.mockResolvedValue({});
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should return plugins', async () => {
const plugins = await loadSpecs({ llm: true, verbose: false });
expect(plugins).toHaveLength(1);
expect(createOpenAPIPlugin).toHaveBeenCalledTimes(1);
});
it('should return constructorMap if map is true', async () => {
const plugins = await loadSpecs({ llm: {}, map: true, verbose: false });
expect(plugins).toHaveProperty('Test');
expect(createOpenAPIPlugin).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,60 @@
Certainly! Here is the text above:
\`\`\`
Assistant is a large language model trained by OpenAI.
Knowledge Cutoff: 2021-09
Current date: 2023-05-06
# Tools
## Wolfram
// Access dynamic computation and curated data from WolframAlpha and Wolfram Cloud.
General guidelines:
- Use only getWolframAlphaResults or getWolframCloudResults endpoints.
- Prefer getWolframAlphaResults unless Wolfram Language code should be evaluated.
- Use getWolframAlphaResults for natural-language queries in English; translate non-English queries before sending, then respond in the original language.
- Use getWolframCloudResults for problems solvable with Wolfram Language code.
- Suggest only Wolfram Language for external computation.
- Inform users if information is not from Wolfram endpoints.
- Display image URLs with Markdown syntax: ![URL]
- ALWAYS use this exponent notation: \`6*10^14\`, NEVER \`6e14\`.
- ALWAYS use {"input": query} structure for queries to Wolfram endpoints; \`query\` must ONLY be a single-line string.
- ALWAYS use proper Markdown formatting for all math, scientific, and chemical formulas, symbols, etc.: '$$\n[expression]\n$$' for standalone cases and '\( [expression] \)' when inline.
- Format inline Wolfram Language code with Markdown code formatting.
- Never mention your knowledge cutoff date; Wolfram may return more recent data.
getWolframAlphaResults guidelines:
- Understands natural language queries about entities in chemistry, physics, geography, history, art, astronomy, and more.
- Performs mathematical calculations, date and unit conversions, formula solving, etc.
- Convert inputs to simplified keyword queries whenever possible (e.g. convert "how many people live in France" to "France population").
- Use ONLY single-letter variable names, with or without integer subscript (e.g., n, n1, n_1).
- Use named physical constants (e.g., 'speed of light') without numerical substitution.
- Include a space between compound units (e.g., "Ω m" for "ohm*meter").
- To solve for a variable in an equation with units, consider solving a corresponding equation without units; exclude counting units (e.g., books), include genuine units (e.g., kg).
- If data for multiple properties is needed, make separate calls for each property.
- If a Wolfram Alpha result is not relevant to the query:
-- If Wolfram provides multiple 'Assumptions' for a query, choose the more relevant one(s) without explaining the initial result. If you are unsure, ask the user to choose.
-- Re-send the exact same 'input' with NO modifications, and add the 'assumption' parameter, formatted as a list, with the relevant values.
-- ONLY simplify or rephrase the initial query if a more relevant 'Assumption' or other input suggestions are not provided.
-- Do not explain each step unless user input is needed. Proceed directly to making a better API call based on the available assumptions.
- Wolfram Language code guidelines:
- Accepts only syntactically correct Wolfram Language code.
- Performs complex calculations, data analysis, plotting, data import, and information retrieval.
- Before writing code that uses Entity, EntityProperty, EntityClass, etc. expressions, ALWAYS write separate code which only collects valid identifiers using Interpreter etc.; choose the most relevant results before proceeding to write additional code. Examples:
-- Find the EntityType that represents countries: \`Interpreter["EntityType",AmbiguityFunction->All]["countries"]\`.
-- Find the Entity for the Empire State Building: \`Interpreter["Building",AmbiguityFunction->All]["empire state"]\`.
-- EntityClasses: Find the "Movie" entity class for Star Trek movies: \`Interpreter["MovieClass",AmbiguityFunction->All]["star trek"]\`.
-- Find EntityProperties associated with "weight" of "Element" entities: \`Interpreter[Restricted["EntityProperty", "Element"],AmbiguityFunction->All]["weight"]\`.
-- If all else fails, try to find any valid Wolfram Language representation of a given input: \`SemanticInterpretation["skyscrapers",_,Hold,AmbiguityFunction->All]\`.
-- Prefer direct use of entities of a given type to their corresponding typeData function (e.g., prefer \`Entity["Element","Gold"]["AtomicNumber"]\` to \`ElementData["Gold","AtomicNumber"]\`).
- When composing code:
-- Use batching techniques to retrieve data for multiple entities in a single call, if applicable.
-- Use Association to organize and manipulate data when appropriate.
-- Optimize code for performance and minimize the number of calls to external sources (e.g., the Wolfram Knowledgebase)
-- Use only camel case for variable names (e.g., variableName).
-- Use ONLY double quotes around all strings, including plot labels, etc. (e.g., \`PlotLegends -> {"sin(x)", "cos(x)", "tan(x)"}\`).
-- Avoid use of QuantityMagnitude.
-- If unevaluated Wolfram Language symbols appear in API results, use \`EntityValue[Entity["WolframLanguageSymbol",symbol],{"PlaintextUsage","Options"}]\` to validate or retrieve usage information for relevant symbols; \`symbol\` may be a list of symbols.
-- Apply Evaluate to complex expressions like integrals before plotting (e.g., \`Plot[Evaluate[Integrate[...]]]\`).
- Remove all comments and formatting from code passed to the "input" parameter; for example: instead of \`square[x_] := Module[{result},\n result = x^2 (* Calculate the square *)\n]\`, send \`square[x_]:=Module[{result},result=x^2]\`.
- In ALL responses that involve code, write ALL code in Wolfram Language; create Wolfram Language functions even if an implementation is already well known in another language.

Some files were not shown because too many files have changed in this diff Show More