Compare commits

..
841 Commits
Author SHA1 Message Date
Danny AvilaandGitHub bb06066b71 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
FuegovicandGitHub 29f346ca8e 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 OrlandoandGitHub 3067c45258 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 OrlandoandGitHub ba6546563e 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 AvilaandGitHub 1f922934db Update README.md 2023-05-16 09:48:54 -04:00
Danny AvilaandGitHub 9e4746d0c1 Update README.md 2023-05-16 06:53:56 -04:00
Danny AvilaandGitHub c0ef36fbeb Update README.md 2023-05-16 06:52:47 -04:00
Danny AvilaandGitHub cc41fe58bc fix(getIcon.jsx): match initial styling better with official (#277) 2023-05-15 12:15:33 -04:00
David ShinandGitHub 99c8325cde 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 ShinandGitHub f66f71ec25 Update GPT4 model icon (#274) 2023-05-15 10:08:30 -04:00
FuegovicandGitHub da190cfe49 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
Danny AvilaandGitHub 9714ca080f Release 0.4.4 (#271) 2023-05-14 20:39:40 -04:00
Danny AvilaandGitHub 5b51f8994b Fix react errors, max context tokens, and preset mobile view (#269)
* fix: react errors

* fix: max tokens issue

* fix: max tokens issue
2023-05-14 17:26:21 -04:00
Pawan KumarandGitHub 4cd09fe6ed fix code to adjust max_tokens according to model selection (#263) 2023-05-14 12:16:38 -04:00
Danny AvilaandGitHub 5dc910825a refactor(style.css): change font file paths (#268) 2023-05-14 12:12:56 -04:00
AnirudhandGitHub 05249a5b51 Fonts (#261) 2023-05-14 12:06:53 -04:00
Danny AvilaandGitHub 92a49c303b refactor(Conversation.js): change default pageSize from 12 to 14 in getConvosByPage and getConvosQueried functions. Remove unnecessary parentheses and curly braces in getConvosQueried function. Remove unnecessary parentheses in deleteConvos function. (#267) 2023-05-14 11:45:18 -04:00
bd28d73175 Add Popup Menu to Save Space in Sidebar (#260)
---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-05-14 11:42:17 -04:00
0374ae4cf3 Correct the typo in auth.json for accessing Google Palm (#266)
Co-authored-by: Anton Volnuhin <anton@volnuhin.ru>
2023-05-14 11:25:22 -04:00
d88e0fda7d Msg Clipboard to checkmark (optimistic UX) (#247)
* revert unintended package-lock.json change

* used default checkmark which is included in project

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-05-14 09:00:20 -04:00
Danny AvilaandGitHub 9cd5ba8e39 Release v0.4.3 2023-05-13 17:10:19 -04:00
Danny AvilaandGitHub 39357aa590 Release v0.4.3 2023-05-13 17:09:25 -04:00
Danny AvilaandGitHub 1745b939ad Release v0.4.3 2023-05-13 17:08:28 -04:00
Danny AvilaandGitHub 44ffd1ee30 chore: bump package versions to 0.4.3 (#265) 2023-05-13 16:59:45 -04:00
Danny AvilaandGitHub e7433cbb74 Feat: PaLM 2 (#262)
* feat(api): add googleapis package to package.json
feat(api): add reqDemo.js file to make a request to Google Cloud AI Platform API to get a response from a chatbot model.

* feat: add PaLM2 support

* feat(conversationPreset.js): add support for topP and topK for google endpoint
feat(askGoogle.js): add support for topP and topK for google endpoint
feat(ask/index.js): add google endpoint
feat(endpoints.js): add google endpoint
feat(MessageHeader.jsx): add support for modelLabel for google endpoint
feat(PresetItem.jsx): add support for modelLabel for google endpoint
feat(HoverButtons.jsx): add support for google endpoint
feat(createPayload.ts): add google endpoint
feat(types.ts): add google endpoint
feat(store/endpoints.js): add google endpoint
feat(cleanupPreset.js): add support for topP and topK for google endpoint
feat(getDefaultConversation.js): add support for topP and topK for google endpoint
feat(handleSubmit.js): add support for topP and topK for google endpoint

* fix: messages payload

* refactor(GoogleClient.js): set maxContextTokens based on isTextModel value
feat(GoogleClient.js): add delay option to TextStream constructor
feat(getIcon.jsx): add support for google endpoint and PaLM2 model label

* feat: palm frontend changes

* feat(askGoogle.js): set default example to empty input and output
feat(Examples.jsx): add ability to add and remove examples
refactor(Settings.jsx): remove examples from props and setOption function

style(GoogleOptions): remove unnecessary whitespace after Settings2 import
feat(GoogleOptions): add addExample and removeExample functions to manage examples
fix(cleanupPreset): set default example to [{ input: '', output: ''}]
fix(getDefaultConversation): set default example to [{ input: '', output: ''}]
fix(handleSubmit): set default example to [{ input: '', output: ''}]

* style(client): adjust height of settings and examples components to 350px
fix(client): fix path to palm.png image in getIcon.jsx file

* style(EndpointOptionsPopover.jsx, Examples.jsx, Settings.jsx): improve button styles and update input placeholders

* feat (palm): finalize examples on the frontend

* feat(GoogleClient.js): filter out empty examples in options
feat(GoogleClient.js): add support for promptPrefix in buildPayload method
feat(GoogleClient.js): add support for examples in buildPayload method
feat(conversationPreset.js): add maxOutputTokens field to conversation preset schema
feat(presetSchema.js): add examples field to preset schema
feat(askGoogle.js): add support for examples and promptPrefix in endpointOption
feat(EditPresetDialog.jsx): add Examples component for Google endpoint
feat(EditPresetDialog.jsx): add button to show/hide Examples component
feat(EditPresetDialog.jsx): add functionality to add, remove, and edit examples in Examples component
feat(EndpointOptionsDialog.jsx): change endpoint name to PaLM for Google endpoint
feat(Settings.jsx): add maxHeight prop to limit height of Settings component in EditPresetDialog and EndpointOptionsDialog

fix(Settings.jsx): add examples prop to ChatGPTBrowser component
fix(EndpointItem.jsx): add alternate name for google endpoint
fix(MessageHeader.jsx): change title for google endpoint to PaLM
feat(endpoints.js): add google endpoint to endpointsConfig
fix(cleanupPreset.js): add missing comma in examples array

* chore: change endpoint order

* feat(PaLM 2): complete for testing

* fix(PaLM): handle blocked messages
2023-05-13 16:29:06 -04:00
LaraClaraandGitHub 8427c1565b chore: NPM Workspaces and scripts (#244)
* chore: NPM Workspaces and scripts
- Allows everything to be run in the root directory

* chore:Update package-lock after workspace change

* docs: Minor docs typo fix
- most people run in dev mode, ie vite runs the server, this defaults to that method
2023-05-12 09:40:14 -04:00
Danny AvilaandGitHub 8c831b215b Release 0.4.2 2023-05-11 16:46:27 -04:00
Danny AvilaandGitHub 164aad12f4 Release 0.4.2 (#242)
* release: 0.4.2

* docs: update changelog and readme for v0.4.2 release

* docs(README.md): add important information about new user system and env variables
2023-05-11 16:39:44 -04:00
Danny AvilaandGitHub 676ab04d96 fix(docker): update .dockerignore to include client/.env file (#241)
fix(docker): add COPY command to copy client/.env file into the container before building
2023-05-11 15:59:43 -04:00
c88f0c43b6 Feature/logging system with pino and sanitization (#214) (#227)
* feat: Create structured data logging system with Pino

This commit creates a new feature that enables structured data logging using the Pino logging library. The structured data logging feature allows for more granular and customizable logging, making it easier to analyze and debug issues in the application.

The changes made in this commit include:
- Adding support for structured data logging using the Pino API
- Adding support to redact sensible data from logging output using pino
  redact.
- Pino integrate natively with fluentd, logstash, Docker Logging Drivers
  and other JSON based system.

* Add pino package to project

* Logging-System: Add support for an array of regex to redact

* Logging-Systems: Add Redact Patterns and Pino Redact Paths + Boolean logics wasn't right.

Co-authored-by: Olivier Contant <ocontant@users.noreply.github.com>
2023-05-10 23:59:26 -04:00
Danny AvilaandGitHub 037883d6ef refactor(titleConvo.js, endpoints.js): add support for AZURE_OPENAI_API_KEY environment variable (#235) 2023-05-10 23:56:24 -04:00
Danny AvilaandGitHub 3ce17b086c feat(chatgpt-client.js, titleConvo.js, genAzureEndpoints.js): add support for Azure OpenAI API endpoint generation (#234)
This commit adds support for generating Azure OpenAI API endpoints in the
`chatgpt-client.js` and `titleConvo.js` files. The `genAzureEndpoint` function
in `genAzureEndpoints.js` generates the endpoint URL based on the provided
parameters. The `chatgpt-client.js` and `titleConvo.js` files now use this
function to generate the endpoint URL when the `AZURE_OPENAI_API_KEY` environment
variable is set.
2023-05-10 23:47:26 -04:00
YouthandGitHub 73a5ea467d * refactor(getCitations.js): add null check for adaptiveCards variable before accessing its properties (#232) 2023-05-10 21:23:15 -04:00
FuegovicandGitHub ddfc4644ae Update README.md to v0.4.1 (#224)
* Update CONTRIBUTORS.md

* Update CHANGELOG.md

v0.4.1 Changelog

v0.4.1 changelog and link
Contributors in the TOC

* Update README.md

add a link to the alternative docs from @DavidDev1334

* Update linux_install.md

Credit @DavidDev1334 for linux install doc
2023-05-09 22:12:12 -04:00
Danny AvilaandGitHub 5ec8ebe94b chore: bump version to 0.4.1 in package.json files (#222)
feat: update docker-compose.yml to use latest image from docker hub
2023-05-09 17:57:04 -04:00
9a81578d8a Update Message.js (#191)
Fixed Error handling, Code duplication and Naming conventions. Contact me for more information at: DavidTheDev#0166

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
2023-05-09 17:51:39 -04:00
Danny AvilaandGitHub 761223aa7a Feat: Add Azure support (#219)
feat(api): add support for Azure OpenAI API

- Add Azure OpenAI API environment variables to .env.example
- Modify chatgpt-client.js to use Azure OpenAI API if environment variables are present
- Modify askOpenAI.js to use arrow function syntax
- Modify handlers.js to add console.log statement for partial variable
2023-05-09 17:42:55 -04:00
FuegovicandGitHub 480680864f update documentation structure (#220)
* documentation refactor

* Update README.md

* Delete README.MD.md

* Delete LOCAL_INSTALL.md

* Rename LICENSE.MD.md to LICENSE.MD

* Update LICENSE.md

* Delete LICENSE.MD

* Rename CONTRIBUTORS.MD.md to CONTRIBUTORS.md

* Rename CHANGELOG.MD.md to CHANGELOG.md

* new documents layout

* Update README.md

* Rename mac_install (1).md to mac_install.md

* Rename docker_install.md to docker_install.md

* Rename linux_install.md to linux_install.md

* Update and rename mac_install.md to mac_install.md

* Rename windows_install.md to windows_install.md

* Update docker_install.md

* Update linux_install.md

* Update mac_install.md

* Update windows_install.md

* Update windows_install.md

* Update linux_install.md

* Update tech_stack.md

* Update roadmap.md

* Update project_origin.md

* Update bing_jailbreak_info.md

* Update user_auth_system.md

* Update proxy.md

* Update google_search.md

* Update heroku.md

* Update testing.md

* Update pull_request_template.md

* Update documentation_guidelines.md

* Update contributor_guidelines.md

* Update code_of_conduct.md

* Update README.md

* Update README.md

* Update README.md

* Update roadmap.md

* Update tech_stack.md

* Update feature_request_template.md

* Update bug_report_template.md

* Update custom_issue_template.md

* Update README.md

fix redirect

* Update README.md

dynamic toc

* Update README.md

hide plugins section for now

* Update README.md

removed plugins from TOC

* Update README.md

* Update README.md

* Update documentation_guidelines.md

* Update documentation_guidelines.md

* Update documentation_guidelines.md

directives update

* Update README.md

update shortcut

* Update CHANGELOG.md

* Update roadmap.md

add public trello link

* Update linux_install.md
2023-05-09 13:47:14 -04:00
Danny AvilaandGitHub c2bd510274 chore: update docker image version to 0.4.0 (#218)
* chore(docker-compose.yml): update docker-compose file to use local node-api image build instead of docker hub image build

* chore(docker-compose.yml): update docker image version to 0.4.0
2023-05-08 18:57:30 -04:00
Dan OrlandoandGitHub 82a899cbfb fix issue with validation when google account has multiple spaces in name (#211) 2023-05-07 20:31:27 -04:00
Dan OrlandoandGitHub 764100026d fix browser refresh redirecting to /chat/new (#210) 2023-05-07 19:16:12 -04:00
FuegovicandGitHub 1730b0a0dd Update README.md (#209)
Fix typos in the google login setup instruction and added the update info in the update section of the readme
2023-05-07 19:11:00 -04:00
Dan OrlandoandGitHub 376f23b494 remove github-passport and update package.lock files (#208) 2023-05-07 16:22:13 -04:00
Dan OrlandoandGitHub 76ccf15a5b update user system section of readme (#207) 2023-05-07 15:51:18 -04:00
Dan OrlandoandGitHub 4c72d01388 Bump package version and fix spacing in user system section of readme (#206) 2023-05-07 15:27:04 -04:00
2bb98c9eba feat: Auth and User System (#205)
* server-side JWT auth implementation

* move oauth routes and strategies, fix bugs

* backend modifications for wiring up the frontend login and reg forms

* Add frontend data services for login and registration

* Add login and registration forms

* Implment auth context, functional client side auth

* protect routes with jwt auth

* finish local strategy (using local storage)

* Start setting up google auth

* disable token refresh, remove old auth middleware

* refactor client, add ApiErrorBoundary context

* disable google and facebook strategies

* fix: fix presets not displaying specific to user

* fix: fix issue with browser refresh

* fix: casing issue with User.js (#11)

* delete user.js to be renamed

* fix: fix casing issue with User.js

* comment out api error watcher temporarily

* fix: issue with api error watcher (#12)

* delete user.js to be renamed

* fix: fix casing issue with User.js

* comment out api error watcher temporarily

* feat: add google auth social login

* fix: make google login url dynamic based on dev/prod

* fix: bug where UI is briefly displayed before redirecting to login

* fix: fix cookie expires value for local auth

* Update README.md

* Update LOCAL_INSTALL structure

* Add local testing instructions

* Only load google strategy if client id and secret are provided

* Update .env.example files with new params

* fix issue with not redirecting to register form

* only show google login button if value is set in .env

* cleanup log messages

* Add label to button for google login on login form

* doc: fix client/server url values in .env.example

* feat: add error message details to registration failure

* Restore preventing paste on confirm password

* auto-login user after registering

* feat: forgot password (#24)

* make login/reg pages look like openai's

* add password reset data services

* new form designs similar to openai, add password reset pages

* add api's for password reset

* email utils for password reset

* remove bcrypt salt rounds from process.env

* refactor: restructure api auth code, consolidate routes (#25)

* add api's for password reset

* remove bcrypt salt rounds from process.env

* refactor: consolidate auth routes, use controller pattern

* refactor: code cleanup

* feat: migrate data to first user (#26)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes after refactor (#27)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes

* fix: issue with auto-login when logging out then logging in with new browser window (#28)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes

* fix: fix issue with auto-login in new tab

* doc: Update README and .env.example files with user system information (#29)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes

* fix: fix issue with auto-login in new tab

* doc: update README and .env.example files

* Fixup: LOCAL_INSTALL.md PS instructions (#200) (#30)

Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it>

* feat: send user with completion to protect against abuse (#31)

* Fixup: LOCAL_INSTALL.md PS instructions (#200)

* server-side JWT auth implementation

* move oauth routes and strategies, fix bugs

* backend modifications for wiring up the frontend login and reg forms

* Add frontend data services for login and registration

* Add login and registration forms

* Implment auth context, functional client side auth

* protect routes with jwt auth

* finish local strategy (using local storage)

* Start setting up google auth

* disable token refresh, remove old auth middleware

* refactor client, add ApiErrorBoundary context

* disable google and facebook strategies

* fix: fix presets not displaying specific to user

* fix: fix issue with browser refresh

* fix: casing issue with User.js (#11)

* delete user.js to be renamed

* fix: fix casing issue with User.js

* comment out api error watcher temporarily

* feat: add google auth social login

* fix: make google login url dynamic based on dev/prod

* fix: bug where UI is briefly displayed before redirecting to login

* fix: fix cookie expires value for local auth

* Only load google strategy if client id and secret are provided

* Update .env.example files with new params

* fix issue with not redirecting to register form

* only show google login button if value is set in .env

* cleanup log messages

* Add label to button for google login on login form

* doc: fix client/server url values in .env.example

* feat: add error message details to registration failure

* Restore preventing paste on confirm password

* auto-login user after registering

* feat: forgot password (#24)

* make login/reg pages look like openai's

* add password reset data services

* new form designs similar to openai, add password reset pages

* add api's for password reset

* email utils for password reset

* remove bcrypt salt rounds from process.env

* refactor: restructure api auth code, consolidate routes (#25)

* add api's for password reset

* remove bcrypt salt rounds from process.env

* refactor: consolidate auth routes, use controller pattern

* refactor: code cleanup

* feat: migrate data to first user (#26)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes after refactor (#27)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes

* fix: issue with auto-login when logging out then logging in with new browser window (#28)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes

* fix: fix issue with auto-login in new tab

* doc: Update README and .env.example files with user system information (#29)

* refactor: use /api for auth routes

* fix: use user id instead of username

* feat: migrate data to first user on register

* fix: fix social login routes

* fix: fix issue with auto-login in new tab

* doc: update README and .env.example files

* Send user id to openai to protect against abuse

* add meilisearch to gitignore

* Remove webpack

---------

Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it>

---------

Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Alfredo Fomitchenko <alfredo.fomitchenko@mail.polimi.it>
2023-05-07 10:04:51 -07:00
alfredo-fandGitHub 795a4ff4dc Fixup: LOCAL_INSTALL.md PS instructions (#200) 2023-05-03 07:16:38 -04:00
Danny AvilaandGitHub 5c65f39d4e Merge pull request #196 from alfredo-f/alfredo/playwright
Add instructions for local testing.
2023-04-30 06:54:45 -04:00
Alfredo Fomitchenko b119c9b8c4 Add local testing instructions 2023-04-30 08:53:30 +02:00
Danny AvilaandGitHub 56a8ac42c1 Merge pull request #195 from alfredo-f/alfredo/local_install
Update LOCAL_INSTALL structure
2023-04-29 16:34:22 -04:00
Alfredo Fomitchenko a72140ccd7 Update LOCAL_INSTALL structure 2023-04-29 21:13:33 +02:00
Danny AvilaandGitHub ffcf3cd1ee Update README.md 2023-04-28 08:15:35 -04:00
Danny AvilaandGitHub cb903d33d2 Merge pull request #188 from fuegovic/main
Added a link for the "automated installer"
2023-04-26 21:34:39 -04:00
FuegovicandGitHub 4c34cd752a Update README.md
layout fix for previous changes
2023-04-26 16:26:25 -04:00
FuegovicandGitHub cc26543c69 Update README.md
added information under the fuegovic's automated installer link
2023-04-26 16:24:37 -04:00
FuegovicandGitHub ea96b8029a Update LOCAL_INSTALL.md
Layout Updates
2023-04-26 16:21:36 -04:00
FuegovicandGitHub 77ee901fb9 Update README.md
added a link for fuegovic's automated installer
2023-04-26 16:17:46 -04:00
Danny AvilaandGitHub 7dd667c22b Merge pull request #187 from fuegovic/main
Update .env.example
2023-04-25 14:29:30 -04:00
FuegovicandGitHub f063851340 Update .env.example
Added clarification about the `user_provided` token use
2023-04-25 13:46:47 -04:00
FuegovicandGitHub 41fac2bb39 Update .env.example
added back the lines : # Set to "user_provided" to allow user provided token.
2023-04-25 13:09:24 -04:00
Danny AvilaandGitHub 5cbbbdc548 Merge pull request #186 from zhangsean/patch-1
Add container name
2023-04-25 10:00:09 -04:00
FuegovicandGitHub b0e3b1f9ae Update .env.example
I continued the work on the standardization of the layout. I also eliminated duplicate key=value pairs to simplify the configuration and reduce the likelihood of errors. I also updated some of the commented instructions and notes throughout the file to keep the instructions clear while making it easier to prevent errors when using a script to parse the key=value pairs.
2023-04-25 04:26:38 -04:00
Sean ZhangandGitHub d0fef7a360 Fix container name 2023-04-25 10:14:13 +08:00
Sean ZhangandGitHub 763e72a590 Add container name 2023-04-25 10:04:38 +08:00
Danny AvilaandGitHub 8fb19497aa Merge pull request #183 from danny-avila/fix-edit-wrap
add whitespace-pre-wrap to the message editor to preserve line breaks
2023-04-21 23:13:42 -04:00
Daniel Avila 1bd34188d5 style(Message.jsx): add whitespace-pre-wrap to the message editor to preserve line breaks 2023-04-21 23:12:45 -04:00
Danny Avila e60fd333d5 build(Dockerfile-app): add Dockerfile for building the app image
feat(docker-compose.yml): use pre-built image for api service instead of building from local file
2023-04-21 14:38:23 -04:00
Daniel Avila 7eb9217c8a fix(chatgpt-browser.js): update public reverse proxy URL 2023-04-20 13:13:47 -04:00
Danny AvilaandGitHub 73c154320d Update README.md 2023-04-16 23:12:59 -07:00
Danny AvilaandGitHub ea579e7eb9 Update README.md 2023-04-14 18:11:20 -07:00
Daniel Avila 1c024662fe feat(api): update @waylaidwanderer/chatgpt-api dependency to version 1.35.0 2023-04-12 05:47:10 -07:00
Danny AvilaandGitHub 4be33a9b66 Merge pull request #179 from danny-avila/fix-unknown-conversationId
fix: fix infinite query failure when conversationId is not found
2023-04-11 19:52:42 -06:00
Daniel Avila 69078eb8cf fix(api): correct typo in environment variable name from "user_provide" to "user_provided" in bingai.js and chatgpt-browser.js clients and endpoints.js router 2023-04-11 21:50:53 -04:00
Danny AvilaandGitHub a05f7f8579 Merge pull request #177 from danny-avila/user-providered-key
User providered key and unfinished messages.
2023-04-11 19:46:28 -06:00
Daniel Avila f39bdf30ea feat(Input/NewConversationMenu): save last selected model to localStorage
fix(getDefaultConversation.js): use last selected model from localStorage if available
2023-04-11 21:33:14 -04:00
Daniel Avila cf186c6490 refactor(tokenizer.js): remove console.log statement from tokenizer.js 2023-04-11 10:29:55 -04:00
Daniel Avila d342b6220a style(endpoints.js): fix indentation and add semicolons
fix(tokenizer.js): add try-catch block and error handling
style(SetTokenDialog/index.jsx): fix typo in sentence
refactor(data-service.ts): change argument format to match server API
2023-04-11 10:28:11 -04:00
Daniel Avila 2eca77f321 fix: only give initialResponse unfinished true value when not a cancellable endpoint 2023-04-10 18:27:06 -04:00
Daniel D Orlando 05d9f1553c fix: fix infinite query failure when conversationId is not found 2023-04-10 14:55:39 -07:00
Daniel Avila 8529d38345 refactor(Message.jsx): remove cancelled message bubble and improve wording of unfinished message 2023-04-10 17:15:28 -04:00
Daniel Avila 8d74b5b628 chore: bump version to 0.3.3 in package.json files 2023-04-10 17:06:49 -04:00
Daniel Avila e08eccaac7 fix(Input): fix isNotAppendable condition to include isSubmitting variable
fix(Input): prevent submitting message when isSubmitting is true and Enter key is pressed
2023-04-10 17:03:11 -04:00
JinruiandWentao Lyu e05a4f31c5 fix nginx container copying from wrong folder 2023-04-11 03:51:56 +08:00
Wentao Lyu fde919ec3f fix: typo 2023-04-11 03:50:37 +08:00
Wentao Lyu b878fb6628 mark initial response as unfinished. 2023-04-11 03:42:05 +08:00
Wentao Lyu 9bdb24f941 cleanup 2023-04-11 03:29:54 +08:00
Wentao Lyu cc6ec32220 merge from dannya
feat: support unfinished messages.
2023-04-11 03:26:38 +08:00
Wentao Lyu fc115e85be feat: support user-provided token to bingAI and chatgptBrowser 2023-04-10 14:51:38 +08:00
Daniel Avila 94a25ed149 wip: feat: abort messages and continue conversation
fix(addToCache.js): remove unused variables and parameters
feat(addToCache.js): add message to cache with id, parentMessageId, role, and text
fix(askOpenAI.js): remove parentMessageId parameter from addToCache call
feat(MessageHandler.jsx): add latestMessage to store on cancel of submission, and generate messageId and parentMessageId for latestMessage
2023-04-09 22:21:27 -04:00
Daniel Avila 4fd99422a8 feat(api): add support for saving messages to database
fix(api): change arrowParens prettier option to always
fix(api): update addToCache to include endpointOption and latestMessage
fix(api): update askOpenAI to include endpointOption in abortControllers
fix(client): remove abortKey state and add currentParent state to MessageHandler
2023-04-09 11:17:08 -04:00
Daniel Avila f4cfe40ceb wip: refactor: new abort message handling 2023-04-09 09:23:03 -04:00
Daniel Avila 2a70a7ff65 refactor(messageHandler): sends all necessary data to cache/save unfinished response 2023-04-09 09:22:14 -04:00
Daniel Avila 6cdc1d3ee6 feat(api-endpoints.ts): add abortRequest endpoint
feat(data-service.ts): add abortRequestWithMessage function
feat(react-query-service.ts): add useAbortRequestWithMessage hook
2023-04-09 09:21:04 -04:00
Daniel Avila 02e6a89ebd Merge branch 'main' into fix-sse 2023-04-09 07:54:06 -04:00
Daniel Avila 0bd9450572 fix(Messages/index.jsx): import lodash throttle function efficiently 2023-04-09 07:52:42 -04:00
Daniel Avila 33442b9f73 chore(package-lock.json): remove duplicate media-typer dependency and update its version to 0.3.0 in type-is and express dependencies. 2023-04-09 07:47:08 -04:00
Daniel Avila 120ab4afe4 Merge branch 'main' into fix-sse 2023-04-09 07:41:55 -04:00
Danny AvilaandGitHub 0484973ed6 Update README.md 2023-04-09 07:40:03 -04:00
Danny AvilaandGitHub d913edfe23 Update README.md 2023-04-09 07:39:41 -04:00
Danny AvilaandGitHub 46819ef1e8 Merge pull request #176 from danny-avila/minor-fixes
Minor fixes
2023-04-09 07:38:28 -04:00
Daniel Avila 50ec165f71 chore: update package versions to 0.3.2 2023-04-09 07:37:57 -04:00
Daniel Avila 942a85f531 fix: vite build issue: set memory limit for Node.js to 2048 MB 2023-04-09 07:35:10 -04:00
Daniel Avila 83d0443c8a fix: page no longer refreshes on stop generating button 2023-04-09 07:32:07 -04:00
Daniel Avila 88aea81288 WIP: fix: fix abort messages and continue conversation on abort
feat(askOpenAI.js): add abort endpoint to cancel requests
feat(MessageHandler): add abort functionality to cancel requests
feat(submission.js): add lastResponse and source atoms to store
feat(handleSubmit.js): add stopGenerating function to cancel requests
2023-04-08 23:19:29 -04:00
Danny AvilaandGitHub 5fbefa15ce Merge pull request #171 from git-bruh/main
fix: replace various anchor tags with buttons to prevent text selection on repeated clicks
2023-04-08 08:38:41 -04:00
Daniel Avila 0f62be812a refactor(AdjustToneButton.jsx): replace svg icon with lucide-react Settings2 icon 2023-04-08 08:37:44 -04:00
Danny AvilaandGitHub 92c1bb6511 Merge pull request #174 from HyunggyuJang/fix/reset-after-title-generation
Fix #119
2023-04-08 08:32:13 -04:00
Danny AvilaandGitHub cb56b74b0e Merge pull request #160 from HyunggyuJang/sydney/tone-adjustable
Make sydney tone adjustable during conversation
2023-04-08 08:30:05 -04:00
Daniel Avila 94078ce5d4 chore: remove GA until more tests are written 2023-04-08 08:21:34 -04:00
Hyunggyu Jang e5e4ee2987 Fix error 2023-04-08 13:32:15 +09:00
Hyunggyu Jang 100be3b42f Make sydney tone adjustable 2023-04-08 13:29:08 +09:00
Danny AvilaandGitHub 0a80f836f0 Merge pull request #165 from danny-avila/dano/react-query-typescript
Refactor: Create data-provider for api services with React Query and TypeScript
2023-04-07 22:14:44 -04:00
Daniel Avila 8b952be268 refactor(NewConversationMenu): remove unused imports and commented code
feat(NewConversationMenu): add support for deleting presets and creating new presets using data-provider
2023-04-07 22:11:28 -04:00
Daniel Avila 285351bb53 Merge branch 'main' into dano/react-query-typescript 2023-04-07 22:11:15 -04:00
Danny AvilaandGitHub 625f63b072 Merge pull request #173 from danny-avila/feat-new-titleconvo
Feat new titleconvo
2023-04-07 20:50:14 -04:00
Daniel D Orlando bf4258c0a5 Add react query devtools 2023-04-07 16:13:00 -07:00
Daniel D Orlando 9a0e3804fa fix nav pagination 2023-04-07 10:16:53 -07:00
Wentao Lyu 90946011f7 fix: update titleConvo to use same title protocal as node-chatgpt-api 2023-04-08 00:36:58 +08:00
Wentao Lyu 06b90f6a77 feat: add host params to bingAI.
[but seems not work in China]
2023-04-08 00:14:44 +08:00
Wentao Lyu 96b004a696 feat: add animation to New Topic. 2023-04-08 00:14:15 +08:00
Wentao Lyu 9623fe2e9f clean code with newConversationId in askbingai
Revert "Merge pull request #167 from danny-avila/fix-sydney"

This reverts commit 15999bda95, reversing
changes made to e1c6517b8f.
2023-04-07 23:13:13 +08:00
Danny AvilaandGitHub d79d297441 Merge pull request #172 from danny-avila/dano/fix-vite-memory-allocation-issue
Fix: fix javascript heap out of memory error from vite
2023-04-07 10:44:48 -04:00
Daniel D Orlando fd5fba45e6 remove unnecessary code 2023-04-07 07:36:28 -07:00
Daniel D Orlando 6e42d4fa3d Fix: fix javascript heap out of memory error from vite 2023-04-07 07:27:05 -07:00
Danny AvilaandGitHub 5ea8f75f70 Merge branch 'main' into dano/react-query-typescript 2023-04-07 10:19:13 -04:00
Danny AvilaandGitHub 7ec90a3585 Merge pull request #168 from danny-avila/feat-export-convo
fix: remove use-screenshot
2023-04-07 10:15:43 -04:00
git-bruh fc91ed49bc fix: replace various anchor tags with buttons to prevent text selection on repeated clicks 2023-04-07 19:25:30 +05:30
Danny AvilaandGitHub 6ce1b9d850 Update README.md 2023-04-07 09:42:36 -04:00
Daniel D Orlando c983670b9e Fix bug - when clicking on title in search results, was not switching to conversation 2023-04-07 06:02:28 -07:00
Daniel D Orlando e0f9e92bfc fix bad setState warning in console 2023-04-07 05:20:14 -07:00
Wentao Lyu ca720efde8 fix: remove use-screenshot
feat: displat file type
2023-04-07 13:21:20 +08:00
Danny AvilaandGitHub 34c3663308 Update README.md 2023-04-07 00:06:05 -04:00
Danny AvilaandGitHub 15999bda95 Merge pull request #167 from danny-avila/fix-sydney
fix: undo use of newConversationId which broke sydney
2023-04-06 20:26:29 -04:00
Daniel Avila 6a77401978 fix: undo commit 3b94a98 which broke sydney 2023-04-06 20:25:28 -04:00
Danny AvilaandGitHub 644ff160fc Merge branch 'main' into dano/react-query-typescript 2023-04-06 19:47:36 -04:00
Danny AvilaandGitHub e1c6517b8f Merge pull request #164 from danny-avila/workflow
ci(playwright.yml): add Playwright tests workflow
2023-04-06 19:43:36 -04:00
Daniel Avila 8a243e12fb chore(landing.spec.js): comment out title expectation and update landing title text 2023-04-06 19:39:19 -04:00
Daniel Avila 3f42db4956 chore(playwright.yml): move 'Start API server' job to after 'Install Playwright Browsers' job
fix(landing.spec.js): change page.goto URL to 'http://localhost:3080/'
2023-04-06 19:33:33 -04:00
Daniel Avila 323e951d7f ci(playwright.yml): add environment variables for secrets BINGAI_TOKEN, CHATGPT_TOKEN, MONGO_URI, and OPENAI_KEY 2023-04-06 19:26:25 -04:00
Danny AvilaandGitHub 11e4928582 Merge pull request #166 from danny-avila/fix-format
fix: formatting for user messages
2023-04-06 18:59:40 -04:00
Daniel Avila 24cb6d4013 fix: formatting for user messages 2023-04-06 18:58:56 -04:00
Daniel Avila 19183678a3 build(playwright.yml): add caching for API and Client dependencies
Add caching for API and Client dependencies to speed up the build process.
2023-04-06 18:52:13 -04:00
Daniel D Orlando dccd766d91 remove unused import 2023-04-06 15:50:15 -07:00
Daniel D Orlando d24abf6a2a Cleanup App.jsx 2023-04-06 15:42:39 -07:00
Daniel Avila 6b1b0f967d build(playwright.yml): add API and Client dependencies installation and server start before running tests
feat(playwright.yml): add global dependencies installation before running tests
2023-04-06 18:38:12 -04:00
Daniel Avila a56c8696d3 fix(playwright.config.js): correct baseURL typo from 'http:/localhost:3080' to 'http://localhost:3080' 2023-04-06 18:31:02 -04:00
Daniel D Orlando 7b7ba96786 Move createPayload and sse to data-provider, create TSubmission type 2023-04-06 14:56:33 -07:00
Daniel D Orlando 0fb9820110 change fetchById to call getConversationById 2023-04-06 14:56:33 -07:00
Daniel D Orlando c271f044c7 remove swr 2023-04-06 14:56:33 -07:00
Daniel D Orlando ec2ddc168b delete fetchers.js 2023-04-06 14:56:33 -07:00
Daniel D Orlando 0d5b51ec8c use create preset mutation for preset import 2023-04-06 14:56:33 -07:00
Daniel D Orlando 06a7ed31ac remove unused endpoints 2023-04-06 14:56:33 -07:00
Daniel D Orlando 4eff1c03dd package.lock 2023-04-06 14:56:33 -07:00
Daniel D Orlando 83df28f45d add RQ tokenizer 2023-04-06 14:56:33 -07:00
Daniel D Orlando 48e33fe1e9 Add support for deleting individual presets 2023-04-06 14:56:33 -07:00
Daniel D Orlando fbeff7a461 Code cleanup 2023-04-06 14:56:33 -07:00
Daniel D Orlando 61cb2858bb refactor and optimize search, add RQ for search 2023-04-06 14:56:33 -07:00
Daniel D Orlando 3d0bfaef51 Add presets and endpoints data services 2023-04-06 14:56:33 -07:00
Daniel D Orlando f2d18c81fc add deletePresetMutation to NewConverationMenu 2023-04-06 14:56:33 -07:00
Daniel D Orlando 68041d68ae fix types 2023-04-06 14:56:33 -07:00
Daniel D Orlando 93b685a1a2 change endpoints.ts to api-endpoints.ts 2023-04-06 14:56:33 -07:00
Daniel D Orlando 9e708225aa Add preset mutation 2023-04-06 14:56:33 -07:00
Daniel D Orlando 1cb8ef9803 feat: convert Chat.jsx to RQ 2023-04-06 14:56:00 -07:00
Daniel D Orlando 573112de7b fix: fix conversations in nav (put refreshConvoHint back) 2023-04-06 14:56:00 -07:00
Daniel D Orlando dd0a91a9f6 add RQ to clear all conversations 2023-04-06 14:56:00 -07:00
Daniel D Orlando 94e0636b32 add delete conversation mutation, fix withAuthentication on post requests 2023-04-06 14:56:00 -07:00
Daniel D Orlando bd53b878d4 update react-query-service 2023-04-06 14:56:00 -07:00
Daniel D Orlando c6d3bcd457 feat: Add RQ to Conversation component, create temp Chat component with RQ for compare and debugging 2023-04-06 14:56:00 -07:00
Daniel D Orlando 39f53e6ddf add QueryClientProvider to main 2023-04-06 14:56:00 -07:00
Daniel D Orlando 10941bf623 add DS_Store to gitignore 2023-04-06 14:56:00 -07:00
Daniel D Orlando 8c392ac05e build: add react query 2023-04-06 14:56:00 -07:00
Daniel D Orlando 9dae1ade60 turn off no-debugger in eslintrc for debugginer purposes 2023-04-06 14:55:26 -07:00
Daniel D Orlando ccc2f392e2 feat: add conversation query to nav 2023-04-06 14:55:26 -07:00
Daniel D Orlando 2048e34311 feat: add new data services to App.jsx 2023-04-06 14:55:26 -07:00
Daniel D Orlando 2589754171 feat: add data-provider 2023-04-06 14:54:37 -07:00
Daniel Avila 4510f04073 feat(playwright.yml): add GitHub Actions workflow for running Playwright tests on push and pull request events on main and master branches 2023-04-06 17:34:47 -04:00
Daniel Avila e98ce09d6b ci(playwright.yml): add Playwright tests workflow
fix(Landing.jsx): add id attribute to landing page title
test(landing.spec.js): update landing page title and h1 text content assertions
2023-04-06 17:33:02 -04:00
Danny AvilaandGitHub 21920dd864 Merge pull request #155 from danny-avila/feat-export-convo
Feature support export conversation
2023-04-06 17:11:59 -04:00
Daniel Avila 7d45d229af refactor(PresetItem.jsx): swap order of the edit and delete preset buttons 2023-04-06 17:07:44 -04:00
Daniel Avila 5dad9da918 chore(docker-compose.yml): instructions for CHATGPT_REVERSE_PROXY .env variable 2023-04-06 17:06:42 -04:00
Daniel Avila e0b0b68346 feat(Conversation.jsx): set document title when conversation is switched 2023-04-06 16:52:05 -04:00
Danny Avila 31cef16cc3 fix: fileName formatting fixes 2023-04-06 16:18:36 -04:00
Danny Avila 4245b43140 fix: used forked repo of use-react-screenshot for dep updates 2023-04-06 16:00:05 -04:00
Wentao Lyu 5664a0c2a5 fix: remove blank in screenshot 2023-04-07 02:00:51 +08:00
Danny AvilaandGitHub dde6de6bd5 Merge pull request #163 from danny-avila/fix-markdown
fix: markdown formatting errors
2023-04-06 13:55:04 -04:00
Danny Avila e077f2b73d refactor(askChatGPTBrowser.js): remove unused titleConvo import
style(Message.jsx, style.css): adjust margins and paddings to improve readability
2023-04-06 13:53:19 -04:00
Wentao Lyu 6e8a0a2f94 fix: didnt use preset from a exist convo 2023-04-07 01:49:28 +08:00
Wentao Lyu 96914387a6 feat: export to screenshot 2023-04-07 00:05:07 +08:00
Wentao Lyu 6f0b559927 feat: export conversation: csv, json, txt, markdown 2023-04-07 00:05:07 +08:00
Wentao Lyu 3b94a98719 fix: use new conversation Id 2023-04-07 00:05:07 +08:00
Daniel Avila 017447b064 chore(api): update chatgpt-api dependency to version 1.34.0
feat(api): use gen_title response for askChatGPTBrowser.js (official title)
2023-04-05 21:34:39 -04:00
Danny AvilaandGitHub 385eb2f398 Update README.md 2023-04-05 17:09:04 -04:00
Danny AvilaandGitHub 963939fe76 Update README.md 2023-04-05 17:02:00 -04:00
Danny AvilaandGitHub cf3902567e Update README.md 2023-04-05 16:58:54 -04:00
Danny AvilaandGitHub b4d451d7ae Merge pull request #142 from danny-avila/feat-endpoint-style-structure
Endpoint style structure and more customize of all model.
2023-04-05 15:07:27 -04:00
Danny Avila 97e39b8203 Merge branch 'main' into feat-endpoint-style-structure 2023-04-05 14:06:37 -04:00
Danny Avila 60f51dfeeb fix: remove messageHeader click when endpoint is browser 2023-04-05 14:05:54 -04:00
Wentao Lyu e7a6589958 fix: lost model of browser 2023-04-06 01:55:28 +08:00
Danny Avila d4cd9411c0 feat: add delete for presets in menu
[200~refactor(presets.js): remove unused getPreset function
refactor(presets.js): use arrow function syntax for map callback
refactor(presets.js): add console.log for debugging purposes
refactor(presets.js): simplify map callback syntax
refactor(presets.js): remove commented out code
refactor(FileUpload.jsx): remove commented out code
refactor(NewConversationMenu.jsx): rename data parameter to res for
clarity
refactor(NewConversationMenu.jsx): rename clearPresetsTrigger to
deletePresetsTrigger for clarity
refactor(NewConversationMenu.jsx): add onDeletePreset prop to
PresetItems component
refactor(PresetItem.jsx): add TrashIcon component and onDeletePreset
prop
refactor(PresetItems.jsx): add onDeletePreset prop to PresetItem
component
2023-04-05 13:21:29 -04:00
Danny Avila e1c731299c fix(ui): change SelectDropdown to SelectDropDown in multiple files 2023-04-05 12:00:52 -04:00
Danny Avila 214067cfcb feat(client): add doubleClickHandler prop to Slider component to reset to default
feat(client): add doubleClickHandler to temperature, topP, freqP, and presP sliders in OpenAI Settings component
2023-04-05 11:28:20 -04:00
Danny Avila 7d3df376ef style(Settings.jsx): add openDelay prop for faster HoverCard views 2023-04-05 10:29:06 -04:00
Wentao Lyu 474ced1dbe fix: typo in askBingAI.js 2023-04-05 22:13:38 +08:00
Danny Avila 55e949578f style(MessageHeader.jsx): add hover opacity to the background color of the message header 2023-04-05 10:03:52 -04:00
Danny Avila b730d631af feat(client): add rc-input-number package to dependencies
feat(client): add classnames package to dependencies
2023-04-05 09:41:46 -04:00
Wentao Lyu 03cade8bd5 fix: input style
fix: use ChatGptBrowser
2023-04-05 21:21:31 +08:00
Wentao Lyu 22b9524ad3 feat: update env example.
feat: support OPENAI_REVERSE_PROXY
feat: support set availModels in env file

fix: chatgpt Browser send logic refactor.

fix: title wrong usage of responseMessage
BREAKING: some env paramaters has been changed!
2023-04-05 21:21:02 +08:00
Wentao Lyu a5202f84cc fix: force reset to default if 0 or false 2023-04-05 17:25:35 +08:00
Wentao Lyu 4b373ebc0e feat: range number input in options. 2023-04-05 17:14:05 +08:00
Wentao Lyu 0bc2db08c7 style: options in mobile 2023-04-05 16:27:11 +08:00
Wentao Lyu a641a0e373 fix: dont clear the input message if conversationId changed. 2023-04-05 16:16:26 +08:00
Wentao Lyu 6abe34ee3b fix: validate the import preset 2023-04-05 16:16:11 +08:00
Wentao Lyu 8c2d577e60 feat: remove customGpts 2023-04-05 16:15:46 +08:00
Daniel Avila 5aa6b516ed feat: Import Presets from NewConversationMenu
feat(FileUpload.jsx): add support for importing JSON files and fetching presets after import
feat(NewConversationMenu.jsx): add FileUpload component to select and import JSON files
feat(fetchers.js): add handleFileSelected function to handle importing JSON files and uploading presets to the server
2023-04-04 23:18:58 -04:00
Daniel Avila 0846aa0436 docs(bingai.js): remove commented example response and add reference to demo file 2023-04-04 21:00:04 -04:00
Daniel Avila 3ab9b7f736 refactor(askOpenAI.js): rename gptResponse to response, fixes titling error 2023-04-04 18:17:23 -04:00
Wentao Lyu 56e02f4968 change Switch to simple mode to close button 2023-04-05 03:58:22 +08:00
Wentao Lyu 46fba87f9a feat: add confirm of clear all presets. 2023-04-05 03:50:06 +08:00
Wentao Lyu ee10e0e43e feat: use default preset to create new conversation. 2023-04-05 03:49:54 +08:00
Wentao Lyu 579b53de29 feat: style match 2023-04-05 03:07:46 +08:00
Wentao Lyu 9f1ded7f75 feat: add jailbreak option for bingai
fix some bugs.
2023-04-05 02:46:22 +08:00
Wentao Lyu efb440128a code clean. 2023-04-05 02:30:25 +08:00
Wentao Lyu 010d900c90 fix: use universal setToneStyle 2023-04-05 02:30:14 +08:00
Wentao Lyu 0c4b754fba fix: to set default value when change endpoint of preset 2023-04-05 02:29:43 +08:00
Wentao Lyu d864da6a21 fix: rewrite ask openAI and ask BingAI. now all code cleaned. 2023-04-05 02:29:11 +08:00
Danny AvilaandGitHub 0ec68bf5a8 Update defaultSystemMessage.md 2023-04-04 13:35:34 -04:00
Danny AvilaandGitHub 82b4401e49 Update defaultSystemMessage.md 2023-04-04 13:35:10 -04:00
Danny AvilaandGitHub 99d238b0de Update defaultSystemMessage.md 2023-04-04 13:30:24 -04:00
Danny AvilaandGitHub 617aa6f499 Update defaultSystemMessage.md 2023-04-04 13:30:04 -04:00
Danny Avila f922a1d102 Merge branch 'feat-endpoint-style-structure' of https://github.com/danny-avila/chatgpt-clone into feat-endpoint-style-structure 2023-04-04 12:53:59 -04:00
Danny Avila bb75b6df3b feat(bingai.js): add context and systemMessage parameters to askBing function
feat(conversationPreset.js): add context and systemMessage fields to conversation preset schema
feat(askBingAI.js): pass context and systemMessage parameters to ask function
feat(Settings.jsx): add toneStyle prop to BingAISettings component
feat(BingAIOptions/index.jsx): add useEffect to check if advanced mode is needed
feat(cleanupPreset.js): add context and systemMessage fields to cleaned up preset object
feat(getDefaultConversation.js): add context and systemMessage fields to default conversation object
feat(handleSubmit.js): add context and systemMessage fields to message object
2023-04-04 12:53:41 -04:00
Danny AvilaandGitHub 4b04959290 Update defaultSystemMessage.md 2023-04-04 11:22:23 -04:00
Danny AvilaandGitHub 28c5b21217 Update defaultSystemMessage.md 2023-04-04 11:21:30 -04:00
Daniel D Orlando 086267ee01 Add esbuild to package.json 2023-04-04 07:32:28 -07:00
Danny Avila 3484ff687d Merge branch 'feat-endpoint-style-structure' of https://github.com/danny-avila/chatgpt-clone into feat-endpoint-style-structure 2023-04-04 10:08:12 -04:00
Danny Avila 6e233accf5 style(tokenizer.js): comment out console.log statement
fix(Settings.jsx): handle null or empty context in useEffect to avoid errors
2023-04-04 10:07:48 -04:00
Danny Avila 1a196580b2 feat: count tokens for context
feat(BingAI): convert toneStyle to lowercase before setting it in state
feat(BingAI): pass setToneStyle function to Settings component
refactor(BingAI): remove unused import and change setOption to setToneStyle in Settings component
refactor(fetchers.js): add axiosPost function for debounced axios post requests
2023-04-04 10:04:21 -04:00
Wentao Lyu e1d52b858b fix: cannot open edit preset the use preset. 2023-04-04 22:01:01 +08:00
Wentao Lyu cef98070e9 style: show scrollbar over content. 2023-04-04 13:27:09 +08:00
Wentao Lyu 7353829719 refactor: code clean up 2023-04-04 13:11:59 +08:00
Daniel Avila 89e38d67f4 feat(bing-settings): Work in Progress, will finish tomorrow
feat(api): add @dqbd/tiktoken package as a dependency
feat(api): add /api/tokenizer endpoint to tokenize text using @dqbd/tiktoken
feat(client): add toneStyle dropdown to BingAI Settings component
feat(client): add token count to BingAI Settings component

style(ui): add z-index to Dropdown and EndpointOptionsPopover components

Add z-index to Dropdown and EndpointOptionsPopover components to ensure they are displayed above other components.
2023-04-03 21:18:19 -04:00
Danny AvilaandGitHub 825be5aa4d Update defaultSystemMessage.md 2023-04-03 20:12:56 -04:00
Danny AvilaandGitHub d8deb89e3a Update defaultSystemMessage.md 2023-04-03 20:07:24 -04:00
Danny AvilaandGitHub da69e4176a Create defaultSystemMessage.md 2023-04-03 20:06:55 -04:00
Danny AvilaandGitHub 85372118ed Delete defaultSystemMessage 2023-04-03 20:06:36 -04:00
Danny AvilaandGitHub 4fe48fe6bb Create defaultSystemMessage 2023-04-03 20:06:08 -04:00
Daniel Avila 03f63975cc Merge branch 'main' into feat-endpoint-style-structure 2023-04-03 17:30:59 -04:00
Danny Avila 28f1e851f9 chore(Settings.jsx): comment out unused import of InputNumber component 2023-04-03 16:29:02 -04:00
Danny Avila 58d162d8b4 reset package-lock files 2023-04-03 14:39:27 -04:00
Danny Avila 4961cc9250 reset package-lock on root dir 2023-04-03 14:24:32 -04:00
Wentao Lyu 8720f39def feat: preserve the title of preset. 2023-04-04 01:39:40 +08:00
Wentao Lyu 089d99e679 feat: open option dialog when click messageHeader 2023-04-04 01:23:37 +08:00
Wentao Lyu d2579b44d1 feat: support edit preset,
feat: support view current conversation options.
feat: save current conversation as a preset.
feat: preset delete all.
2023-04-04 01:12:14 +08:00
Wentao Lyu dae0c2d5e3 server fix: code refactor 2023-04-04 01:10:50 +08:00
Danny AvilaandGitHub 3d246df6c9 Merge pull request #149 from adamrb/main
Support local config for reverse proxy
2023-04-03 13:07:45 -04:00
Wentao Lyu ea88e9b981 fix: bingAI/settings: wrong usage of checkbox 2023-04-04 01:04:33 +08:00
Adam BoeglinandGitHub 584772b3f2 Support local config for reverse proxy 2023-04-03 09:29:52 -07:00
Wentao Lyu e7c9ae5a7d fix: regenerate should work for error message. 2023-04-03 21:32:21 +08:00
Wentao Lyu 4a95b30a0d fix: proxy auth in vite.config.js 2023-04-03 21:31:59 +08:00
Wentao Lyu 15ada95249 feat: export preset. 2023-04-03 12:54:15 +08:00
Daniel Avila ced65f8c98 feat(client): add @radix-ui/react-checkbox dependency and create BingAI Settings component
feat(BingAIOptions): add advanced mode to BingAIOptions component
feat(Checkbox): add Checkbox component to ui components
2023-04-02 18:45:41 -04:00
Daniel Avila 871bc4c78b refactor(getIcon.jsx): if model is GPT-4, change background color to black. 2023-04-02 15:23:28 -04:00
Daniel Avila 24a82c8018 Merge branch 'main' into feat-endpoint-style-structure 2023-04-02 15:10:21 -04:00
Danny AvilaandGitHub cd7e3f3774 Merge pull request #148 from danny-avila/update-proxy
fix(chatgpt-browser.js): update reverseProxyUrl to use new domain nam…
2023-04-02 14:54:47 -04:00
Daniel Avila ab7cf8c881 fix(chatgpt-browser.js): update reverseProxyUrl to use new domain name 'https://bypass.churchless.tech' instead of 'https://bypass.duti.tech' 2023-04-02 14:54:02 -04:00
Daniel Avila aa4fd57459 feat(chatgpt-browser): add support for multiple GPT models
This commit adds support for multiple GPT models in the chatGPT-browser
client. The available models are now stored in a Map object, which maps
the model label to its corresponding model.

The commit adds a new component, ChatGPTOptions, to the client
UI to allow the user to select the GPT model to use in the chat. The
component is only displayed when the chatGPT-browser endpoint is
selected.
2023-04-02 14:34:12 -04:00
Danny AvilaandGitHub 8551995a51 Merge pull request #138 from danny-avila/danorlando/setup-e2e-with-playwright
test: Playwright setup
2023-04-02 14:28:23 -04:00
Daniel D Orlando 85f3b488ce test: move playwright action out of workflows 2023-04-02 11:11:53 -07:00
Daniel D OrlandoandDaniel D Orlando 12b7e9a6bb reset back to npm ci 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando d5e062eeed try using npm install instead of npm ci in github action 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando f5e120c330 try explicitly setting registry before npm ci step 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando afaa0253b8 undo change adding .npmrc to try to resolve the package resolution issue on npm ci step of github action 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando 3ec2942365 try manually removing @playwright from the resolution path to make github action work 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando 6d7f0448ff try adding .npmrc to resolve problem getting playwright-core from the registry 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando 70427a628f generate new package-lock.json 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando b19ef425b7 try removing package-lock.json and forcing it to generate on the npm ci step 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando b7c911534c use node 18 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando fe6b1fc12a generate new package-lock.json 2023-04-02 11:07:48 -07:00
Daniel D OrlandoandDaniel D Orlando bd94c4233d Playwright setup 2023-04-02 11:07:48 -07:00
Daniel Avila eef2303c8e Merge branch 'main' into feat-endpoint-style-structure 2023-04-02 12:10:14 -04:00
Danny AvilaandGitHub 6f30b6ec9d Merge pull request #147 from danny-avila/lift-api
chore(api): update "@waylaidwanderer/chatgpt-api" dependency to versi…
2023-04-02 12:08:33 -04:00
Daniel Avila 709dbbbc4b chore(api): update "@waylaidwanderer/chatgpt-api" dependency to version 1.33.2 2023-04-02 12:07:59 -04:00
Daniel Avila 77b46cc1a7 refactor(ModelDropDown.jsx): remove commented out code and simplify model display 2023-04-02 11:37:10 -04:00
Danny AvilaandGitHub f069d6b621 Update README.md 2023-04-02 08:27:38 -04:00
Daniel Avila f187da3afa reset package-lock 2023-04-02 00:03:54 -04:00
Daniel Avila 0564e3ed93 Merge branch 'main' into feat-endpoint-style-structure 2023-04-02 00:03:13 -04:00
Danny AvilaandGitHub 6cad5d5b50 Merge pull request #145 from danny-avila/fix-docker
fix docker issues with vite
2023-04-02 00:01:33 -04:00
Daniel Avila 8bc4d5e3fd refactor(Dockerfile): change COPY command to copy client side code from /client/dist instead of /client/public
refactor(Dockerfile): change port number from 3080 to 80
refactor(Dockerfile): remove unnecessary EXPOSE command
refactor(package.json): remove devDependencies for Vite and React plugin
2023-04-02 00:00:49 -04:00
Daniel Avila b14726b2dd fix docker issues with vite 2023-04-01 23:59:16 -04:00
Daniel Avila 3121a3a6ba fix: add brackets to main property for dialogtemplate 2023-04-01 19:42:09 -04:00
Daniel Avila 8fa20d9356 Merge branch 'main' into feat-endpoint-style-structure 2023-04-01 19:41:36 -04:00
Daniel Avila 48b901fdfe Merge branch 'master' of https://github.com/danny-avila/chatgpt-clone into search 2023-04-01 19:13:54 -04:00
Danny AvilaandGitHub c65a3b69ff Merge pull request #143 from danny-avila/DanO/use-vite-instead-of-webpack
feat: Use VITE in place of Webpack
2023-04-01 19:03:13 -04:00
Wentao Lyu 2cce2e30ba fix: change endpoint should not skip when endpoint same 2023-04-02 04:48:33 +08:00
Wentao Lyu ad5fbc5fb1 merge Daniel Avila's commit
feat(ModelDropDown.jsx): add optional props to customize component appearance and behavior

feat(client): add ModelDropDown component to OpenAIOptions component
2023-04-02 04:40:13 +08:00
Daniel D Orlando eb2d9bac33 refactor: have build served from dist folder 2023-04-01 13:28:37 -07:00
Wentao Lyu 45e17da241 feat: add preset and edit preset. 2023-04-02 04:15:07 +08:00
Wentao Lyu 80ef5008dd feat: add preset in server 2023-04-02 04:14:42 +08:00
Daniel D Orlando 6498ab79a4 build: install and configure Vite, move index.html 2023-04-01 12:58:49 -07:00
Daniel D Orlando 78dabe55ae refactor: rename tailwind config 2023-04-01 12:57:39 -07:00
Daniel D Orlando 64fc2e84f2 refactor: rename and fix postcss.config 2023-04-01 12:57:17 -07:00
Daniel D Orlando ff757f27cf fix: fix files to adhere to standard conventions 2023-04-01 12:56:32 -07:00
Daniel Avila 2157eb8f30 feat(ModelDropDown.jsx): add optional props to customize component appearance and behavior
feat(client): add ModelDropDown component to OpenAIOptions component
2023-04-01 15:29:38 -04:00
Daniel Avila 7487b59de7 Merge branch 'feat-endpoint-style-structure' of https://github.com/danny-avila/chatgpt-clone into feat-endpoint-style-structure 2023-04-01 14:27:40 -04:00
Daniel Avila efe55d8842 style(ui): replace SaveIcon with Save from lucide-react in EndpointOptionsPopover component 2023-04-01 14:27:34 -04:00
Wentao Lyu d76efa7874 code cleanup 2023-04-02 00:57:27 +08:00
Daniel Avila c5805d710b Merge branch 'feat-endpoint-style-structure' of https://github.com/danny-avila/chatgpt-clone into feat-endpoint-style-structure 2023-04-01 12:49:42 -04:00
Daniel Avila f5ffa81455 complete: modeldropdown 2023-04-01 12:49:20 -04:00
Wentao Lyu 4d7fa26e6c feat: move EndpointOptionsPopover as a common component 2023-04-02 00:29:53 +08:00
Wentao Lyu 099210c10e feat: add default text.
fix: slider should set a number
2023-04-01 23:45:19 +08:00
Wentao Lyu 46e3ef4049 feat: endpoint setting mobile style.
feat: save and endpoint option works!
2023-04-01 23:27:44 +08:00
Wentao Lyu edaf7c3ad1 feat: advanced style for openAI endpoint 2023-04-01 14:33:26 +08:00
Wentao Lyu 60be4f12b7 feat: return endpoint config from server 2023-04-01 14:33:07 +08:00
Daniel Avila b687ab30ed style(OpenAIOptions): add dark mode support to Settings2 icon
feat(OpenAIOptions): pass isOpen prop to Settings component to toggle visibility
2023-03-31 21:51:06 -04:00
Daniel Avila 467e24c9ed style(Input/OpenAIOptions): comment out unused code and add gray color to settings icon 2023-03-31 21:48:27 -04:00
Daniel Avila 9098362377 style(Settings.jsx): remove unnecessary line break
style(Settings.jsx): add text-sm class to Label components
style(Settings.jsx): add flex, h-10, max-h-10, w-full, resize-none, px-3, py-2, focus:ring-0, focus:ring-offset-0, focus:ring-opacity-0, focus:outline-none classes to Input component
2023-03-31 21:45:29 -04:00
Daniel Avila 064dfb5336 fix(Settings.jsx): change min value of Slider component to -2 to allow negative values for presP variable 2023-03-31 21:11:43 -04:00
Daniel Avila 404566c1fb update more button and misc. styling 2023-03-31 18:38:58 -04:00
Daniel Avila 92dbdcaaa2 refactor(OptionHover.jsx): simplify types object by removing unnecessary nesting and description keys 2023-03-31 17:35:30 -04:00
Danny Avila 97e3ff6b6f reset package-lock 2023-03-31 16:19:48 -04:00
Danny Avila b5541903e4 merge latest 2023-03-31 16:09:45 -04:00
Danny Avila 580d82ffe8 basic settings done for openAI 2023-03-31 16:08:52 -04:00
Wentao Lyu 5cb59885ec fix : endpoint option should hide on exist conversation 2023-04-01 03:02:16 +08:00
Wentao Lyu ec47879edc clean: parentMassageId is no more needed in conversation. 2023-04-01 02:15:09 +08:00
Wentao Lyu b67af67433 feat: support copy to clipboard
feat: move regenerate to all messages
feat: move stop generate to replace submit button
feat: make options' position more clear
2023-04-01 02:12:15 +08:00
Danny Avila e8b2c2059d feat: select options 2023-03-31 13:12:06 -04:00
Wentao Lyu bb1f8d731b feat: select model
feat: force to be advanced mode
2023-04-01 00:38:05 +08:00
Wentao Lyu 059006382d feat: add animation 2023-04-01 00:09:49 +08:00
Wentao Lyu 462660d554 feat: prototype of endpoint setting 2023-03-31 23:16:00 +08:00
Danny Avila b703d3706b refactor(BingStyles.jsx): use cn utility function to concatenate class names in defaultSelected variable 2023-03-31 08:36:42 -04:00
Wentao Lyu 8b86fd0901 fix: jailbreak should be boolean 2023-03-31 16:36:24 +08:00
Wentao Lyu a8e44d0028 feat: view endpoint setting in message header. 2023-03-31 16:35:30 +08:00
Wentao Lyu babc4da7ab remove yarn.lock 2023-03-31 04:41:24 +08:00
Wentao Lyu 03f1a439d6 fix: suggestions seems not used. what's this? 2023-03-31 04:39:11 +08:00
Wentao Lyu 96639b82a8 feat: bingstyles now support endpoint 2023-03-31 04:33:26 +08:00
Wentao Lyu e8e3903b78 feat: feat: new endpoint-style endpoint select
fix: a wrong use of bingai params
2023-03-31 04:22:16 +08:00
Wentao Lyu adcc021c9e feat: feat: new endpoint-style submit 2023-03-31 03:22:57 +08:00
Wentao Lyu 089ca5f120 feat: new endpoint-style icon 2023-03-31 01:41:41 +08:00
Wentao Lyu 7e8b31cd09 fix: a typo in authenticatedOrRedirect 2023-03-31 00:26:43 +08:00
Wentao Lyu c53778e7c1 feat: new endpoint-style structure in client side
NOT FINISHED. model menu and icon and send message not work.
2023-03-31 00:20:45 +08:00
Wentao Lyu dd825dc6d4 feat: new endpoint-style structure in server side 2023-03-31 00:20:18 +08:00
Wentao Lyu 36c126e7a5 feat: show toneStyle in conversation title bar 2023-03-30 18:18:06 +08:00
Daniel Avila 9b04ffabca chore(client): update postcss-loader and postcss-preset-env packages to latest versions
chore(client): update webpack package to latest version
2023-03-29 20:17:17 -04:00
Danny Avila cf2bab31cf reset packages and uninstall unused dep. 2023-03-29 14:12:24 -04:00
Danny Avila d6fdf41011 fix: add text to global state 2023-03-29 11:25:27 -04:00
Danny AvilaandGitHub 79bb54db9c Merge pull request #140 from danny-avila/feat-refactor
Code refactoring
2023-03-29 11:00:04 -04:00
Danny Avila 4a94ee7af8 feat: allow default gpt api model before frontend customization 2023-03-29 10:17:24 -04:00
Danny Avila 39ff9c1bc2 fix: resize bing tabs 2023-03-29 09:02:49 -04:00
Danny Avila b9699feb3b fix: prevent scroll to top on initial messages 2023-03-29 08:49:39 -04:00
Danny Avila f93df2aea6 lift react markdown 2023-03-29 08:19:00 -04:00
Wentao Lyu 2c1871d5ba fix: update url rule in nginx 2023-03-29 16:11:43 +08:00
Wentao Lyu e796a19136 fix: remove related messages when deleting conversations. 2023-03-29 13:32:01 +08:00
Daniel Avila 0d7300be9b fix: chatgptBrowser handling and ask.js refactor 2023-03-28 22:28:43 -04:00
Daniel Avila 005d8fb178 edit titleConvo for consistent results 2023-03-28 19:50:37 -04:00
Daniel Avila f53b620df5 chore: add back clear convo dialog 2023-03-28 19:10:22 -04:00
Wentao Lyu e706f0ea9e typo 2023-03-29 06:44:18 +08:00
Wentao Lyu e663270072 fix: show message list. 2023-03-29 05:48:44 +08:00
Danny Avila 74924d2eea reset package-lock again 2023-03-28 16:29:42 -04:00
Danny Avila a04ea81143 Merge branch 'feat-refactor' of https://github.com/danny-avila/chatgpt-clone into feat-refactor 2023-03-28 15:35:40 -04:00
Wentao Lyu e818ee913d fix: add typescript package for react 2023-03-29 03:34:13 +08:00
Danny Avila 2a16a64612 fix: cursor appears on placeholder message 2023-03-28 15:32:22 -04:00
Danny Avila 95e9f05688 chore: reset package lock file 2023-03-28 15:15:10 -04:00
Wentao Lyu ee3f6e1d1d remove react-redux.
help needed, please generate a package-lock.json
2023-03-29 02:42:37 +08:00
Wentao Lyu b7af3595cf cleanup remove redux store 2023-03-29 02:38:24 +08:00
Wentao Lyu aa26eea8c5 fix: missing icon of search result
feat: use search result message as single list
2023-03-29 02:29:15 +08:00
Danny AvilaandGitHub c4be973b78 Update README.md 2023-03-28 14:12:08 -04:00
Wentao Lyu dc743df255 feat: update title generator prompt, to support better on language. 2023-03-29 01:50:57 +08:00
Wentao Lyu 319e4f0f95 fix: set to default model in searchPlaceholderConversation
fix: set max auth cookie to 7 days
2023-03-29 01:26:58 +08:00
Wentao Lyu f595cb2aa1 fix: three dot looks wide on android chrome. 2023-03-29 01:09:03 +08:00
Wentao Lyu 894aad9f0b sync with main 2023-03-29 00:20:57 +08:00
Wentao Lyu 5467d550e5 Merge remote-tracking branch 'origin/main' into feat-refactor-0.1.1 2023-03-29 00:19:46 +08:00
Wentao Lyu d0d0a3d23e feat: print nothing found when no search result.
fix: handle 404 of conversation fetch failed
2023-03-29 00:18:27 +08:00
Wentao Lyu 370dc2dd8a feat: support search-style-url
fix: url can be null in conversationId and query
fix: get conversation api should handle not found.
2023-03-29 00:08:02 +08:00
Danny AvilaandGitHub d9363b93c6 Create .github/FUNDING.yml 2023-03-28 11:48:47 -04:00
Danny AvilaandGitHub b1b904ce5a Merge pull request #139 from danny-avila/fix-bugs
Fix bugs
2023-03-28 11:45:05 -04:00
Danny Avila 4564b648f7 fix: failsafe to prevent all convos clearing from delete button 2023-03-28 11:42:36 -04:00
Danny Avila 0fbbe74479 revert bing to last working state 2023-03-28 11:38:56 -04:00
Wentao Lyu 8ea98cca5d refactor: bing button
THIS IS NOT FINISHED. DONT USE THIS
2023-03-28 23:00:29 +08:00
Wentao Lyu c7c30d8bb5 refactor: basic message and send message. as well as model
THIS IS NOT FINISHED. DONT USE THIS
2023-03-28 22:39:27 +08:00
Wentao Lyu de8f519742 refactor: update package-lock 2023-03-28 20:37:34 +08:00
Wentao Lyu af3d74b104 refactor: nav and search.
feat: use recoil to replace redux
feat: use react-native

THIS IS NOT FINISHED. DONT USE THIS
2023-03-28 20:36:21 +08:00
Danny AvilaandGitHub 2ad675196f Merge pull request #137 from danny-avila/fix-css
fix: set max height on customgpt prompt prefix
2023-03-28 08:11:43 -04:00
Danny Avila e434b3afea fix: set max height on customgpt prompt prefix 2023-03-28 08:10:22 -04:00
Wentao Lyu d8ccc5b870 fix: clearConvo will remove all messages 2023-03-28 01:19:44 +08:00
Wentao Lyu 7d43032a98 feat: return home page on any path
fix: clearConvo will remove all messages
2023-03-28 00:15:29 +08:00
Danny AvilaandGitHub 5b8483828b Merge pull request #136 from danny-avila/fix-css
fix: css matches official more closely with new markdown handling
2023-03-27 09:56:14 -04:00
Danny Avila 3c23f16b98 fix: css matches official more closely with new markdown handling 2023-03-27 09:55:24 -04:00
Danny AvilaandGitHub 7dc479e0a0 Merge pull request #135 from HyunggyuJang/follow-up/title-generation
updateConvo for title updating
2023-03-27 09:31:45 -04:00
Danny AvilaandGitHub ee9419cb0b Merge pull request #133 from HyunggyuJang/refactor/markdown
Use react markdown as default, cleanup dependencies
2023-03-27 09:31:32 -04:00
Danny AvilaandGitHub 1c26bbe43e Merge pull request #128 from HyunggyuJang/sydney/tone-adjustment
Make tone adjustment available after conversation started if sydney
2023-03-27 09:31:16 -04:00
Danny AvilaandGitHub 52d57f67aa Merge pull request #127 from HyunggyuJang/bing/fast-tone
Add fast tab for bing tone
2023-03-27 09:31:03 -04:00
Hyunggyu Jang 7486a56816 updateConvo for title updating 2023-03-27 12:53:40 +09:00
Hyunggyu Jang 40e23b013a Use react markdown as default, cleanup dependencies 2023-03-27 10:22:37 +09:00
Hyunggyu Jang c119c4044a Adjust color 2023-03-26 19:52:47 +09:00
Hyunggyu Jang 6f037309ad Fix for mobile view 2023-03-26 13:49:55 +09:00
Hyunggyu Jang af4110ff15 Do not allow to override bing AI tone in the middle of conversation 2023-03-26 13:25:29 +09:00
Hyunggyu Jang 55f04ffa60 Make tone adjustment available after conversation started if sydney 2023-03-26 13:12:48 +09:00
Hyunggyu Jang e5cf51b2d6 Add fast tab for bing tone 2023-03-26 13:10:36 +09:00
Danny AvilaandGitHub e8e512a451 Merge pull request #126 from danny-avila/minor-updates
chore: minor updates
2023-03-25 10:21:41 -04:00
Daniel Avila c2967eafa4 chore: update docker-compose for interchangeable env for search, uninstalled unused deps 2023-03-25 10:20:39 -04:00
Danny AvilaandGitHub 853b4dfd49 Merge pull request #125 from danny-avila/upgrade-node
Upgrade node
2023-03-25 10:02:17 -04:00
Danny AvilaandGitHub e49adaa314 Merge pull request #124 from danny-avila/bing-styles
Bing styles
2023-03-25 10:01:19 -04:00
Daniel Avila 394cdcd9f4 fix: remove 'class=' error 2023-03-25 09:55:33 -04:00
Daniel Avila c5561434c8 lift node-api library 2023-03-25 09:53:39 -04:00
Daniel Avila 26e7a715e0 feat: complete bing styles (bing is passed tone style) 2023-03-25 09:40:36 -04:00
Daniel Avila b07b74ba54 feat: complete bing styles (view) 2023-03-25 09:34:00 -04:00
Danny AvilaandGitHub 70152174b9 Merge pull request #123 from HyunggyuJang/fix/unknown-lang
Fix Unknown language bug
2023-03-25 08:39:10 -04:00
Hyunggyu Jang 8bd29f6d98 Fix Unknown language bug 2023-03-25 18:40:12 +09:00
Daniel Avila b8720eec3d feat: dark mode for style tabs 2023-03-24 19:02:42 -04:00
Daniel Avila ad0f2408c8 Merge branch 'main' into bing-styles 2023-03-24 18:50:56 -04:00
Danny AvilaandGitHub e2ad9accbe Merge pull request #122 from danny-avila/fix-search
Fix search
2023-03-24 18:45:03 -04:00
Daniel Avila b496174b4c fix: correctly escapes user content in search results 2023-03-24 18:44:12 -04:00
Daniel Avila 3295eb806c chore: expose mongodb on port 27018 2023-03-24 18:27:51 -04:00
Daniel Avila 34bef48e84 fix: correctly searches bing messages 2023-03-24 18:26:59 -04:00
Danny Avila a6c93ad681 dark mode in progress 2023-03-24 16:31:27 -04:00
Danny Avila 89ab74a913 feat: complete frontend/backend tone handling 2023-03-24 16:21:10 -04:00
Danny AvilaandGitHub a46ec62532 Update README.md 2023-03-24 16:19:42 -04:00
Danny AvilaandGitHub 22a967927f Update README.md 2023-03-24 16:19:16 -04:00
Danny AvilaandGitHub 0aa7581358 Add files via upload
in-depth local instructions thanks to @fuegovic
2023-03-24 16:17:04 -04:00
Danny AvilaandGitHub 513cd28528 Update README.md 2023-03-24 16:16:06 -04:00
Danny Avila 83b88bd759 feat: complete view for styles 2023-03-24 14:46:07 -04:00
Danny AvilaandGitHub 4f18a471b0 Merge pull request #121 from danny-avila/fix-clear-mobile
fix: clear convos on mobile now has top z-index
2023-03-24 08:20:30 -04:00
Danny AvilaandGitHub 737abd54ac Merge pull request #120 from HyunggyuJang/refactor/sydney
Refactor sydney & fix error
2023-03-24 08:19:06 -04:00
Hyunggyu Jang 1de51467ec Fix forgetfulness for bing/sydney 2023-03-24 12:48:20 +09:00
Hyunggyu Jang dcc0ab98e1 refactor: Remove verbose user message update for sydney 2023-03-24 12:27:46 +09:00
Daniel Avila 730256dcda fix: clear convos on mobile now has top z-index 2023-03-23 23:18:36 -04:00
Danny AvilaandGitHub 40e793477b Update README.md 2023-03-23 16:30:58 -04:00
Danny AvilaandGitHub b856db4772 Update .env.example
fix typo
2023-03-23 16:26:22 -04:00
Danny AvilaandGitHub cab1cbceab Merge pull request #118 from danny-avila/fix-auth
fix: auth env var must have no value, as well as assigned sample_user in route
2023-03-23 15:41:58 -04:00
Danny Avila b73be0dcfa fix: auth env var must have no value, as well as assigned username incase a falsy value is set 2023-03-23 15:37:25 -04:00
Danny AvilaandGitHub c6fb3018e7 Merge pull request #117 from danny-avila/search-final
Search final
2023-03-23 13:37:55 -04:00
Danny Avila 95cf27ee3e edit env example 2023-03-23 13:30:55 -04:00
Danny Avila 7afe09fa02 chore: clear timeouts 2023-03-23 13:16:07 -04:00
Danny Avila bff33c79b3 merge latest pr from main 2023-03-23 13:04:40 -04:00
Danny Avila f73936e5f4 Merge branch 'main' into search-final 2023-03-23 13:02:52 -04:00
Danny Avila 2b8d37c38f edit env example 2023-03-23 12:45:42 -04:00
Danny Avila ca3da2505a edit env example 2023-03-23 11:49:54 -04:00
Danny AvilaandGitHub d3046dca07 Merge pull request #115 from HyunggyuJang/sydney/branching
Enable branching (edit message) for Sydney
2023-03-23 11:38:44 -04:00
Danny AvilaandGitHub 25fd39c2b9 Update README.md
organize readme for readability
2023-03-23 11:35:23 -04:00
Daniel Avila ab8724b568 edit docker-compose for meilisearch 2023-03-22 22:35:25 -04:00
Hyunggyu Jang c240d14864 Enable branching (edit message) for Sydney 2023-03-23 11:27:14 +09:00
Daniel Avila 1d464fdcfa reduce noisy meili errors if not set 2023-03-22 21:23:01 -04:00
Daniel Avila aacc292522 Merge branch 'main' into search-final 2023-03-22 20:57:20 -04:00
Danny AvilaandGitHub 350a1bbae0 Update README.md 2023-03-22 20:48:47 -04:00
Danny AvilaandGitHub 90b74aff2e Merge pull request #104 from HyunggyuJang/refactor/docker
Refactor: merge docker setup file into one dockerfile & one docker-compose.yml
2023-03-22 20:13:10 -04:00
Daniel Avila 37f36ec44a chore: update meilisearch compose 2023-03-22 20:12:38 -04:00
Daniel Avila 71b7eaa3f5 chore: update env example and add browser client config 2023-03-22 20:09:52 -04:00
Daniel Avila 719413f87a feat: syncs across document deletions 2023-03-22 19:53:09 -04:00
Daniel Avila 97634865eb feat: syncs across document deletions 2023-03-22 19:52:38 -04:00
Daniel Avila 1dbfb0dab7 minor styling changes, cache queried messages on server 2023-03-22 18:26:29 -04:00
Daniel Avila 0a671849b5 feat: clearing convos requires confirmation 2023-03-22 17:51:51 -04:00
Daniel Avila 655e7ce6d6 chore: improve meili error handling 2023-03-22 17:15:32 -04:00
Danny Avila 68979015c1 markdown styling changes in progress 2023-03-22 16:31:57 -04:00
Danny Avila 8f58c95452 feat: main styling/ui/ux final changes 2023-03-22 16:06:11 -04:00
Danny Avila 67161c983f chore: meilisearch setup config 2023-03-22 10:23:55 -04:00
Danny Avila 73449d9ec6 feat: build tree by convoId 2023-03-22 10:23:36 -04:00
HyunggyuJangandGitHub f5d102b7bd Update docker-compose.yml 2023-03-22 23:20:03 +09:00
Hyunggyu Jang 40ed6fa9ec Provide nginx docker build recipe 2023-03-22 22:59:29 +09:00
Danny Avila 5164cf46ac chore: error handling for complete omission of env var 2023-03-22 09:38:38 -04:00
Daniel Avila 277685c218 search: updating search endpoint (wip) 2023-03-22 01:34:36 -04:00
Daniel Avila e25aa74d7b search: correctly register/export schema/models, also made IIFE 2023-03-22 01:33:49 -04:00
Daniel Avila 47a6cfcafd chore: error controller (wip) 2023-03-22 01:32:55 -04:00
Daniel Avila 83a96706b4 chore: add prettier 2023-03-22 01:32:30 -04:00
Daniel Avila 75be4d9722 search: helper fn for invalid convoId strings 2023-03-22 01:31:49 -04:00
Daniel Avila a5cf2f9148 search: correctly register/export schema/models for mongoMeili 2023-03-22 01:31:01 -04:00
Daniel Avila 8be19f9982 search: sync on offset between meili and mongo 2023-03-22 01:30:04 -04:00
Hyunggyu Jang 36f3d37ecc Remove nginx setting 2023-03-22 10:50:59 +09:00
Hyunggyu Jang c233cc0d5c Move Dockerfiles into one toplevel Dockerfile 2023-03-22 10:50:44 +09:00
Daniel Avila 194051e424 feat: api will plugin mongoMeili without a valid connection 2023-03-21 20:06:27 -04:00
Daniel Avila 94c0fbb525 feat: api will disable search if no meilisearch connection 2023-03-21 19:44:31 -04:00
Daniel Avila 97a6cd801b feat: simple api call to enable search 2023-03-21 19:31:57 -04:00
Danny AvilaandGitHub 1041146fcb Merge pull request #108 from danny-avila/upgrade-fix
fix: chatgpt now using latest
2023-03-21 17:41:05 -04:00
Danny Avila e531a17e0f fix: chatgpt now using latest 2023-03-21 16:32:32 -04:00
Danny AvilaandGitHub 30a7a80bfc Merge pull request #107 from danny-avila/chores
Chores
2023-03-21 14:27:00 -04:00
Danny Avila 67f8374c9e chore: eslint rules and blinker update in text handling 2023-03-21 13:41:31 -04:00
Danny Avila 0cc4aea204 chore: reorg. content files, add blinking cursor 2023-03-21 09:46:08 -04:00
Danny Avila 04796824d5 chore: init eslint 2023-03-21 08:48:35 -04:00
Danny AvilaandGitHub 9020239e1f Merge pull request #103 from HyunggyuJang/use-lock-file
Use npm ci rather than install to be consistent with lock file
2023-03-21 08:31:09 -04:00
Hyunggyu Jang 0a12b47760 Use npm ci rather than install to be consistent with lock file 2023-03-21 12:23:25 +09:00
Danny AvilaandGitHub 9358a4fdb5 Merge pull request #102 from danny-avila/bing-hotfix
bing hotfix, latest api, uses sydney
2023-03-20 17:16:45 -04:00
Daniel Avila 7d796f2c3e bing hotfix, latest api, uses sydney 2023-03-20 17:02:37 -04:00
Danny AvilaandGitHub 0a1651f6a1 Update README.md 2023-03-20 04:44:42 -04:00
Daniel Avila d13315c45b chore: comment out auth route 2023-03-20 02:58:41 -04:00
Daniel Avila 0b75d5d6fe chore: another hotfix patch on getConvoTitle 2023-03-20 02:30:08 -04:00
Wentao LyuandDaniel Avila 39819b744c doc: add magic of generate auth code 2023-03-20 02:22:04 -04:00
Daniel Avila 28c8f066d9 chore: add latest api as dep with alias 2023-03-20 02:18:03 -04:00
Daniel Avila c85602b93b chore: Replace hard coded message ID with unique one 2023-03-20 01:51:07 -04:00
Daniel Avila 08c91871c7 chore: hotfix: browser will not leave empty convo 2023-03-20 01:42:06 -04:00
Daniel Avila 80ca3bc375 chore: hotfix for browser client 2023-03-20 01:35:02 -04:00
HyunggyuJangandDaniel Avila 0405206438 Remove crypto from client side
Unless, if we tries to access the client side page from http protocol, crypto.randomUUID() isn't available.
2023-03-20 00:58:17 -04:00
Daniel Avila 0af8f6a699 chore: fix broken browser client 2023-03-20 00:51:56 -04:00
Daniel Avila b0936fa322 chore: unplug meilisearch, add leading option to throttle 2023-03-20 00:48:16 -04:00
Daniel Avila 4cd0ff2682 fix: throttle scroll to bottom 2023-03-19 11:45:03 -04:00
Daniel Avila 4ce60537ca search result styling changes 2023-03-19 11:25:12 -04:00
Daniel Avila 0b47218cd5 markdown library change 2023-03-19 01:14:19 -04:00
Daniel Avila d56aa2edef loads up to 20 messages, debugging markdown issue 2023-03-18 23:18:36 -04:00
Daniel Avila 4e6168d8fa setup message population on search 2023-03-18 18:40:53 -04:00
Daniel Avila 4197a92609 feat: search working as expected 2023-03-18 17:49:24 -04:00
Daniel Avila da42d6272a add loading state for fetching 2023-03-18 15:59:59 -04:00
Daniel Avila b97594c000 fix: conflicting fetch with /api/convos 2023-03-18 14:28:10 -04:00
Daniel Avila 0f54ffd8b4 feat: search bar working, still in progress 2023-03-18 01:40:49 -04:00
Daniel Avila 610cba4a60 backend logic drafted, moving to frontend 2023-03-17 22:20:36 -04:00
Daniel Avila 4f5ee8b198 move db functions 2023-03-17 19:58:13 -04:00
Daniel Avila 586c162404 merge commit 2023-03-17 19:40:44 -04:00
Danny AvilaandGitHub 1308ef1394 Merge pull request #88 from wtlyu/feat-clean-after-regenerate-pr
Feat clean after regenerate pr
2023-03-17 15:31:32 -04:00
Danny Avila 1513c27f7d adjust: wording and placeholder text size 2023-03-17 14:48:21 -04:00
Wentao Lyu 0ff3bbb28f clean up 2023-03-18 02:15:24 +08:00
Wentao Lyu 7987c0100c fix: add crypto as deps. [need confirm] 2023-03-18 02:04:51 +08:00
Wentao Lyu e8611a1d07 fix: don't reset isSubmitting in useEffect of submission 2023-03-18 01:54:06 +08:00
Wentao Lyu ce78123369 fix: allow delete of last char while submitting 2023-03-18 01:24:35 +08:00
Wentao Lyu a90db1f1a4 fix: add model to customGpts
fix: filter models by model only
2023-03-18 01:12:45 +08:00
Wentao Lyu a213868b17 fix: set isSubmitting with messages together
style: some notification
2023-03-18 01:12:45 +08:00
Wentao Lyu 6b2a2bb858 feat: save cancelled flag in message 2023-03-18 01:11:44 +08:00
Wentao Lyu fea3afa740 fix: missing import 2023-03-18 01:10:20 +08:00
Wentao Lyu 7372b37fe6 style: hide footer in mobile mode
style: avoid ui overflow in mobile mode
2023-03-18 01:09:59 +08:00
Danny AvilaandGitHub e11ce141d7 Merge pull request #86 from danny-avila/fix-scroll
fix: fix weird scrolling behavior on last message
2023-03-17 12:38:15 -04:00
Danny Avila 46fbd3b66a fix: fix weird scrolling behavior on last message 2023-03-17 12:34:54 -04:00
Danny AvilaandGitHub ce3f03267a Merge pull request #80 from wtlyu/feat-regenerate-and-cancel
Feat regenerate and cancel
2023-03-17 10:40:45 -04:00
Danny AvilaandWentao Lyu 9a2392e4d5 fix: mobile styling after regen buttons 2023-03-17 22:20:23 +08:00
Danny AvilaandGitHub 1eab4d240d Merge pull request #85 from danny-avila/bing-refusal
Bing refusal
2023-03-17 09:14:40 -04:00
Danny Avila 5568a60174 handle bing message refusal 2023-03-17 09:11:45 -04:00
Danny Avila ea4180f22a remove detect code (not always accurate) 2023-03-17 09:11:31 -04:00
Daniel Avila 6d2f3361d0 feat: search in progress 2023-03-16 21:20:40 -04:00
Daniel Avila 9995a159aa feat: reorganize api files, add mongoMeili 2023-03-16 19:38:16 -04:00
Daniel Avila 854f1c3572 feat: search, refactoring messages model 2023-03-16 17:20:26 -04:00
Danny Avila dcc13daf67 testing: mongo meilisearch 2023-03-16 16:22:08 -04:00
Wentao Lyu 2310bab348 revert: dont overwrite the mobile input panel color 2023-03-17 03:54:15 +08:00
Wentao Lyu d64edfdc7d feat: add chrome title color 2023-03-17 03:50:34 +08:00
Wentao Lyu a8c53f1f0d mobile style, for input panel and regenerate buttons 2023-03-17 03:50:34 +08:00
Wentao Lyu ef9f1ee1cf feat: cancellable api request 2023-03-17 03:50:34 +08:00
Wentao Lyu 66ad54168a feat: regenerate for bingai 2023-03-17 03:50:34 +08:00
Wentao Lyu 0891566d1e feat: add regenerate to all response message as official 2023-03-17 03:49:59 +08:00
Wentao Lyu e3b0cb7db7 feat: show model at the top of conversation messages. 2023-03-17 03:48:45 +08:00
Danny Avila c27554ed2e fix: allow customs in model filter 2023-03-16 15:05:23 -04:00
Danny AvilaandGitHub 87f793f1c4 Merge pull request #79 from danny-avila/improve-titles
fix: add deterministic titling
2023-03-16 15:03:04 -04:00
Danny Avila 4078c5283b fix: add deterministic titling 2023-03-16 15:02:40 -04:00
Danny AvilaandGitHub 5cac7e48f0 Merge pull request #74 from wtlyu/feat-model-based-on-key
feat: show model based on configured Keys
2023-03-16 15:00:23 -04:00
Danny AvilaandGitHub 7a08c77850 Update README.md 2023-03-16 14:11:24 -04:00
Danny AvilaandGitHub 1fe9e29187 Merge branch 'master' into feat-model-based-on-key 2023-03-16 13:39:41 -04:00
Danny AvilaandGitHub ba8692dbe4 Merge pull request #59 from wtlyu/feat-usersys
Feat usersys
2023-03-16 13:35:03 -04:00
Danny AvilaandGitHub d30b406c4c Merge pull request #4 from wtlyu/revisions
Revisions
2023-03-16 13:34:36 -04:00
Danny Avila 915cda70ef fix: user:user redundancies 2023-03-16 13:32:04 -04:00
Danny Avila df19595c5b fix: icon and mobile textarea styling 2023-03-16 13:30:10 -04:00
Danny Avila 867b3073d4 fix: icon and mobile textarea styling 2023-03-16 13:29:13 -04:00
Danny AvilaandGitHub e4e28dbbe2 Update README.md 2023-03-16 10:57:44 -04:00
Danny AvilaandGitHub cdbc0e21e7 Update README.md 2023-03-16 10:57:18 -04:00
Wentao Lyu b6f7f95709 feat: set default model once model list read. 2023-03-16 21:12:33 +08:00
Wentao Lyu 131af50034 feat: show model based on configured Keys 2023-03-16 14:44:14 +08:00
Wentao Lyu 23c050b54e style: mobile style of landing 2023-03-16 14:07:48 +08:00
Wentao Lyu 7442294c41 fix: mobile sizing style of icon 2023-03-16 14:05:12 +08:00
Wentao Lyu a47dbe6262 feat: use same icon style in TextChat 2023-03-16 13:45:46 +08:00
Wentao Lyu aabb19656e feat: combine customgpt to user 2023-03-16 13:30:20 +08:00
Wentao Lyu b0284b6974 sync updates and merge with feat-resubmit 2023-03-16 13:20:54 +08:00
Wentao Lyu 62d88380e0 feat: add sample multi-user support
feat: update README
2023-03-16 13:20:34 +08:00
Daniel Avila 41f351786f fix: oversight, artifact in bing route 2023-03-15 22:36:16 -04:00
Danny AvilaandGitHub ffcfb69dee Merge pull request #72 from danny-avila/fix-mobile-switch
fix: mobile view for sibling switch
2023-03-15 19:06:41 -04:00
Daniel Avila c91ce36227 fix: mobile view for sibling switch 2023-03-15 19:05:17 -04:00
Danny AvilaandGitHub d06e58f043 Merge pull request #55 from wtlyu/feat-resubmit
Feature: multipath message & resubmit, conversation placeholder, separate title generation, and some bug fix.
2023-03-15 18:06:35 -04:00
Daniel Avila d052d221dc chore: switch focus to textarea when custom model change (still need to figure out reg model change) 2023-03-15 18:05:34 -04:00
Danny AvilaandGitHub ff45511011 Merge pull request #3 from wtlyu/final-adj
Final adjustments
2023-03-15 17:08:18 -04:00
Danny Avila 8c6340aed0 chore: refactor cursor blink, debugging 2023-03-15 16:38:01 -04:00
Danny Avila 84b104e65f chore: delegate response text parsing to one location 2023-03-15 15:44:48 -04:00
Danny Avila a0c94715ce chore: refactor titleConvo 2023-03-15 15:21:04 -04:00
Danny Avila a8aad30fc8 chore: memoized Messages component, will require custom equality check 2023-03-15 14:36:17 -04:00
Danny Avila 2fd50c99b8 fix: debounce title request and handle error with default title 2023-03-15 12:47:30 -04:00
Danny Avila 96ca783517 chore: re-organize message modules, fix icon size, convo reset properly rebuilds Tree 2023-03-15 10:42:45 -04:00
Wentao Lyu 45ca0a8713 fix: gptCustom icon should show as same in model and message 2023-03-15 14:42:39 +08:00
Wentao Lyu 5d0b849930 feat: show icon within model select menu
fix: use icon for gptCustom
2023-03-15 14:21:08 +08:00
Wentao Lyu 54aa9debb4 fix: don't reset new convo if model not change
fix: change model will clear all messages.
2023-03-15 13:38:01 +08:00
Daniel Avila 4e91437049 fix: convo resets, sets new Convo 2023-03-14 21:32:25 -04:00
Daniel Avila 918f2fecb6 fix: convo resets on model change 2023-03-14 21:25:02 -04:00
Daniel Avila 796d8031e8 fix: ensure custom params are not passed to non custom models 2023-03-14 20:21:41 -04:00
Daniel Avila 6e32f71565 fix: adjust custom client for new progress CB 2023-03-14 20:15:06 -04:00
Daniel Avila 6192c2964e fix: validation to avoid saving customGpt params to non-custom models 2023-03-14 20:14:38 -04:00
Daniel Avila 626a8fbd8e fix: if db is empty, will never show new convos until refresh 2023-03-14 18:53:46 -04:00
Danny AvilaandGitHub a8344ec5bf Merge pull request #2 from wtlyu/refactors
Refactors
2023-03-14 18:16:44 -04:00
Danny AvilaandGitHub c230fe41f4 Merge branch 'feat-resubmit' into refactors 2023-03-14 16:09:25 -04:00
Danny Avila d0ef0f84c8 chore: delegate text handling to one place, html sanitization in progress 2023-03-14 16:05:46 -04:00
Wentao Lyu 8289558d94 feat: pagination in nav 2023-03-15 04:05:14 +08:00
Danny Avila 2e20b28c4d chore: refactor progressCB to one place, fix sydney, and sanitize html 2023-03-14 15:42:59 -04:00
Danny Avila 9a17e94f8f fix: refactor migration and sort old convos correctly 2023-03-14 14:51:26 -04:00
Wentao Lyu 71fc86b9a6 fix: buildTree should store parent-not-exist message as root. rather than dropping them. 2023-03-15 02:43:21 +08:00
Wentao Lyu 8882432210 fix: hide the edit button when bingai 2023-03-15 02:33:08 +08:00
Wentao Lyu 644f3f716f feat: re-orginazed three ask api. To provide ability to reproduce message.
feat: bing and sydney come to work again, [need more test]
2023-03-15 02:15:46 +08:00
Wentao Lyu 8b00805d24 fix: dont send gen_title twice 2023-03-15 02:15:46 +08:00
Wentao Lyu d73375958b feat: return error as a error message, not only text 2023-03-15 02:15:46 +08:00
Wentao Lyu 7168498543 fix: jailbreakConversationId=false response will be saved jailbreakConversationId='false' in database. 2023-03-15 02:15:46 +08:00
Wentao Lyu 27515cb00a fix: generate title by backend 2023-03-15 02:15:46 +08:00
Daniel AvilaandWentao Lyu 3e7ce67609 fix: migrate old schema to new 2023-03-15 02:15:46 +08:00
Daniel AvilaandWentao Lyu 4fd05e15b4 fix: migrate old schema to new 2023-03-15 02:15:46 +08:00
Wentao Lyu 0fa19bb6ad feat: save error message into database. 2023-03-15 02:15:46 +08:00
Wentao Lyu d9e5464b3b fix: cleanup debug msg 2023-03-15 02:15:46 +08:00
Wentao Lyu 953c5fc970 fix: w<1024px, will overflow. 2023-03-15 02:15:46 +08:00
Wentao Lyu 2afbc5883f fix: use onCompositionStart and onCompositionEnd to aviod enter submit when using input method. 2023-03-15 02:15:46 +08:00
Wentao Lyu 953f846958 fix: loading and send button, mobile style
feat: sibling switch, mobile style
fix: only the real submitting message will blink
feat: drop the text version username, use a similar square. (or it will mass up the sibling switch)
2023-03-15 02:15:46 +08:00
Wentao Lyu a4d5f6a3f2 feat: fully multipath and resubmit 2023-03-15 02:15:46 +08:00
Wentao Lyu 4a39965b22 fix: add proxy to titleConvo 2023-03-15 02:15:46 +08:00
Wentao Lyu 90dc171b34 test: generate of title shouldn't be mislead by answer 2023-03-15 02:15:46 +08:00
Wentao Lyu 0e98cb4206 fix: in mobile view, resubmit edit button should always visible 2023-03-15 02:15:46 +08:00
Wentao Lyu 9f8e9cb091 feat: gen title by sperate api call
feat:

fix: rename of convo should based on real request.
2023-03-15 02:15:46 +08:00
Wentao Lyu 8773878be2 feat: create conversation at the beginning then return the userMessage 2023-03-15 02:15:46 +08:00
Wentao Lyu 5a409ccfa6 fix: don't resubmit html label
fix: hide the resubmit editor border
2023-03-15 02:15:46 +08:00
Wentao Lyu 0ed8a40a41 feat: merge all message.id into message.messageId
feat: the first message will have a parentMessageId as 00000000-0000-0000-0000-000000000000 (in order not to create new convo when resubmit)
feat: ask will return the userMessage as well, to send back the messageId

TODO: comment out the title generation.
TODO: bing version need to be test

fix: never use the same messageId
fix: never delete exist messages
fix: connect response.parentMessageId to the userMessage.messageId
fix: set default convo title as new Chat
2023-03-15 02:15:46 +08:00
Wentao Lyu be71140dd4 fix: new message should append to the exist one 2023-03-15 02:15:46 +08:00
Wentao Lyu 6d51ec3e37 feat: auto hide edit button when edit is enabled. 2023-03-15 02:15:46 +08:00
Wentao Lyu bdfc895800 feat: support resubmit.
TODO: basic implementation. should add multi-path record in future.

feat: add deleteMessahesSince
feat: saveMessage will do createOrSave
feat: reorginazed submission
2023-03-15 02:15:46 +08:00
Wentao Lyu b9975ac283 fix: missing setSubmission 2023-03-15 02:15:46 +08:00
Wentao Lyu dd1f74da72 replace all created to timestamps: true in db 2023-03-15 02:15:46 +08:00
Danny AvilaandGitHub 5d65427a6e Merge pull request #56 from HyunggyuJang/build/docker-from-rootdir
Build docker files from root dir
2023-03-13 13:17:23 -04:00
Danny AvilaandGitHub 6a16d31f26 Merge pull request #60 from wtlyu/feat-autofocus
feat: auto focus
2023-03-13 13:15:43 -04:00
Wentao Lyu 8affbfdb4a feat: auto focus 2023-03-14 01:12:11 +08:00
Hyunggyu Jang 70d59c5b3c Build docker files from root dir 2023-03-13 22:49:40 +09:00
Danny AvilaandGitHub 73979ee67f Merge pull request #54 from danny-avila/minor-fixes
fix: model menu key issue
2023-03-12 16:46:25 -04:00
Daniel Avila 8e513d83a5 fix: model menu key issue 2023-03-12 16:45:44 -04:00
Danny AvilaandGitHub 9bc85ea83d Merge pull request #47 from wtlyu/feat-title-with-language
fix: give a stronger prompt to generate tile using the input language
2023-03-12 15:10:45 -04:00
Wentao Lyu 943eb5c74d fix: give a stronger prompt to generate tile using the input language 2023-03-13 01:51:32 +08:00
Danny AvilaandGitHub bac37cfe36 Update README.md 2023-03-11 23:29:27 -05:00
Danny AvilaandGitHub b2334054da Update README.md 2023-03-11 23:28:56 -05:00
Danny AvilaandGitHub a705e907ff Merge pull request #45 from danny-avila/submit-state
fixes: allow simultaneous convos & minor improvements
2023-03-11 23:28:33 -05:00
Daniel Avila c8bb6b13bf update: readme.md 2 2023-03-11 23:24:53 -05:00
Daniel Avila 50a17bc1b8 update: readme.md 2023-03-11 23:22:51 -05:00
Daniel Avila 3038015a5b fix: removes newline from start of stream response if present 2023-03-11 23:11:55 -05:00
Daniel Avila 20da895ee7 update bypass proxy url for browser client 2023-03-11 23:08:04 -05:00
Daniel Avila 8762765dd0 fix: retain scroll view on delete convo 2023-03-11 22:59:32 -05:00
Daniel Avila f1aabfa543 fix: reset submission when convo clears 2023-03-11 22:35:39 -05:00
Daniel Avila 23de688bf3 fix: stops stream upon conversation 2023-03-11 21:42:08 -05:00
Daniel Avila 79f050bac7 feat: loading state for messages 2023-03-11 18:39:46 -05:00
Danny AvilaandGitHub afae13eec6 Merge pull request #43 from danny-avila/fix-custom-name
fix: format sender text change and mobile styling
2023-03-11 17:38:17 -05:00
Daniel Avila 4fa599e97a fix: format sender text change and mobile styling 2023-03-11 17:36:10 -05:00
Danny AvilaandGitHub 3b1d6aaa79 Merge pull request #42 from danny-avila/title-lang
feat: title the chat in user's language
2023-03-11 16:28:36 -05:00
Daniel Avila 339230cd74 feat: title the chat in user's language 2023-03-11 16:26:41 -05:00
Daniel Avila 95bad60f7d fix: detectCode bug 2023-03-11 14:46:21 -05:00
Danny AvilaandGitHub 950b8f3f1e Merge pull request #34 from wtlyu/feat-mobile
feat: basic support mobile mode.
2023-03-11 12:59:09 -05:00
Danny AvilaandGitHub 0e2e5b8393 Update README.md 2023-03-11 12:32:59 -05:00
Danny AvilaandGitHub d2cb9957fe Merge pull request #35 from danny-avila/rm-short-filter
fix: rm short filter
2023-03-11 12:10:56 -05:00
Daniel Avila 78b4004ead fix: rm short filter 2023-03-11 12:10:00 -05:00
Danny AvilaandGitHub 8624062488 Merge pull request #26 from wtlyu/master
support config host name and proxy address. and fix a docker bug
2023-03-11 12:05:16 -05:00
Wentao Lyu 070fee2ece feat: basic support in mobile mode. including:
navbar show and hide,
similar fade animation,
auto close when select new convo,
mobile title will change with convo,
new chat button.
2023-03-12 00:32:03 +08:00
Danny AvilaandGitHub b1569450fd Merge pull request #32 from danny-avila/fix-hjs
Fix hjs
2023-03-11 10:32:38 -05:00
Daniel Avila 09659be002 fix: unsupported lang handler in client 2023-03-11 10:28:46 -05:00
Wentao Lyu 5e4fa09dcb update readme, add proxy and host information 2023-03-11 16:14:24 +08:00
Wentao Lyu 16c9589058 fix: use quotes outside of key=value in docker-compose.yml
fix: set HOST=0.0.0.0 by default in api's dockerfile
2023-03-11 16:14:11 +08:00
Wentao Lyu 8604030404 Add more information in env.example.
Give current host address when HOST=0.0.0.0
2023-03-11 15:03:18 +08:00
Wentao Lyu 97668217b9 fix: add proxy to bingai too 2023-03-11 15:02:26 +08:00
Wentao Lyu c5c865a25f fix: frontend api request shouldn't hard code the domain and port. 2023-03-11 14:13:39 +08:00
Wentao Lyu 06c99154ac feat: support config host name and proxy address 2023-03-11 14:12:51 +08:00
Daniel Avila 9d71b58345 fix: handles unsupported hjs languages in client response 2023-03-10 22:21:15 -05:00
Danny AvilaandGitHub fb9f77ae5e Merge pull request #23 from danny-avila/tab-links
Add back Tab links
2023-03-10 13:18:54 -05:00
Danny Avila fc2b9bf7f2 feat: tab link component, edit wrapper 2023-03-10 13:12:24 -05:00
Danny Avila cdbe00ec6f feat: tab link component 2023-03-10 13:11:17 -05:00
Danny Avila 72ff47e204 Rolled back to v0.0.2 2023-03-10 12:55:45 -05:00
Danny AvilaandGitHub 57d3025717 Merge pull request #20 from danny-avila/override-links
feat: links open in new tab
2023-03-10 09:45:52 -05:00
Danny Avila 6c02558f1b feat: links open in new tab 2023-03-10 09:44:40 -05:00
Danny AvilaandGitHub 0c62863e52 Update README.md 2023-03-10 08:38:45 -05:00
Danny Avila d825721dad fix: update readme and compose file 2023-03-10 08:37:15 -05:00
Danny AvilaandGitHub 0b3b6f91fc Merge pull request #19 from wtlyu/master
support config host name and proxy address.
2023-03-10 08:21:22 -05:00
Wentao Lyu 3a9b532248 fix: frontend api request shouldn't hard code the domain and port. 2023-03-10 21:12:16 +08:00
Wentao Lyu 67156f4a7a feat: support config host name and proxy address 2023-03-10 21:12:16 +08:00
Daniel Avila 4e616cd2ed fix: error handling titles, remove openAI req 2023-03-10 07:43:43 -05:00
Danny AvilaandGitHub 12cf3405e4 Merge pull request #16 from danny-avila/updateAI
feat: add sydney (jailbroken bing) and more bing styling with citations
2023-03-09 20:34:28 -05:00
Daniel Avila 4383894ad3 finish update 2023-03-09 20:31:56 -05:00
Daniel Avila d0ecaabfd8 feat: cites text with links 2023-03-09 20:29:44 -05:00
Daniel Avila a286f027c8 model menu won't close on model deletion 2023-03-09 19:02:59 -05:00
Daniel Avila 0ba92c4c03 add new reducer to ensure conversations are renewed on model change 2023-03-09 18:57:37 -05:00
Daniel Avila 30936573ac feat: includes sources 2023-03-09 18:42:36 -05:00
Daniel Avila a451574760 feat: cites text with links 2023-03-09 18:07:36 -05:00
Danny Avila 5e57deab5f bing styling in progress 2023-03-09 16:09:53 -05:00
Daniel Avila eae82edb83 merge readme update 2023-03-08 22:33:25 -05:00
Daniel Avila 1e0cbbf1cc merge readme update 2023-03-08 22:32:51 -05:00
Daniel Avila 3986b4ec9e Merge branch 'master' of https://github.com/danny-avila/chatgpt-clone into updateAI 2023-03-08 22:31:40 -05:00
Daniel Avila 67054e7504 working sydney, need to test the other models 2023-03-08 22:30:29 -05:00
Daniel Avila 2c1ae68dc4 adding sydney in progress. only uses jailbreakConvoId and parentMsgId 2023-03-08 21:06:58 -05:00
Daniel Avila 69b3edc52c feat: sydney is functional 2023-03-08 19:47:23 -05:00
Danny AvilaandGitHub 732c75d145 Update README.md 2023-03-08 10:26:12 -05:00
Danny AvilaandGitHub 0f54251459 Merge pull request #12 from HyunggyuJang/fix/webpack-artifacts
webpack artifacts should exist for nginx image
2023-03-07 21:07:13 -05:00
Hyunggyu Jang 72ef76b8a0 webpack artifacts should exist for nginx image 2023-03-08 10:38:22 +09:00
Danny AvilaandGitHub 81abbdb335 Merge pull request #11 from HyunggyuJang/fix/docker-name
DockerFile is invalid name
2023-03-07 20:11:44 -05:00
Hyunggyu Jang 67ca79dd3f DockerFile is invalid name 2023-03-08 09:51:04 +09:00
Danny AvilaandGitHub 5af03bb3d8 Update README.md 2023-03-07 14:54:44 -05:00
Danny AvilaandGitHub e5d25f45e3 Merge pull request #7 from danny-avila/easy-config-setup
dockerize app, improve ui/ux, add setup instructions
2023-03-07 14:53:57 -05:00
Danny Avila ad967beaf8 update readme 2023-03-07 14:51:41 -05:00
Danny Avila a8eaf2452a update readme, setup files, and clear convo bug 2023-03-07 14:43:55 -05:00
Danny Avila 5ad32e211f update docker-compose 2023-03-07 14:23:56 -05:00
Danny Avila da61a2841f fix: customGpt persistence blindspot, also remove free gpt 2023-03-07 14:22:33 -05:00
Danny Avila da55ac3774 update readme 2023-03-07 13:58:57 -05:00
Danny Avila f2b092545f fix: convos correctly delete 2023-03-07 13:53:23 -05:00
Danny Avila 17c01aa252 fix: ignoreDir pointing correctly 2023-03-07 13:49:15 -05:00
Danny Avila 6be037323e chore: model menu functions behave as expected 2023-03-07 13:19:56 -05:00
Daniel Avila 191118b90b add buttons to custom model menu items 2023-03-06 21:43:49 -05:00
Daniel Avila 6d757ff91f improve custom model selection ux 2023-03-06 21:12:36 -05:00
Daniel Avila d93282640c modify starting files and readme 2023-03-06 18:17:43 -05:00
Daniel Avila b9e9203a75 Merge branch 'easy-config-setup' of https://github.com/danny-avila/chatgpt-clone into easy-config-setup 2023-03-06 16:59:03 -05:00
Danny Avila ee8eba9ad5 updating readme 2023-03-06 16:30:16 -05:00
Danny Avila 102327313e modify docker setup 2023-03-06 15:57:01 -05:00
Danny Avila 08f59819cf modify docker setup 2023-03-06 15:56:25 -05:00
Danny Avila d9930dd2bc edit docker setup 2023-03-06 14:38:34 -05:00
Danny Avila 453301db0e chore: edit readme and docker-compose 2023-03-06 14:30:58 -05:00
Danny Avila 52e529dcbe complete docker setup 2023-03-06 14:04:06 -05:00
Danny Avila ff7b016190 initial docker detup 2023-03-06 12:49:22 -05:00
Danny Avila f5e079742a chore: reorganize client files for docker 2023-03-06 10:47:06 -05:00
Danny Avila affbaaf1a5 chore: server setup complete 2023-03-06 10:28:37 -05:00
Danny Avila fca546af63 reorganize dirs for dockerize 2023-03-06 10:15:07 -05:00
Danny AvilaandGitHub 062cd1d838 Merge pull request #6 from danny-avila/easy-config-setup
chore: misc. fixes/improvements before containerizing
2023-03-06 09:27:14 -05:00
Danny Avila 6ae154cc42 optimize convo paginations 2023-03-06 08:58:52 -05:00
Danny Avila c617b15bcd disable prompts until complete 2023-03-06 08:50:36 -05:00
Danny Avila b750bd2970 update roadmap 2023-03-06 08:41:09 -05:00
Daniel Avila 8d60cbdc7a feat: dynamic custom model saving/updating in dialog 2023-03-05 17:02:00 -05:00
Daniel Avila 15986c078b update readme with 2nd use case img 2023-03-05 16:23:35 -05:00
Daniel Avila 15f3972b9f remove models caching 2023-03-05 15:30:40 -05:00
Danny AvilaandGitHub 3224dca3b8 Merge pull request #5 from danny-avila/convo-pagination
feat: convo pagination in increments of 12
2023-03-05 14:42:43 -05:00
Daniel Avila a98367d27f convo fetching in increments of 12 2023-03-05 14:41:50 -05:00
Danny AvilaandGitHub 80319c7fcc Merge pull request #4 from danny-avila/dark-mode-match
feat: match more official stylings
2023-03-05 13:32:59 -05:00
Daniel Avila dae7f8d822 match more official stylings 2023-03-05 13:31:12 -05:00
Danny AvilaandGitHub c8d8484d86 Merge pull request #2 from danny-avila/customChatGPT
feature: customize chat gpt
2023-03-04 21:47:48 -05:00
Daniel Avila f40f80f030 update readme 2023-03-04 21:46:58 -05:00
Daniel Avila fc899da944 convo change also changes model selection 2023-03-04 21:10:45 -05:00
Daniel Avila d64f914552 complete customGpt 2023-03-04 20:48:59 -05:00
Daniel Avila b4b0c123ba prompt prefix and label state changes when customGpt selected 2023-03-04 18:00:12 -05:00
Daniel Avila 9c3a78f96b customGpts persist through localStorage 2023-03-04 17:39:06 -05:00
Daniel Avila 62bb6ea8f8 refactor model menu items into component 2023-03-03 21:33:09 -05:00
Daniel Avila 0bec306e59 remove punctuation from titles 2023-03-03 21:31:37 -05:00
Daniel Avila 42c74f254c reduce stylesheet clutter 2023-03-03 21:31:05 -05:00
Danny Avila 9a56130750 save custom settings in progress 2023-03-03 16:33:02 -05:00
Danny Avila 214228542a complete customChatGpt model selection 2023-03-03 15:52:06 -05:00
Danny AvilaandGitHub 0f9cfd0395 Merge pull request #1 from danny-avila/copyCode
complete copyCode for code blocks
2023-03-03 09:46:49 -05:00
Danny Avila 92648554ad complete copyCode for code blocks 2023-03-03 09:45:20 -05:00
Danny Avila ac2229fbb6 add more detail to titleConvo prompt 2023-03-03 09:07:29 -05:00
Danny Avila ec7aaf01a4 add hoverbuttons 2023-03-03 08:51:33 -05:00
Danny Avila 50ff96cbc5 copy code in progress 2023-03-02 16:31:00 -05:00
Danny Avila 0d8755a03a update readme 2023-03-02 16:08:26 -05:00
Danny Avila d235bb7b1b complete language detection for code blocks 2023-03-02 16:07:36 -05:00
Danny Avila c4d0787b49 chatgpt now titles conversations 2023-03-02 13:52:41 -05:00
Daniel Avila 897c384ac9 fix code parsing bug 2023-03-01 21:59:13 -05:00
Daniel Avila cbcc1a1bb4 readme update 2023-03-01 21:41:24 -05:00
Daniel Avila 1f270d349a add official api, remove davinci, organize root dir 2023-03-01 21:39:57 -05:00
Danny AvilaandGitHub 90ac43c8c5 Create LICENSE.md 2023-02-28 17:21:29 -05:00
Daniel Avila 1bba86f2e2 minor changes 2023-02-26 14:10:44 -05:00
Daniel Avila 8e5148b390 fix embed wrapper not invoking immediately 2023-02-25 10:33:26 -05:00
Daniel Avila 0ee31f0443 fix codeWrapper issue, will produce funky text with ` commenting 2023-02-25 10:16:21 -05:00
Daniel Avila ed699d2c06 wrap code in progress, revert old message handling 2023-02-25 09:06:26 -05:00
Daniel Avila 0f6fcd5928 organize modules 2023-02-25 09:04:32 -05:00
Daniel Avila 78c9f3dc74 trying different markdown library for proper list styling 2023-02-24 23:16:19 -05:00
Danny Avila 34ebd97c9d indicate initial gpt response 2023-02-24 16:31:59 -05:00
Danny Avila 61d898c387 fix bing chat not persisting after 1st invocation 2023-02-24 16:20:23 -05:00
Daniel Avila fd01fd3334 adds markdown support, fix code wrapping 2023-02-24 11:24:09 -05:00
Daniel Avila c58a9bbe93 finish highlight.js styling (for chatgpt) 2023-02-23 23:56:55 -05:00
Danny Avila e95e22de15 code highlighting in progress 2023-02-23 16:32:08 -05:00
Daniel Avila 187f7b5b03 handles in-line code in model response text (not multi-line yet) 2023-02-22 22:42:22 -05:00
Daniel Avila 1a6cddb8bb persist models in localStorage, minor changes 2023-02-22 21:30:48 -05:00
Daniel Avila e2a3ecbf88 update readme 2023-02-21 21:41:30 -05:00
Daniel Avila a516b38e27 adds linting, fix linter errors, abort automatic scrolling 2023-02-21 21:31:36 -05:00
Daniel Avila 16932b37c0 fix bingai bugs and add chatgptbrowser client 2023-02-21 21:30:56 -05:00
Daniel Avila 168d5e8075 fix linter errors 2023-02-21 19:40:28 -05:00
Daniel Avila 7b7548c905 persist bing ai conversation 2023-02-20 21:52:08 -05:00
Daniel Avila 81e52fed73 properly records bing conversations 2023-02-20 21:16:40 -05:00
Daniel Avila b4e22936ba update schema for bing options 2023-02-19 21:06:21 -05:00
Danny Avila ac2e897709 adds og bing icon 2023-02-16 14:06:18 -05:00
Danny Avila 5c7000c273 adds bing ai functionality, still need to code out message/convo storing since it returns a lot of useful parameters 2023-02-15 12:29:56 -05:00
Daniel Avila e0a26f52cd update readme 2023-02-15 00:06:10 -05:00
Daniel Avila f18317f7b7 useDocumentTitle changes title on first title change, scrollBar renders when expected 2023-02-14 21:23:14 -05:00
Daniel Avila 7347eda219 update readme 2023-02-14 19:46:24 -05:00
Daniel Avila e1b615835a new demo gif 2023-02-14 19:38:28 -05:00
Daniel Avila eaa0242e10 new demo gif 2023-02-14 19:11:47 -05:00
Daniel Avila 481927c5a3 adds demo gif 2023-02-14 19:03:48 -05:00
Daniel Avila 0833d795bd readme update and footer link update 2023-02-14 18:40:00 -05:00
Daniel Avila 7d7a4dd905 proxy server is down 2023-02-14 18:34:46 -05:00
Daniel Avila e520973203 draft readme 2023-02-14 18:31:29 -05:00
Danny Avila 9c225a9bb3 drafting readme 2023-02-14 16:33:22 -05:00
Danny Avila 742073fb89 refactor convos fetching to nav components, results in faster loading 2023-02-14 16:15:45 -05:00
Daniel Avila e4d1ff2523 rearrange convo fetching for performance in progress 2023-02-13 23:14:35 -05:00
Daniel Avila c00a2c902b front and backend logic for model switching 2023-02-13 21:15:28 -05:00
Daniel Avila a5afd5c48f animates scroll to bottom and debounces bottom 2023-02-13 20:13:59 -05:00
Daniel Avila 779f142058 fix static flag bug 2023-02-13 18:02:29 -05:00
Danny Avila 92b2109dc3 adds scrolltobottom button, still handling initial state when scrollbar is present 2023-02-13 16:31:18 -05:00
Danny Avila 583fd56ca5 adds shadcn-ui components, and model button to switch chat models 2023-02-13 15:58:35 -05:00
Danny Avila acaef39d12 refactors server route for brevity, state working, styling matching to chatgpt 2023-02-13 13:32:54 -05:00
Danny Avila dcf4f2a524 debugging convo state, useSWR now within convos component 2023-02-13 10:20:21 -05:00
Daniel Avila 0e5c7c7721 fixing convo state in progress 2023-02-12 22:57:59 -05:00
Daniel Avila 500b6c9fa8 adds functionality for waylaidwanderer chatgpt client 2023-02-12 22:24:36 -05:00
Daniel Avila 5b8c4452cc remove unused packages and minor changes 2023-02-12 16:48:15 -05:00
Daniel Avila ed44daf8b3 refactor express to use routes 2023-02-12 16:38:33 -05:00
Daniel Avila cd71bb0727 remove nodemon from server script 2023-02-11 17:35:07 -05:00
Daniel Avila 48c591a8b9 scrollbar styling 2023-02-11 13:48:48 -05:00
Daniel Avila 8eeac36b35 update press release 2023-02-11 13:05:48 -05:00
Daniel Avila 4cecbcf642 update press release 2023-02-11 13:02:30 -05:00
Daniel Avila 8432cbd293 update press release 2023-02-11 13:01:58 -05:00
Daniel Avila 256b60435f update press release, minor change in textchat component, remove unused package 2023-02-11 13:01:03 -05:00
Daniel Avila d9d5f4a509 delegate footer to own component 2023-02-11 12:54:02 -05:00
Daniel Avila e8c514fff9 delete updates state title 2023-02-11 12:49:07 -05:00
Daniel Avila ef4ec39a76 update press release notes 2023-02-11 11:54:17 -05:00
Daniel Avila 4304597cb4 small detail for renaming convos 2023-02-11 11:54:03 -05:00
Daniel Avila 7dd4e78bbf tests scrolling to top, show/hide templates 2023-02-11 11:37:20 -05:00
Daniel Avila 5af5a97d8f complete renaming functions, abstracts more svg, sets title to current convo title, adds a try again feature to errors 2023-02-11 10:22:15 -05:00
Daniel Avila 592b7629aa abstracts svgs, fiddling with prompt templates idea 2023-02-10 21:00:02 -05:00
Danny Avila cab4b70b7e took a screenshot of use case 2023-02-09 16:21:36 -05:00
Daniel Avila c36f752551 fix scrolling bug 2023-02-08 22:58:24 -05:00
Danny Avila 217bdb4865 client error handling, regen response without saving messages 2023-02-08 15:26:42 -05:00
Danny Avila 87685c3791 conversation conditional rendering 2023-02-08 11:05:54 -05:00
Danny Avila 0c6dc8665b dark mode alternates to light mode on dark theme, error state in progress 2023-02-08 10:49:38 -05:00
Danny Avila 5cd50e7826 handle textarea text state with redux and setText on example click 2023-02-08 09:59:01 -05:00
Danny Avila 581ee0e2ca adds favicon to head 2023-02-08 09:46:26 -05:00
Danny Avila d07047722e adds favicon to head 2023-02-08 09:44:10 -05:00
Danny Avila b5042a738e adds dark mode and logs errors in message response 2023-02-08 09:15:47 -05:00
Danny Avila 58498ed951 clears conversations and no new line on enter 2023-02-08 08:27:23 -05:00
Daniel Avila 51d93da3f8 adds server and client error handling, clear convos in progress 2023-02-08 00:02:29 -05:00
Daniel Avila 511ac948b4 clear all convos in progress, async code refactored in debugging warning 2023-02-07 19:07:48 -05:00
Daniel Avila 053368646d useeffect triggers react warning 'expected static flag was missing' but workarounds don't look as nice 2023-02-07 19:07:12 -05:00
Danny Avila 6842ac880c optimistic ui for message sending and submit state 2023-02-07 16:22:35 -05:00
Danny Avila 9d41ed4615 reorganize components and add root import plugin 2023-02-07 10:26:19 -05:00
Danny Avila faf8800e67 remove sourcemap error 2023-02-07 09:59:45 -05:00
Danny Avila 36ac055ae5 Complete rewire state to redux 2023-02-07 09:41:54 -05:00
Daniel Avila 7978ddd871 adding redux in progress 2023-02-07 00:05:00 -05:00
Daniel Avila 85efaa4173 adding redux to reduce prop drilling and allow for advanced conversation state 2023-02-06 21:18:28 -05:00
Daniel Avila 6e3f63ee46 adding clear methods for convos 2023-02-06 21:17:46 -05:00
Daniel Avila c7c50dbbab convo successfully changes on convo click 2023-02-06 18:25:11 -05:00
Danny Avila d35ed31b20 convo selection handling in progress 2023-02-06 16:28:50 -05:00
Danny Avila c794287ced load convos on message send & other conditional rendering 2023-02-06 16:00:59 -05:00
Danny Avila 2869638cc0 get convos endpoint 2023-02-06 15:17:54 -05:00
Danny Avila c821d1eee5 made model method one line shorter 2023-02-06 14:58:43 -05:00
Danny Avila 4e6b6be15c titles conversations based on first two messages 2023-02-06 14:57:47 -05:00
Danny Avila 511c8c9599 saves all conversations with messages 2023-02-06 14:05:02 -05:00
Danny Avila 232a823b9b store all messages to mongoDB 2023-02-06 13:28:21 -05:00
Danny Avila f2003da779 layout mimics actual chatgpt ui 2023-02-06 13:27:28 -05:00
Daniel Avila 254f9d7e26 adds db connectivity and saving, need to process user messages still 2023-02-05 23:05:07 -05:00
Daniel Avila f889f23792 chatgpt is taking shape, convo persists, layout mimics original 2023-02-05 19:41:24 -05:00
Daniel Avila 3a199757ae sse events on text submit 2023-02-05 15:29:35 -05:00
Daniel Avila 27f7276fc8 working SSE stream 2023-02-05 15:26:59 -05:00
Daniel Avila 81866f197a webpack config for polyfills 2023-02-04 20:49:58 -05:00
Daniel Avila 502bdc5dd5 building potential Server side events 2023-02-04 20:49:42 -05:00
Daniel Avila 641dcfad73 chat methods defined, also test file for library testing 2023-02-04 20:49:01 -05:00
Daniel Avila 7c4e7ab07a abstracts textarea 2023-02-04 20:48:33 -05:00
Daniel Avila 7b9f3ee9c2 update env variables and build 2023-02-04 20:09:58 -05:00
Daniel Avila 39781f5318 testing chatgpt logic 2023-02-04 20:09:02 -05:00
Daniel Avila 521a8eb704 templating model 2023-02-04 20:08:46 -05:00
Daniel Avila 92860a1bb4 adds express server 2023-02-04 20:08:28 -05:00
Daniel Avila 037a12a282 logs textarea input 2023-02-04 20:07:37 -05:00
Daniel Avila 5dc2d5efba updates to react 18 method createRoot 2023-02-04 20:07:08 -05:00
Daniel Avila 21a6a0587c adds basic layout 2023-02-04 19:19:53 -05:00
Daniel Avila 0fb3a1cab3 adds tailwindcss 2023-02-04 18:17:47 -05:00
Daniel Avila 74232d7671 initial commit, project is ready to start 2023-02-04 17:17:17 -05:00
hackreactor-bot 466dd01327 (init) 2022-10-20 11:45:11 +00:00
1525 changed files with 48491 additions and 97711 deletions
-25
View File
@@ -1,25 +0,0 @@
{
"name": "Frappe Bench",
"forwardPorts": [
8000,
9000,
6787
],
"remoteUser": "frappe",
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"debug.node.autoAttach": "disabled"
},
"dockerComposeFile": "./docker-compose.yml",
"service": "frappe",
"workspaceFolder": "/workspace/frappe-bench",
"postCreateCommand": "bash /workspace/scripts/init.sh",
"shutdownAction": "stopCompose",
"extensions": [
"ms-python.python",
"ms-vscode.live-server",
"grapecity.gc-excelviewer",
"mtxr.sqltools",
"visualstudioexptteam.vscodeintellicode"
]
}
-46
View File
@@ -1,46 +0,0 @@
version: "3.7"
services:
mariadb:
image: mariadb:10.6
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
- --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6
environment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- mariadb-data:/var/lib/mysql
# Enable PostgreSQL only if you use it, see development/README.md for more information.
# postgresql:
# image: postgres:11.8
# environment:
# POSTGRES_PASSWORD: 123
# volumes:
# - postgresql-data:/var/lib/postgresql/data
redis-cache:
image: redis:alpine
redis-queue:
image: redis:alpine
redis-socketio:
image: redis:alpine
frappe:
image: frappe/bench:latest
command: sleep infinity
environment:
- SHELL=/bin/bash
volumes:
- ..:/workspace:cached
working_dir: /workspace/frappe-bench
ports:
- 8000-8005:8000-8005
- 9000-9005:9000-9005
volumes:
mariadb-data:
postgresql-data:
+3
View File
@@ -0,0 +1,3 @@
**/node_modules
api/.env
client/dist/images
-38
View File
@@ -1,38 +0,0 @@
[flake8]
ignore =
E121,
E126,
E127,
E128,
E203,
E225,
E226,
E231,
E241,
E251,
E261,
E265,
E302,
E303,
E305,
E402,
E501,
E741,
W291,
W292,
W293,
W391,
W503,
W504,
F403,
F811,
B007,
B950,
W191,
E124, # closing bracket, irritating while writing QB code
E131, # continuation line unaligned for hanging indent
E123, # closing bracket does not match indentation of opening bracket's line
E101, # ensured by use of black
max-line-length = 200
exclude=.github/helper/semgrep_rules
+2 -3
View File
@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: [The-Commit-Company]
github: [danny-avila]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
@@ -8,7 +8,6 @@ tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
-75
View File
@@ -1,75 +0,0 @@
[flake8]
ignore =
B001,
B007,
B009,
B010,
B950,
E101,
E111,
E114,
E116,
E117,
E121,
E122,
E123,
E124,
E125,
E126,
E127,
E128,
E131,
E201,
E202,
E203,
E211,
E221,
E222,
E223,
E224,
E225,
E226,
E228,
E231,
E241,
E242,
E251,
E261,
E262,
E265,
E266,
E271,
E272,
E273,
E274,
E301,
E302,
E303,
E305,
E306,
E402,
E501,
E502,
E701,
E702,
E703,
E741,
F401,
F403,
F405,
F811,
W191,
W291,
W292,
W293,
W391,
W503,
W504,
E711,
E129,
F841,
E713,
E712,
max-line-length = 200
-46
View File
@@ -1,46 +0,0 @@
#!/bin/bash
set -e
cd ~ || exit
echo "Setting Up Bench..."
pip install frappe-bench
bench -v init frappe-bench --skip-assets --python "$(which python)"
cd ./frappe-bench || exit
bench -v setup requirements
echo "Setting Up Raven..."
bench get-app raven "${GITHUB_WORKSPACE}"
echo "Setting Up Sites & Database..."
mkdir ~/frappe-bench/sites/raven.test
cp "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/raven.test/site_config.json
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "SET GLOBAL character_set_server = 'utf8mb4'";
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "CREATE DATABASE test_raven";
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "CREATE USER 'test_raven'@'localhost' IDENTIFIED BY 'test_raven'";
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "GRANT ALL PRIVILEGES ON \`test_raven\`.* TO 'test_raven'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "FLUSH PRIVILEGES";
echo "Setting Up Procfile..."
sed -i 's/^watch:/# watch:/g' Procfile
sed -i 's/^schedule:/# schedule:/g' Procfile
echo "Starting Bench..."
bench start &> bench_start.log &
CI=Yes bench build &
build_pid=$!
bench --site raven.test reinstall --yes
bench --site raven.test install-app raven
wait $build_pid
-14
View File
@@ -1,14 +0,0 @@
#!/bin/bash
set -e
echo "Setting Up System Dependencies..."
sudo apt update
sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client
install_wkhtmltopdf() {
wget -q https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
}
install_wkhtmltopdf &
-19
View File
@@ -1,19 +0,0 @@
{
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_raven",
"db_password": "test_raven",
"allow_tests": true,
"db_type": "mariadb",
"auto_email_id": "test@example.com",
"mail_server": "smtp.example.com",
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
"root_password": "123",
"host_name": "http://raven.test:8000",
"monitor": 1,
"server_script_enabled": true,
"mute_emails": true
}
-7
View File
@@ -1,7 +0,0 @@
# Add 'UI' label to all PRs that touch ts / tsx files in the 'src' directory.
"UI":
- "frontend/src/**/*.{ts,tsx,css}"
# Add 'backend' label to all PRs that touch json / py files in the 'raven' directory.
"backend":
- "raven/**/*.{json,py}"
-11
View File
@@ -1,11 +0,0 @@
# configuration to force mariadb to use utf8mb4 charecter set, as required by frappe
# This file need to be placed at /etc/mysql/conf.d/ in the mariadb container as a volume
# See .github/wotkflows/ci.yml to see how it is used
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[mysql]
default-character-set = utf8mb4
+72
View File
@@ -0,0 +1,72 @@
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
tests_e2e:
name: Run end-to-end tests
timeout-minutes: 60
runs-on: ubuntu-latest
env:
BINGAI_TOKEN: ${{ secrets.BINGAI_TOKEN }}
CHATGPT_TOKEN: ${{ secrets.CHATGPT_TOKEN }}
MONGO_URI: ${{ secrets.MONGO_URI }}
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
steps:
- 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 API dependencies
working-directory: ./api
run: npm ci
- 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
- name: Build Client
working-directory: ./client
run: npm run build
- name: Install global dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- 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
+28
View File
@@ -0,0 +1,28 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
tests_e2e:
name: Run end-to-end tests
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- 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
-64
View File
@@ -1,64 +0,0 @@
name: Build Container Image
on:
workflow_dispatch:
push:
branches:
- main
tags:
- "*"
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
arch: [amd64, arm64]
permissions:
packages: write
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/${{ matrix.arch }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set Branch
run: |
export APPS_JSON='[{"url": "https://github.com/The-Commit-Company/raven","branch": "main"}]'
echo "APPS_JSON_BASE64=$(echo $APPS_JSON | base64 -w 0)" >> $GITHUB_ENV
echo "FRAPPE_BRANCH=version-15" >> $GITHUB_ENV
- name: Set Image Tag
run: |
echo "IMAGE_TAG=stable" >> $GITHUB_ENV
- uses: actions/checkout@v4
with:
repository: frappe/frappe_docker
path: builds
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
context: builds
file: builds/images/layered/Containerfile
tags: >
ghcr.io/${{ github.repository }}:${{ github.ref_name }},
ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }}
build-args: |
"FRAPPE_BRANCH=${{ env.FRAPPE_BRANCH }}"
"APPS_JSON_BASE64=${{ env.APPS_JSON_BASE64 }}"
-100
View File
@@ -1,100 +0,0 @@
name: CI
on:
push:
branches:
- main
paths:
- raven/**
pull_request:
paths:
- raven/**
jobs:
tests:
runs-on: ubuntu-latest
name: Server
services:
redis-cache:
image: redis:alpine
ports:
- 13000:6379
redis-queue:
image: redis:alpine
ports:
- 11000:6379
redis-socketio:
image: redis:alpine
ports:
- 12000:6379
mariadb:
image: anandology/mariadb-utf8mb4:10.3
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18
check-latest: true
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: 'echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT'
- uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Setup
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
pip install frappe-bench
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- name: Install
working-directory: /home/runner/frappe-bench
run: |
bench get-app https://github.com/The-Commit-Company/Raven.git $GITHUB_WORKSPACE
bench setup requirements
bench new-site --db-root-password root --admin-password admin test_site
bench --site test_site install-app raven
bench build
env:
CI: "Yes"
- name: Run Tests
working-directory: /home/runner/frappe-bench
run: |
bench --site test_site set-config allow_tests true
bench --site test_site run-tests --app raven
env:
TYPE: server
-38
View File
@@ -1,38 +0,0 @@
name: Linters
on:
pull_request:
paths:
- raven/**
workflow_dispatch:
push:
branches: [main]
paths:
- raven/**
jobs:
linters:
name: Semantic Commits
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
- name: Install and Run Pre-commit
uses: pre-commit/action@v3.0.1
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules
+64 -16
View File
@@ -1,19 +1,67 @@
.DS_Store
*.pyc
*.egg-info
*.swp
tags
node_modules
### node etc ###
# Logs
data-node
meili_data
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled Dirs (http://nodejs.org/api/addons.html)
build/
dist/
mobile/dev-dist
raven/public/raven
raven/public/raven_v3
raven/public/node_modules
raven/www/raven.html
raven/www/raven_v3.html
*.env
public/main.js
public/main.js.map
public/main.js.LICENSE.txt
client/public/main.js
client/public/main.js.map
client/public/main.js.LICENSE.txt
# Dependency directorys
# Deployed apps should consider commenting these lines out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules/
meili_data/
api/node_modules/
client/node_modules/
bower_components/
# Floobits
.floo
.floobit
.floo
.flooignore
# Environment
.npmrc
.env
cache.json
api/data/
owner.yml
archive
.vscode/settings.json
src/style - official.css
/e2e/specs/.test-results/
/e2e/playwright-report/
/playwright/.cache/
.DS_Store
*.code-workspace
.idea
# meilisearch
meilisearch
data.ms/*
apps/mobile/ios
apps/mobile/android
apps/mobile/.expo
-52
View File
@@ -1,52 +0,0 @@
exclude: "node_modules|.git"
default_stages: [pre-commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
files: "raven.*"
exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
- id: check-yaml
- id: check-merge-conflict
- id: check-ast
- id: check-json
files: "raven.*"
- id: check-toml
- id: debug-statements
- repo: https://github.com/asottile/pyupgrade
rev: v2.34.0
hooks:
- id: pyupgrade
files: "raven.*"
args: ["--py310-plus"]
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
files: "raven.*"
additional_dependencies: ["click==8.0.4"]
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
files: "raven.*"
additional_dependencies: ["flake8-bugbear", "flake8-tuple"]
args: ["--config", ".github/helper/flake8.conf"]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
files: "raven.*"
exclude: ".*setup.py$"
ci:
autoupdate_schedule: weekly
skip: []
submodules: false
-2
View File
@@ -1,2 +0,0 @@
.devcontainer/docker-compose.yml
frontend/
-88
View File
@@ -1,88 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Bench Web",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
"args": [
"frappe",
"serve",
"--port",
"8000",
"--noreload",
"--nothreading"
],
"cwd": "${workspaceFolder}/frappe-bench/sites",
"env": {
"DEV_SERVER": "1"
}
},
{
"name": "Bench Default Worker",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
"args": [
"frappe",
"worker",
"--queue",
"default"
],
"cwd": "${workspaceFolder}/frappe-bench/sites",
"env": {
"DEV_SERVER": "1"
}
},
{
"name": "Bench Short Worker",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
"args": [
"frappe",
"worker",
"--queue",
"short"
],
"cwd": "${workspaceFolder}/frappe-bench/sites",
"env": {
"DEV_SERVER": "1"
}
},
{
"name": "Bench Long Worker",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
"args": [
"frappe",
"worker",
"--queue",
"long"
],
"cwd": "${workspaceFolder}/frappe-bench/sites",
"env": {
"DEV_SERVER": "1"
}
},
{
"name": "Honcho SocketIO Watch Schedule Worker",
"type": "python",
"request": "launch",
"program": "/home/frappe/.local/bin/honcho",
"cwd": "${workspaceFolder}/frappe-bench",
"console": "internalConsole",
"args": [
"start",
"socketio",
"watch",
"schedule",
"worker_short",
"worker_long",
"worker_default"
]
}
]
}
-18
View File
@@ -1,18 +0,0 @@
{
"python.defaultInterpreterPath": "frappe-bench/env/bin/python",
"debug.node.autoAttach": "disabled",
"sqltools.connections": [
{
"mysqlOptions": {
"authProtocol": "default"
},
"previewLimit": 50,
"server": "mariadb",
"port": 3306,
"driver": "MariaDB",
"name": "MariaDB",
"username": "root",
"password": "123"
}
]
}
+136
View File
@@ -0,0 +1,136 @@
# # 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)
+26
View File
@@ -0,0 +1,26 @@
# 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)
+32
View File
@@ -0,0 +1,32 @@
# Base node image
FROM node:19-alpine AS base
WORKDIR /api
COPY /api/package*.json /api/
WORKDIR /client
COPY /client/package*.json /client/
WORKDIR /
COPY /package*.json /
RUN npm ci
# React client build
FROM base AS react-client
WORKDIR /client
COPY /client/ /client/
ENV NODE_OPTIONS="--max-old-space-size=2048"
RUN npm run build
# Node API setup
FROM base AS node-api
WORKDIR /api
COPY /api/ /api/
COPY --from=react-client /client/dist /client/dist
EXPOSE 3080
ENV HOST=0.0.0.0
CMD ["npm", "start"]
# 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
COPY client/nginx.conf /etc/nginx/conf.d/default.conf
ENTRYPOINT ["nginx", "-g", "daemon off;"]
+35
View File
@@ -0,0 +1,35 @@
# ./Dockerfile
FROM node:19-alpine
WORKDIR /app
# Copy package.json files for client and api
COPY /client/package*.json /app/client/
COPY /api/package*.json /app/api/
COPY /package*.json /app/
# Install dependencies for both client and api
RUN npm ci
# Copy the current directory contents into the container
COPY /client/ /app/client/
COPY /api/ /app/api/
# Set the memory limit for Node.js
ENV NODE_OPTIONS="--max-old-space-size=2048"
# Build artifacts for the client
RUN cd /app/client && npm run build
# Create the necessary directory and copy the client side code to the api directory
RUN mkdir -p /app/api/client && cp -R /app/client/dist /app/api/client/dist
# Make port 3080 available to the world outside this container
EXPOSE 3080
# Expose the server to 0.0.0.0
ENV HOST=0.0.0.0
# Run the app when the container launches
WORKDIR /app/api
CMD ["npm", "start"]
-661
View File
@@ -1,661 +0,0 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
+27
View File
@@ -0,0 +1,27 @@
# 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
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
##
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
##
## [Go Back to ReadMe](README.md)
-18
View File
@@ -1,18 +0,0 @@
include MANIFEST.in
include requirements.txt
include *.json
include *.md
include *.py
include *.txt
recursive-include raven *.css
recursive-include raven *.csv
recursive-include raven *.html
recursive-include raven *.ico
recursive-include raven *.js
recursive-include raven *.json
recursive-include raven *.md
recursive-include raven *.png
recursive-include raven *.py
recursive-include raven *.svg
recursive-include raven *.txt
recursive-exclude raven *.pyc
+130 -222
View File
@@ -1,271 +1,179 @@
<p align="center">
<a href="https://github.com/The-Commit-Company/raven">
<img src="raven_logo.png" alt="Raven logo" height="100" />
</a>
<hr />
<p align="center">Enterprise-first messaging platform that seamlessly integrates with your ERP
<br />
<br />
<a href="https://frappecloud.com/dashboard/signup?product=raven"><strong>Install on Frappe Cloud</strong></a>
<br />
<a href="https://ravenchat.ai">Website (ravenchat.ai)</a>
<br />
<a href="https://github.com/The-Commit-Company/raven/issues">Issues</a>
·
<a href="https://community.ravenapp.cloud">Community & Support</a>
·
<a href="https://github.com/sponsors/The-Commit-Company?frequency=one-time">Sponsor Us!</a>
</p>
</p>
<p align="center">
<a href="https://github.com/The-Commit-Company/Raven/blob/main/LICENSE">
<img alt="license" src="https://img.shields.io/badge/license-AGPLv3-blue">
<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>
<a href="https://github.com/The-Commit-Company/raven/stargazers"><img src="https://img.shields.io/github/stars/The-Commit-Company/raven" alt="Github Stars"></a>
<a href="https://github.com/The-Commit-Company/raven/pulse"><img src="https://img.shields.io/github/commit-activity/m/The-Commit-Company/raven" alt="Commits-per-month"></a>
</p>
<p align="center">
<img src="screenshots/RavenUIGraphic.png" alt="A graphic showing how the Raven interface looks on the web" />
<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>
<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">
</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.
![clone3](https://user-images.githubusercontent.com/110412045/230538752-9b99dc6e-cd02-483a-bff0-6c6e780fa7ae.gif)
Raven is an open-source messaging platform that brings your team's conversations and information into one centralized place, enhancing collaboration and productivity. Whether you're at your desk or on the move, Raven keeps you connected on any device.
# Features
It's built using [Frappe Framework](https://frappeframework.com) and offers deep integrations with other Frappe apps like [ERPNext](https://frappe.io/erpnext) and [FrappeHR](https://frappe.io/hr).
- 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
<hr>
##
# Sponsors
## Key Features
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>
1. **Organize conversations in channels:** Organize your conversations by topics, projects, or any category that suits your workflow. Send direct messages to other users or create channels for group discussions to keep everyone informed and engaged.
2. **Craft your messages with rich text:** Easily send messages with formatting, highlights, links, emojis, code blocks, mentions and more
3. **Share images, documents, GIFs and more:** Need to share a file? Simply drag and drop it onto the screen. You can also find and share memes using our GIF search
4. **Reactions, Replies, and Threads:** Respond to messages with a reaction (custom emojis supported), inline replies or create threads for longer discussions.
5. **Integrate Seamlessly:** Raven works effortlessly with other Frappe apps, allowing you to share documents from ERPNext with customizable previews, trigger notifications based on document events, perform workflow actions and, print documents directly within chats.
6. **Agents, AI and more:** With Raven AI, automate tasks, extract data from files and images, and execute complex, multistep processes with just a message to an agent. Build your own agents without writing a single line of code to streamline your workflows.
7. **Supports Dark Mode and Mobile**: Raven allows users to customize the app's appearance as well as offers a mobile app.
8. **Authentication and Security**: Supports all popular OAuth providers, custom OAuth providers, Login with email link, 2FA on Email/SMS/Auth App and LDAP.
##
<hr>
## **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/.
### Tech Stack
**Alternatively**
##### Common across web and mobile
- 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
- [Frappe Framework](https://frappeframework.com) - an open-source full stack development framework. It uses Python, MariaDB/Postgres, socket.io and Redis
- [React](https://react.dev)
- [Frappe React SDK](https://github.com/The-Commit-Company/frappe-react-sdk) - React Hooks library that we created/maintain to handle auth, data fetching and API calls to the Frappe Framework backend
- [Tailwind CSS](https://tailwindcss.com/) -
- [Firebase](https://firebase.google.com) - Push notifications, Crash Analytics and Performance Monitoring
**Note:**
##### Web
- 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>
- [RadixUI](https://www.radix-ui.com) - Accessible UI components for the web
- [TipTap](https://www.tiptap.dev) - Rich Text Editor
- [React Icons](https://react-icons.github.io/react-icons/) - icon library
- [emoji-mart](https://github.com/missive/emoji-mart) - Emoji Picker library
---
##### Mobile
- [React Native](https://reactnative.dev)
- [Expo](https://expo.dev) - for all plugins/utilities and deployment
- [NativeWind](https://www.nativewind.dev) - using TailwindCSS but on React Native
- [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/) - Animations for React Native
- [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler) - to handle touch and gestures on React Native
- [Zeego](https://zeego.dev) - Dropdown menus for React Native
<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).
<br>
New contributors to the project include:
## Production setup
- @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).
### Managed Hosting
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications with peace of mind.
The [full changelog can be found here](https://github.com/danny-avila/chatgpt-clone/compare/v0.4.3...v0.4.4)
It takes care of installation, setup, upgrades, monitoring, maintenance and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
⚠️ **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.
<div>
<a href="https://frappecloud.com/dashboard/signup?product=raven" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
</picture>
</a>
</div>
⚠️ **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.
### Self Hosting
For discussion and suggestion you can join us: **[community discord server](https://discord.gg/NGaa9RPCft)**
</details>
Follow these steps to set up Raven in production:
[Past Updates](CHANGELOG.md)
##
**Step 1**: Download the easy install script
<h1>Table of Contents</h1>
```bash
wget https://frappe.io/easy-install.py
```
<details open>
<summary><strong>Getting Started</strong></summary>
**Step 2**: Run the deployment command
* [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)
</details>
```bash
python3 ./easy-install.py deploy \
--project=raven_prod_setup \
--email=your_email.example.com \
--image=ghcr.io/the-commit-company/raven \
--version=stable \
--app=raven \
--sitename subdomain.domain.tld
```
<details>
<summary><strong>General Information</strong></summary>
Replace the following parameters with your values:
- `your_email.example.com`: Your email address
- `subdomain.domain.tld`: Your domain name where Raven will be hosted
* [Project Origin](documents/general_info/project_origin.md)
* [Multilingual Information](documents/general_info/multilingual_information.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)
</details>
The script will set up a production-ready instance of Raven with all the necessary configurations in about 5 minutes.
<details>
<summary><strong>Features</strong></summary>
* [User Auth System](documents/features/user_auth_system.md)
* [Proxy](documents/features/proxy.md)
</details>
## Development Setup
<details>
<summary><strong>Cloud Deployment</strong></summary>
### Docker
* [Heroku](documents/deployment/heroku.md)
</details>
You need Docker, docker-compose and git setup on your machine. Refer [Docker documentation](https://docs.docker.com/). After that, follow below steps:
<details>
<summary><strong>Contributions</strong></summary>
**Step 1**: Setup folder and download the required files
* [Code of Conduct](documents/contributions/code_of_conduct.md)
* [Contributor Guidelines](documents/contributions/contributor_guidelines.md)
* [Documentation Guidelines](documents/contributions/documentation_guidelines.md)
* [Code Standards and Conventions](documents/contributions/coding_conventions.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)
</details>
mkdir raven
cd raven
<details>
<summary><strong>Report Templates</strong></summary>
# Download the docker-compose file
wget -O docker-compose.yml https://raw.githubusercontent.com/The-Commit-Company/raven/develop/docker/docker-compose.yml
* [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>
# Download the setup script
wget -O init.sh https://raw.githubusercontent.com/The-Commit-Company/raven/develop/docker/init.sh
##
### [Alternative Documentation](https://chatgpt-clone.gitbook.io/chatgpt-clone-docs/get-started/docker)
**Step 2**: Run the container and daemonize it
##
docker compose up -d
## Star History
**Step 3**: The site [http://raven.localhost:8000/raven](http://raven.localhost:8000/raven) should now be available. The default credentials are:
- Username: Administrator
- Password: admin
### Local
To setup the repository locally follow the steps mentioned below:
1. Install bench and setup a `frappe-bench` directory by following the [Installation Steps](https://frappeframework.com/docs/user/en/installation)
2. Start the server by running
```bash
bench start
```
3. In a separate terminal window, create a new site by running
```bash
bench new-site raven.test
```
4. Map your site to localhost with the command
```bash
bench --site raven.test add-to-hosts
```
5. Download the Raven app. Run
```bash
bench get-app https://github.com/The-Commit-Company/raven
```
6. Install the app on the site
```bash
bench --site raven.test install-app raven
```
7. Now open the URL `http://raven.test:8000/raven` in your browser, you should see the app running
###### Other Local Configurations
Make sure that you have enabled [developer mode](https://frappeframework.com/how-to-enable-developer-mode-in-frappe) in your Frappe site config. You can do so by running the following in your bench directory:
```bash
bench set-config -g developer_mode 1
```
You also need to disable CSRF (add `ignore_csrf: 1` in your `site_config.json`) since the React web server will not have any CSRF token in live reload mode. Please note that this is only for the local dev setup and is **not meant for Production**.
```bash
bench --site raven.test set-config ignore_csrf 1
```
##### Web App
Raven's **web app** is a React based single page app located in the `/frontend` directory. It uses Vite to compile the app.
If you want to make changes to the frontend with hot reloading, follow the steps below:
1. Navigate to the apps/raven folder in your bench directory
```bash
cd apps/raven
```
2. Start the local web server
```bash
yarn run dev
```
This will start a local server at `http://localhost:8080` where the frontend app can be previewed in real time as you make changes. To make sure the setup works correctly, add a `.env.local` file in the `/frontend` directory with the following contents:
```
VITE_BASE_NAME='' # This needs to be set as empty
VITE_SOCKET_PORT=9000 # This is the port on which socket.io is running. It's usually the main Frappe server port (8000) but in the 9000 series
VITE_SITE_NAME='raven.test' # This is the same of the site
```
##### Mobile App
Raven's **mobile app** is a React Native based app built using Expo. It requires an [Expo Development Build](https://docs.expo.dev/develop/development-builds/introduction/) and will not run on Expo Go.
To get started, you need to install the [Expo CLI](https://docs.expo.dev/develop/tools/#expo-cli)
```bash
npm install -g eas-cli
```
You also need to have Xcode and/or Android Studio installed locally depending on your platform of choice.
The mobile app is located under the `apps/mobile` directory.
To start the development server and run it on a simulator, you can run
```bash
yarn run ios
```
or
```
yarn run android
```
This *should* start the emulators with the app running. For more info, please refer to the [Expo documentation](https://docs.expo.dev/get-started/introduction/)
## Forum and Support
- [Community Forum & Support](https://community.ravenapp.cloud)
- [YouTube](https://www.youtube.com/@TheCommitCompany)
<hr>
## Repo Activity
![Alt](https://repobeats.axiom.co/api/embed/8e66d9bc24dd93b7a61c804484af54233225d857.svg "Repobeats analytics image")
[![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)
## Contributors
Contributions and suggestions bug reports and fixes are welcome!
Please read the documentation before you do!
<a href="https://github.com/The-Commit-Company/raven/graphs/contributors">
<img src="https://contrib.rocks/image?repo=The-Commit-Company/raven" />
For new features, components, or extensions, please open an issue and discuss before sending a PR.
- Join the [Discord community](https://discord.gg/NGaa9RPCft)
This project exists in its current state thanks to all the people who contribute
---
<a href="https://github.com/danny-avila/chatgpt-clone/graphs/contributors">
<img src="https://contrib.rocks/image?repo=danny-avila/chatgpt-clone" />
</a>
## Reporting Bugs
If you find any bugs, feel free to report them here on [GitHub Issues](https://github.com/The-Commit-Company/raven/issues). Make sure you share enough information (app screenshots, browser console screenshots, stack traces, etc) for project maintainers to replicate your bug.
<hr>
## License
AGPLv3
+176
View File
@@ -0,0 +1,176 @@
##########################
# 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,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 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
+39
View File
@@ -0,0 +1,39 @@
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'
}
};
+22
View File
@@ -0,0 +1,22 @@
{
"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"
}
+79
View File
@@ -0,0 +1,79 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const askBing = async ({
text,
parentMessageId,
conversationId,
jailbreak,
jailbreakConversationId,
context,
systemMessage,
conversationSignature,
clientId,
invocationId,
toneStyle,
token,
onProgress
}) => {
const { BingAIClient } = await import('@waylaidwanderer/chatgpt-api');
const store = {
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,
// If the above doesn't work, provide all your cookies as a string instead
// cookies: '',
debug: false,
cache: store,
host: process.env.BINGAI_HOST || null,
proxy: process.env.PROXY || null
});
let options = {};
if (jailbreakConversationId == 'false') {
jailbreakConversationId = false;
}
if (jailbreak)
options = {
jailbreakConversationId: jailbreakConversationId || jailbreak,
context,
systemMessage,
parentMessageId,
toneStyle,
onProgress
};
else {
options = {
conversationId,
context,
systemMessage,
parentMessageId,
toneStyle,
onProgress
};
// don't give those parameters for new conversation
// for new conversation, conversationSignature always is null
if (conversationSignature) {
options.conversationSignature = conversationSignature;
options.clientId = clientId;
options.invocationId = invocationId;
}
}
console.log('bing options', options);
const res = await bingAIClient.sendMessage(text, options);
return res;
// for reference:
// https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/demos/use-bing-client.js
};
module.exports = { askBing };
+47
View File
@@ -0,0 +1,47 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const browserClient = async ({
text,
parentMessageId,
conversationId,
model,
token,
onProgress,
abortController,
userId
}) => {
const { ChatGPTBrowserClient } = await import('@waylaidwanderer/chatgpt-api');
const store = {
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',
// Access token from https://chat.openai.com/api/auth/session
accessToken: process.env.CHATGPT_TOKEN == 'user_provided' ? token : process.env.CHATGPT_TOKEN ?? null,
model: model,
debug: false,
proxy: process.env.PROXY || null,
user: userId
};
const client = new ChatGPTBrowserClient(clientOptions, store);
let options = { onProgress, abortController };
if (!!parentMessageId && !!conversationId) {
options = { ...options, parentMessageId, conversationId };
}
console.log('gptBrowser clientOptions', clientOptions);
if (parentMessageId === '00000000-0000-0000-0000-000000000000') {
delete options.conversationId;
}
const res = await client.sendMessage(text, options);
return res;
};
module.exports = { browserClient };
+67
View File
@@ -0,0 +1,67 @@
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 };
+390
View File
@@ -0,0 +1,390 @@
const crypto = require('crypto');
const TextStream = require('../stream');
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 tokenizersCache = {};
class GoogleAgent {
constructor(credentials, options = {}) {
this.client_email = credentials.client_email;
this.project_id = credentials.project_id;
this.private_key = credentials.private_key;
this.setOptions(options);
this.currentDateString = new Date().toLocaleDateString('en-us', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
constructUrl() {
return `https://us-central1-aiplatform.googleapis.com/v1/projects/${this.project_id}/locations/us-central1/publishers/google/models/${this.modelOptions.model}:predict`;
}
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;
}
this.options.examples = this.options.examples.filter(
obj => obj.input.content !== '' && obj.output.content !== ''
);
const modelOptions = this.options.modelOptions || {};
this.modelOptions = {
...modelOptions,
// set some good defaults (check for undefined in some cases because they may be 0)
model: modelOptions.model || 'chat-bison',
temperature: typeof modelOptions.temperature === 'undefined' ? 0.2 : modelOptions.temperature, // 0 - 1, 0.2 is recommended
topP: typeof modelOptions.topP === 'undefined' ? 0.95 : modelOptions.topP, // 0 - 1, default: 0.95
topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK // 1-40, default: 40
// stop: modelOptions.stop // no stop method for now
};
this.isChatModel = this.modelOptions.model.startsWith('chat-');
const { isChatModel } = this;
this.isTextModel = this.modelOptions.model.startsWith('text-');
const { isTextModel } = this;
this.maxContextTokens = this.options.maxContextTokens || (isTextModel ? 8000 : 4096);
// 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;
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.userLabel = this.options.userLabel || 'User';
this.modelLabel = this.options.modelLabel || 'Assistant';
if (isChatModel) {
// 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 (isTextModel) {
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 `modelLabel` because I've never seen it happen
this.modelOptions.stop = stopTokens;
}
if (this.options.reverseProxyUrl) {
this.completionsUrl = this.options.reverseProxyUrl;
} else {
this.completionsUrl = this.constructUrl();
}
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 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 = [] }) {
let payload = {
instances: [
{
messages: [...messages, { author: this.userLabel, content: input }]
}
],
parameters: this.options.modelOptions
};
if (this.options.promptPrefix) {
payload.instances[0].context = this.options.promptPrefix;
}
if (this.options.examples.length > 0) {
payload.instances[0].examples = this.options.examples;
}
if (this.isTextModel) {
payload.instances = [
{
prompt: input
}
];
}
if (this.options.debug) {
console.debug('buildPayload');
console.dir(payload, { depth: null });
}
return payload;
}
async getCompletion(input, messages = [], abortController = null) {
if (!abortController) {
abortController = new AbortController();
}
const { debug } = this.options;
const url = this.completionsUrl;
if (debug) {
console.debug();
console.debug(url);
console.debug(this.modelOptions);
console.debug();
}
const opts = {
method: 'POST',
agent: new Agent({
bodyTimeout: 0,
headersTimeout: 0
}),
signal: abortController.signal
};
if (this.options.proxy) {
opts.agent = new ProxyAgent(this.options.proxy);
}
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
};
if (typeof opts?.getIds === 'function') {
opts.getIds({
userMessage,
conversationId,
responseMessageId
});
}
console.log('userMessage', userMessage);
await this.saveMessageToDatabase(userMessage, user);
let reply = '';
let blocked = false;
try {
const result = await this.getCompletion(message, messages, opts.abortController);
blocked = result?.predictions?.[0]?.safetyAttributes?.blocked;
reply = result?.predictions?.[0]?.candidates?.[0]?.content || result?.predictions?.[0]?.content || '';
if (blocked === true) {
reply = `Google blocked a proper response to your message:\n${JSON.stringify(
result.predictions[0].safetyAttributes
)}${reply.length > 0 ? `\nAI Response:\n${reply}` : ''}`;
}
if (this.options.debug) {
console.debug('result');
console.debug(result);
}
} catch (err) {
console.error(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);
}
const responseMessage = {
messageId: responseMessageId,
conversationId,
parentMessageId: userMessage.messageId,
sender: 'PaLM2',
text: reply,
error: blocked,
isCreatedByUser: false
};
await this.saveMessageToDatabase(responseMessage, user);
return responseMessage;
}
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;
+15
View File
@@ -0,0 +1,15 @@
const { askClient } = require('./clients/chatgpt-client');
const { browserClient } = require('./clients/chatgpt-browser');
const { askBing } = require('./clients/bingai');
const titleConvo = require('./titleConvo');
const getCitations = require('../lib/parse/getCitations');
const citeText = require('../lib/parse/citeText');
module.exports = {
askClient,
browserClient,
askBing,
titleConvo,
getCitations,
citeText
};
+62
View File
@@ -0,0 +1,62 @@
const { Readable } = require('stream');
class TextStream extends Readable {
constructor(text, options = {}) {
super(options);
this.text = text;
this.currentIndex = 0;
this.delay = options.delay || 20; // Time in milliseconds
}
_read() {
const minChunkSize = 2;
const maxChunkSize = 4;
const { delay } = this;
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 chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize);
this.push(chunk);
this.currentIndex += chunkSize;
}, delay);
} else {
this.push(null); // signal end of data
}
}
randomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
async processTextStream(onProgressCallback) {
const streamPromise = new Promise((resolve, reject) => {
this.on('data', (chunk) => {
onProgressCallback(chunk.toString());
});
this.on('end', () => {
console.log('Stream ended');
resolve();
});
this.on('error', (err) => {
reject(err);
});
});
try {
await streamPromise;
} catch (err) {
console.error('Error processing text stream:', err);
// Handle the error appropriately, e.g., return an error message or throw an error
}
}
}
module.exports = TextStream;
+78
View File
@@ -0,0 +1,78 @@
const { Configuration, OpenAIApi } = require('openai');
const _ = require('lodash');
const { genAzureEndpoint } = require('../utils/genAzureEndpoints');
const proxyEnvToAxiosProxy = (proxyString) => {
if (!proxyString) return null;
const regex = /^([^:]+):\/\/(?:([^:@]*):?([^:@]*)@)?([^:]+)(?::(\d+))?/;
const [, protocol, username, password, host, port] = proxyString.match(regex);
const proxyConfig = {
protocol,
host,
port: port ? parseInt(port) : undefined,
auth: username && password ? { username, password } : undefined
};
return proxyConfig;
};
const titleConvo = async ({ endpoint, text, response }) => {
let title = 'New Chat';
const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default;
try {
const instructionsPayload = {
role: 'system',
content: `Detect user language and write in the same language an extremely concise title for this conversation, which you must accurately detect. Write in the detected language. Title in 5 Words or Less. No Punctuation or Quotation. All first letters of every word should be capitalized and complete only the title in User Language only.
||>User:
"${text}"
||>Response:
"${JSON.stringify(response?.text)}"
||>Title:`
};
const azure = process.env.AZURE_OPENAI_API_KEY ? true : false;
const options = {
azure,
reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null,
proxy: process.env.PROXY || null
};
const titleGenClientOptions = JSON.parse(JSON.stringify(options));
titleGenClientOptions.modelOptions = {
model: 'gpt-3.5-turbo',
temperature: 0,
presence_penalty: 0,
frequency_penalty: 0
};
let apiKey = process.env.OPENAI_KEY;
if (azure) {
apiKey = process.env.AZURE_OPENAI_API_KEY;
titleGenClientOptions.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 titleGenClient = new ChatGPTClient(apiKey, titleGenClientOptions);
const result = await titleGenClient.getCompletion([instructionsPayload], null);
title = result.choices[0].message.content.replace(/\s+/g, ' ').replaceAll('"', '').trim();
} catch (e) {
console.error(e);
console.log('There was an issue generating title, see error above');
}
console.log('CONVERSATION TITLE', title);
return title;
};
const throttledTitleConvo = _.throttle(titleConvo, 1000);
module.exports = throttledTitleConvo;
+44
View File
@@ -0,0 +1,44 @@
require('dotenv').config();
const mongoose = require('mongoose');
const MONGO_URI = process.env.MONGO_URI;
if (!MONGO_URI) {
throw new Error('Please define the MONGO_URI environment variable inside .env.local');
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function connectDb() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
bufferCommands: false
// bufferMaxEntries: 0,
// useFindAndModify: true,
// useCreateIndex: true
};
mongoose.set('strictQuery', true);
cached.promise = mongoose.connect(MONGO_URI, opts).then((mongoose) => {
return mongoose;
});
}
cached.conn = await cached.promise;
return cached.conn;
}
module.exports = connectDb;
+70
View File
@@ -0,0 +1,70 @@
const mongoose = require('mongoose');
const Conversation = mongoose.models.Conversation;
const Message = mongoose.models.Message;
const { MeiliSearch } = require('meilisearch');
let currentTimeout = null;
// eslint-disable-next-line no-unused-vars
async function indexSync(req, res, next) {
try {
if (!process.env.MEILI_HOST || !process.env.MEILI_MASTER_KEY || !process.env.SEARCH) {
throw new Error('Meilisearch not configured, search will be disabled.');
}
const client = new MeiliSearch({
host: process.env.MEILI_HOST,
apiKey: process.env.MEILI_MASTER_KEY
});
const { status } = await client.health();
// console.log(`Meilisearch: ${status}`);
const result = status === 'available' && !!process.env.SEARCH;
if (!result) {
throw new Error('Meilisearch not available');
}
const messageCount = await Message.countDocuments();
const convoCount = await Conversation.countDocuments();
const messages = await client.index('messages').getStats();
const convos = await client.index('convos').getStats();
const messagesIndexed = messages.numberOfDocuments;
const convosIndexed = convos.numberOfDocuments;
console.log(`There are ${messageCount} messages in the database, ${messagesIndexed} indexed`);
console.log(`There are ${convoCount} convos in the database, ${convosIndexed} indexed`);
if (messageCount !== messagesIndexed) {
console.log('Messages out of sync, indexing');
await Message.syncWithMeili();
}
if (convoCount !== convosIndexed) {
console.log('Convos out of sync, indexing');
await Conversation.syncWithMeili();
}
} catch (err) {
// console.log('in index sync');
if (err.message.includes('not found')) {
console.log('Creating indices...');
currentTimeout = setTimeout(async () => {
try {
await Message.syncWithMeili();
await Conversation.syncWithMeili();
} catch (err) {
console.error('Trouble creating indices, try restarting the server.');
}
}, 750);
} else {
console.error(err);
// res.status(500).json({ error: 'Server error' });
}
}
}
process.on('exit', () => {
console.log('Clearing sync timeouts before exiting...');
clearTimeout(currentTimeout);
});
module.exports = indexSync;
+118
View File
@@ -0,0 +1,118 @@
const mongoose = require('mongoose');
const { Conversation } = require('../../models/Conversation');
const { getMessages } = require('../../models/');
const migrateToStrictFollowParentMessageIdChain = async () => {
try {
const conversations = await Conversation.find({ endpoint: null, model: null }).exec();
if (!conversations || conversations.length === 0) return { noNeed: true };
console.log('Migration: To strict follow the parentMessageId chain.');
for (let convo of conversations) {
const messages = await getMessages({
conversationId: convo.conversationId,
messageId: { $exists: false }
});
let model;
let oldId;
const promises = [];
messages.forEach((message, i) => {
const msgObj = message.toObject();
const newId = msgObj.id;
if (i === 0) {
message.parentMessageId = '00000000-0000-0000-0000-000000000000';
} else {
message.parentMessageId = oldId;
}
oldId = newId;
message.messageId = newId;
if (message.sender.toLowerCase() !== 'user' && !model) {
model = message.sender.toLowerCase();
}
if (message.sender.toLowerCase() === 'user') {
message.isCreatedByUser = true;
}
promises.push(message.save());
});
await Promise.all(promises);
await Conversation.findOneAndUpdate(
{ conversationId: convo.conversationId },
{ model },
{ new: true }
).exec();
}
try {
await mongoose.connection.db.collection('messages').dropIndex('id_1');
} catch (error) {
console.log("[Migrate] Index doesn't exist or already dropped");
}
} catch (error) {
console.log(error);
return { message: '[Migrate] Error migrating conversations' };
}
};
const migrateToSupportBetterCustomization = async () => {
try {
const conversations = await Conversation.find({ endpoint: null }).exec();
if (!conversations || conversations.length === 0) return { noNeed: true };
console.log('Migration: To support better customization.');
const promises = [];
for (let convo of conversations) {
const originalModel = convo?.model;
if (originalModel === 'chatgpt') {
convo.endpoint = 'openAI';
convo.model = 'gpt-3.5-turbo';
} else if (originalModel === 'chatgptCustom') {
convo.endpoint = 'openAI';
convo.model = 'gpt-3.5-turbo';
} else if (originalModel === 'bingai') {
convo.endpoint = 'bingAI';
convo.model = null;
convo.jailbreak = false;
} else if (originalModel === 'sydney') {
convo.endpoint = 'bingAI';
convo.model = null;
convo.jailbreak = true;
} else if (originalModel === 'chatgptBrowser') {
convo.endpoint = 'chatGPTBrowser';
convo.model = 'text-davinci-002-render-sha';
convo.jailbreak = true;
} else {
convo.endpoint = 'openAI';
convo.model = 'gpt-3.5-turbo';
}
promises.push(convo.save());
}
await Promise.all(promises);
} catch (error) {
console.log(error);
return { message: '[Migrate] Error migrating conversations' };
}
};
async function migrateDb() {
let ret = [];
ret[0] = await migrateToStrictFollowParentMessageIdChain();
ret[1] = await migrateToSupportBetterCustomization();
const isMigrated = !!ret.find(element => !element?.noNeed);
if (!isMigrated) console.log('[Migrate] Nothing to migrate');
}
module.exports = migrateDb;
+31
View File
@@ -0,0 +1,31 @@
const citationRegex = /\[\^\d+?\^\]/g;
const citeText = (res, noLinks = false) => {
let result = res.text || res;
const citations = Array.from(new Set(result.match(citationRegex)));
if (citations?.length === 0) return result;
if (noLinks) {
citations.forEach((citation) => {
const digit = citation.match(/\d+?/g)[0];
// result = result.replaceAll(citation, `<sup>[${digit}](#) </sup>`);
result = result.replaceAll(citation, `<sup>[${digit}](#) </sup>`);
});
return result;
}
let sources = res.details.sourceAttributions;
if (sources?.length === 0) return result;
sources = sources.map((source) => source.seeMoreUrl);
citations.forEach((citation) => {
const digit = citation.match(/\d+?/g)[0];
result = result.replaceAll(citation, `<sup>[${digit}](${sources[digit - 1]}) </sup>`);
// result = result.replaceAll(citation, `<sup>[${digit}](${sources[digit - 1]}) </sup>`);
});
return result;
};
module.exports = citeText;
+14
View File
@@ -0,0 +1,14 @@
// const regex = / \[\d+\..*?\]\(.*?\)/g;
const regex = / \[.*?]\(.*?\)/g;
const getCitations = (res) => {
const adaptiveCards = res.details.adaptiveCards;
const textBlocks = adaptiveCards && adaptiveCards[0].body;
if (!textBlocks) return '';
let links = textBlocks[textBlocks.length - 1]?.text.match(regex);
if (links?.length === 0 || !links) return '';
links = links.map((link) => link.trim());
return links.join('\n');
};
module.exports = getCitations;
+29
View File
@@ -0,0 +1,29 @@
function mergeSort(arr, compareFn) {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const leftArr = arr.slice(0, mid);
const rightArr = arr.slice(mid);
return merge(mergeSort(leftArr, compareFn), mergeSort(rightArr, compareFn), compareFn);
}
function merge(leftArr, rightArr, compareFn) {
const result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < leftArr.length && rightIndex < rightArr.length) {
if (compareFn(leftArr[leftIndex], rightArr[rightIndex]) < 0) {
result.push(leftArr[leftIndex++]);
} else {
result.push(rightArr[rightIndex++]);
}
}
return result.concat(leftArr.slice(leftIndex)).concat(rightArr.slice(rightIndex));
}
module.exports = mergeSort;
+15
View File
@@ -0,0 +1,15 @@
const cleanUpPrimaryKeyValue = (value) => {
// For Bing convoId handling
return value.replace(/--/g, '|');
};
function replaceSup(text) {
if (!text.includes('<sup>')) return text;
const replacedText = text.replace(/<sup>/g, '^').replace(/\s+<\/sup>/g, '^');
return replacedText;
}
module.exports = {
cleanUpPrimaryKeyValue,
replaceSup
};
+59
View File
@@ -0,0 +1,59 @@
const mergeSort = require('./mergeSort');
const { cleanUpPrimaryKeyValue } = require('./misc');
function reduceMessages(hits) {
const counts = {};
for (const hit of hits) {
if (!counts[hit.conversationId]) {
counts[hit.conversationId] = 1;
} else {
counts[hit.conversationId]++;
}
}
const result = [];
for (const [conversationId, count] of Object.entries(counts)) {
result.push({
conversationId,
count
});
}
return mergeSort(result, (a, b) => b.count - a.count);
}
function reduceHits(hits, titles = []) {
const counts = {};
const titleMap = {};
const convos = [...hits, ...titles];
for (const convo of convos) {
const currentId = cleanUpPrimaryKeyValue(convo.conversationId);
if (!counts[currentId]) {
counts[currentId] = 1;
} else {
counts[currentId]++;
}
if (convo.title) {
// titleMap[currentId] = convo._formatted.title;
titleMap[currentId] = convo.title;
}
}
const result = [];
for (const [conversationId, count] of Object.entries(counts)) {
result.push({
conversationId,
count,
title: titleMap[conversationId] ? titleMap[conversationId] : null
});
}
return mergeSort(result, (a, b) => b.count - a.count);
}
module.exports = { reduceMessages, reduceHits };
+5
View File
@@ -0,0 +1,5 @@
const passport = require('passport');
const requireJwtAuth = passport.authenticate('jwt', { session: false });
module.exports = requireJwtAuth;
+31
View File
@@ -0,0 +1,31 @@
const passport = require('passport');
const DebugControl = require('../utils/debug.js');
function log({ title, parameters }) {
DebugControl.log.functionName(title);
if (parameters) {
DebugControl.log.parameters(parameters);
}
}
const requireLocalAuth = (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
log({
title: '(requireLocalAuth) Error at passport.authenticate',
parameters: [{ name: 'error', value: err }]
});
return next(err);
}
if (!user) {
log({
title: '(requireLocalAuth) Error: No user',
});
return res.status(422).send(info);
}
req.user = user;
next();
})(req, res, next);
};
module.exports = requireLocalAuth;
+84
View File
@@ -0,0 +1,84 @@
const mongoose = require('mongoose');
const major = [0, 0];
const minor = [0, 0];
const patch = [0, 5];
const configSchema = mongoose.Schema(
{
tag: {
type: String,
required: true,
validate: {
validator: function (tag) {
const [part1, part2, part3] = tag.replace('v', '').split('.').map(Number);
// Check if all parts are numbers
if (isNaN(part1) || isNaN(part2) || isNaN(part3)) {
return false;
}
// Check if all parts are within their respective ranges
if (part1 < major[0] || part1 > major[1]) {
return false;
}
if (part2 < minor[0] || part2 > minor[1]) {
return false;
}
if (part3 < patch[0] || part3 > patch[1]) {
return false;
}
return true;
},
message: 'Invalid tag value'
}
},
searchEnabled: {
type: Boolean,
default: false
},
usersEnabled: {
type: Boolean,
default: false
},
startupCounts: {
type: Number,
default: 0
}
},
{ timestamps: true }
);
// Instance method
configSchema.methods.incrementCount = function () {
this.startupCounts += 1;
};
// Static methods
configSchema.statics.findByTag = async function (tag) {
return await this.findOne({ tag });
};
configSchema.statics.updateByTag = async function (tag, update) {
return await this.findOneAndUpdate({ tag }, update, { new: true });
};
const Config = mongoose.models.Config || mongoose.model('Config', configSchema);
module.exports = {
getConfigs: async (filter) => {
try {
return await Config.find(filter).exec();
} catch (error) {
console.error(error);
return { config: 'Error getting configs' };
}
},
deleteConfigs: async (filter) => {
try {
return await Config.deleteMany(filter).exec();
} catch (error) {
console.error(error);
return { config: 'Error deleting configs' };
}
}
};
+128
View File
@@ -0,0 +1,128 @@
// const { Conversation } = require('./plugins');
const Conversation = require('./schema/convoSchema');
const { getMessages, deleteMessages } = require('./Message');
const getConvo = async (user, conversationId) => {
try {
return await Conversation.findOne({ user, conversationId }).exec();
} catch (error) {
console.log(error);
return { message: 'Error getting single conversation' };
}
};
module.exports = {
Conversation,
saveConvo: async (user, { conversationId, newConversationId, ...convo }) => {
try {
const messages = await getMessages({ conversationId });
const update = { ...convo, messages, user };
if (newConversationId) {
update.conversationId = newConversationId;
}
return await Conversation.findOneAndUpdate({ conversationId: conversationId, user }, update, {
new: true,
upsert: true
}).exec();
} catch (error) {
console.log(error);
return { message: 'Error saving conversation' };
}
},
getConvosByPage: async (user, pageNumber = 1, pageSize = 14) => {
try {
const totalConvos = (await Conversation.countDocuments({ user })) || 1;
const totalPages = Math.ceil(totalConvos / pageSize);
const convos = await Conversation.find({ user })
.sort({ createdAt: -1, created: -1 })
.skip((pageNumber - 1) * pageSize)
.limit(pageSize)
.exec();
return { conversations: convos, pages: totalPages, pageNumber, pageSize };
} catch (error) {
console.log(error);
return { message: 'Error getting conversations' };
}
},
getConvosQueried: async (user, convoIds, pageNumber = 1, pageSize = 14) => {
try {
if (!convoIds || convoIds.length === 0) {
return { conversations: [], pages: 1, pageNumber, pageSize };
}
const cache = {};
const convoMap = {};
const promises = [];
// will handle a syncing solution soon
const deletedConvoIds = [];
convoIds.forEach(convo =>
promises.push(
Conversation.findOne({
user,
conversationId: convo.conversationId
}).exec()
)
);
const results = (await Promise.all(promises)).filter((convo, i) => {
if (!convo) {
deletedConvoIds.push(convoIds[i].conversationId);
return false;
} else {
const page = Math.floor(i / pageSize) + 1;
if (!cache[page]) {
cache[page] = [];
}
cache[page].push(convo);
convoMap[convo.conversationId] = convo;
return true;
}
});
// const startIndex = (pageNumber - 1) * pageSize;
// const convos = results.slice(startIndex, startIndex + pageSize);
const totalPages = Math.ceil(results.length / pageSize);
cache.pages = totalPages;
cache.pageSize = pageSize;
return {
cache,
conversations: cache[pageNumber] || [],
pages: totalPages || 1,
pageNumber,
pageSize,
// will handle a syncing solution soon
filter: new Set(deletedConvoIds),
convoMap
};
} catch (error) {
console.log(error);
return { message: 'Error fetching conversations' };
}
},
getConvo,
/* chore: this method is not properly error handled */
getConvoTitle: async (user, conversationId) => {
try {
const convo = await getConvo(user, conversationId);
/* ChatGPT Browser was triggering error here due to convo being saved later */
if (convo && !convo.title) {
return null;
} else {
// TypeError: Cannot read properties of null (reading 'title')
return convo?.title || 'New Chat';
}
} catch (error) {
console.log(error);
return { message: 'Error getting conversation title' };
}
},
deleteConvos: async (user, filter) => {
let toRemove = await Conversation.find({ ...filter, user }).select('conversationId');
const ids = toRemove.map(instance => instance.conversationId);
let deleteCount = await Conversation.deleteMany({ ...filter, user }).exec();
deleteCount.messages = await deleteMessages({ conversationId: { $in: ids } });
return deleteCount;
}
};
+86
View File
@@ -0,0 +1,86 @@
const Message = require('./schema/messageSchema');
module.exports = {
Message,
async saveMessage({
messageId,
newMessageId,
conversationId,
parentMessageId,
sender,
text,
isCreatedByUser = false,
error,
unfinished,
cancelled
}) {
try {
// may also need to update the conversation here
await Message.findOneAndUpdate(
{ messageId },
{
messageId: newMessageId || messageId,
conversationId,
parentMessageId,
sender,
text,
isCreatedByUser,
error,
unfinished,
cancelled
},
{ upsert: true, new: true }
);
return {
messageId,
conversationId,
parentMessageId,
sender,
text,
isCreatedByUser
};
} catch (err) {
console.error(`Error saving message: ${err}`);
throw new Error('Failed to save message.');
}
},
async deleteMessagesSince({ messageId, conversationId }) {
try {
const message = await Message.findOne({ messageId }).exec();
if (message) {
return await Message.find({ conversationId })
.deleteMany({ createdAt: { $gt: message.createdAt } })
.exec();
}
} catch (err) {
console.error(`Error deleting messages: ${err}`);
throw new Error('Failed to delete messages.');
}
},
async getMessages(filter) {
try {
return await Message.find(filter).sort({ createdAt: 1 }).exec();
} catch (err) {
console.error(`Error getting messages: ${err}`);
throw new Error('Failed to get messages.');
}
},
async deleteMessages(filter) {
try {
return await Message.deleteMany(filter).exec();
} catch (err) {
console.error(`Error deleting messages: ${err}`);
throw new Error('Failed to delete messages.');
}
}
};
+46
View File
@@ -0,0 +1,46 @@
const Preset = require('./schema/presetSchema');
const getPreset = async (user, presetId) => {
try {
return await Preset.findOne({ user, presetId }).exec();
} catch (error) {
console.log(error);
return { message: 'Error getting single preset' };
}
};
module.exports = {
Preset,
getPreset,
getPresets: async (user, filter) => {
try {
return await Preset.find({ ...filter, user }).exec();
} catch (error) {
console.log(error);
return { message: 'Error retriving presets' };
}
},
savePreset: async (user, { presetId, newPresetId, ...preset }) => {
try {
const update = { presetId, ...preset };
if (newPresetId) {
update.presetId = newPresetId;
}
return await Preset.findOneAndUpdate(
{ presetId, user },
{ $set: update },
{ new: true, upsert: true }
).exec();
} catch (error) {
console.log(error);
return { message: 'Error saving preset' };
}
},
deletePresets: async (user, filter) => {
let toRemove = await Preset.find({ ...filter, user }).select('presetId');
const ids = toRemove.map(instance => instance.presetId);
let deleteCount = await Preset.deleteMany({ ...filter, user }).exec();
return deleteCount;
}
};
+48
View File
@@ -0,0 +1,48 @@
const mongoose = require('mongoose');
const promptSchema = mongoose.Schema({
title: {
type: String,
required: true
},
prompt: {
type: String,
required: true
},
category: {
type: String,
},
}, { timestamps: true });
const Prompt = mongoose.models.Prompt || mongoose.model('Prompt', promptSchema);
module.exports = {
savePrompt: async ({ title, prompt }) => {
try {
await Prompt.create({
title,
prompt
});
return { title, prompt };
} catch (error) {
console.error(error);
return { prompt: 'Error saving prompt' };
}
},
getPrompts: async (filter) => {
try {
return await Prompt.find(filter).exec()
} catch (error) {
console.error(error);
return { prompt: 'Error getting prompts' };
}
},
deletePrompts: async (filter) => {
try {
return await Prompt.deleteMany(filter).exec()
} catch (error) {
console.error(error);
return { prompt: 'Error deleting prompts' };
}
}
}
+177
View File
@@ -0,0 +1,177 @@
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const Joi = require('joi');
const DebugControl = require('../utils/debug.js');
function log({ title, parameters }) {
DebugControl.log.functionName(title);
DebugControl.log.parameters(parameters);
}
const Session = mongoose.Schema({
refreshToken: {
type: String,
default: ''
}
});
const userSchema = mongoose.Schema(
{
name: {
type: String
},
username: {
type: String,
lowercase: true,
required: [true, "can't be blank"],
match: [/^[a-zA-Z0-9_]+$/, 'is invalid'],
index: true
},
email: {
type: String,
required: [true, "can't be blank"],
lowercase: true,
unique: true,
match: [/\S+@\S+\.\S+/, 'is invalid'],
index: true
},
emailVerified: {
type: Boolean,
required: true,
default: false
},
password: {
type: String,
trim: true,
minlength: 8,
maxlength: 60
},
avatar: {
type: String,
required: false
},
provider: {
type: String,
required: true,
default: 'local'
},
role: {
type: String,
default: 'USER'
},
googleId: {
type: String,
unique: true,
sparse: true
},
facebookId: {
type: String,
unique: true,
sparse: true
},
refreshToken: {
type: [Session]
}
},
{ timestamps: true }
);
//Remove refreshToken from the response
userSchema.set('toJSON', {
transform: function (doc, ret, options) {
delete ret.refreshToken;
return ret;
}
});
userSchema.methods.toJSON = function () {
return {
id: this._id,
provider: this.provider,
email: this.email,
name: this.name,
username: this.username,
avatar: this.avatar,
role: this.role,
emailVerified: this.emailVerified,
createdAt: this.createdAt,
updatedAt: this.updatedAt
};
};
const isProduction = process.env.NODE_ENV === 'production';
const secretOrKey = isProduction ? process.env.JWT_SECRET_PROD : process.env.JWT_SECRET_DEV;
const refreshSecret = isProduction
? process.env.REFRESH_TOKEN_SECRET_PROD
: process.env.REFRESH_TOKEN_SECRET_DEV;
userSchema.methods.generateToken = function () {
const token = jwt.sign(
{
id: this._id,
username: this.username,
provider: this.provider,
email: this.email
},
secretOrKey,
{ expiresIn: eval(process.env.SESSION_EXPIRY) }
);
return token;
};
userSchema.methods.generateRefreshToken = function () {
const refreshToken = jwt.sign(
{
id: this._id,
username: this.username,
provider: this.provider,
email: this.email
},
refreshSecret,
{ expiresIn: eval(process.env.REFRESH_TOKEN_EXPIRY) }
);
return refreshToken;
};
userSchema.methods.comparePassword = function (candidatePassword, callback) {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
if (err) return callback(err);
callback(null, isMatch);
});
};
module.exports.hashPassword = async (password) => {
const hashedPassword = await new Promise((resolve, reject) => {
bcrypt.hash(password, 10, function (err, hash) {
if (err) reject(err);
else resolve(hash);
});
});
return hashedPassword;
};
module.exports.validateUser = (user) => {
log({
title: 'Validate User',
parameters: [{ name: 'Validate User', value: user }]
});
const schema = {
avatar: Joi.any(),
name: Joi.string().min(2).max(80).required(),
username: Joi.string()
.min(2)
.max(80)
.regex(/^[a-zA-Z0-9_]+$/)
.required(),
password: Joi.string().min(8).max(60).allow('').allow(null)
};
return Joi.validate(user, schema);
};
const User = mongoose.model('User', userSchema);
module.exports = User;
+19
View File
@@ -0,0 +1,19 @@
const { getMessages, saveMessage, deleteMessagesSince, deleteMessages } = require('./Message');
const { getConvoTitle, getConvo, saveConvo } = require('./Conversation');
const { getPreset, getPresets, savePreset, deletePresets } = require('./Preset');
module.exports = {
getMessages,
saveMessage,
deleteMessagesSince,
deleteMessages,
getConvoTitle,
getConvo,
saveConvo,
getPreset,
getPresets,
savePreset,
deletePresets
};
+211
View File
@@ -0,0 +1,211 @@
const mongoose = require('mongoose');
const { MeiliSearch } = require('meilisearch');
const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc');
const _ = require('lodash');
const validateOptions = function (options) {
const requiredKeys = ['host', 'apiKey', 'indexName'];
requiredKeys.forEach((key) => {
if (!options[key]) throw new Error(`Missing mongoMeili Option: ${key}`);
});
};
const createMeiliMongooseModel = function ({ index, indexName, client, attributesToIndex }) {
// console.log('attributesToIndex', attributesToIndex);
const primaryKey = attributesToIndex[0];
// MeiliMongooseModel is of type Mongoose.Model
class MeiliMongooseModel {
// Clear Meili index
static async clearMeiliIndex() {
await index.delete();
// await index.deleteAllDocuments();
await this.collection.updateMany(
{ _meiliIndex: true },
{ $set: { _meiliIndex: false } }
);
}
static async resetIndex() {
await this.clearMeiliIndex();
await client.createIndex(indexName, { primaryKey });
}
// Clear Meili index
// Push a mongoDB collection to Meili index
static async syncWithMeili() {
await this.resetIndex();
// const docs = await this.find();
const docs = await this.find({ _meiliIndex: { $in: [null, false] } });
console.log('docs', docs.length);
await Promise.all(
docs.map(function (doc) {
return doc.addObjectToMeili();
})
);
}
// Set one or more settings of the meili index
static async setMeiliIndexSettings(settings) {
return await index.updateSettings(settings);
}
// Search the index
static async meiliSearch(q, params, populate) {
const data = await index.search(q, params);
// Populate hits with content from mongodb
if (populate) {
// Find objects into mongodb matching `objectID` from Meili search
const query = {};
// query[primaryKey] = { $in: _.map(data.hits, primaryKey) };
query[primaryKey] = _.map(data.hits, hit => cleanUpPrimaryKeyValue(hit[primaryKey]));
// console.log('query', query);
const hitsFromMongoose = await this.find(
query,
_.reduce(
this.schema.obj,
function (results, value, key) {
return { ...results, [key]: 1 };
},
{ _id: 1 }
),
);
// Add additional data from mongodb into Meili search hits
const populatedHits = data.hits.map(function (hit) {
const query = {};
query[primaryKey] = hit[primaryKey];
const originalHit = _.find(hitsFromMongoose, query);
return {
...(originalHit ? originalHit.toJSON() : {}),
...hit
};
});
data.hits = populatedHits;
}
return data;
}
// Push new document to Meili
async addObjectToMeili() {
const object = _.pick(this.toJSON(), attributesToIndex);
// NOTE: MeiliSearch does not allow | in primary key, so we replace it with - for Bing convoIds
// object.conversationId = object.conversationId.replace(/\|/g, '-');
if (object.conversationId && object.conversationId.includes('|')) {
object.conversationId = object.conversationId.replace(/\|/g, '--');
}
try {
// console.log('Adding document to Meili', object);
await index.addDocuments([object]);
} catch (error) {
// console.log('Error adding document to Meili');
// console.error(error);
}
await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
}
// Update an existing document in Meili
async updateObjectToMeili() {
const object = _.pick(this.toJSON(), attributesToIndex);
await index.updateDocuments([object]);
}
// Delete a document from Meili
async deleteObjectFromMeili() {
await index.deleteDocument(this._id);
}
// * schema.post('save')
postSaveHook() {
if (this._meiliIndex) {
this.updateObjectToMeili();
} else {
this.addObjectToMeili();
}
}
// * schema.post('update')
postUpdateHook() {
if (this._meiliIndex) {
this.updateObjectToMeili();
}
}
// * schema.post('remove')
postRemoveHook() {
if (this._meiliIndex) {
this.deleteObjectFromMeili();
}
}
}
return MeiliMongooseModel;
};
module.exports = function mongoMeili(schema, options) {
// Vaidate Options for mongoMeili
validateOptions(options);
// Add meiliIndex to schema
schema.add({
_meiliIndex: {
type: Boolean,
required: false,
select: false,
default: false
}
});
const { host, apiKey, indexName, primaryKey } = options;
// Setup MeiliSearch Client
const client = new MeiliSearch({ host, apiKey });
// Asynchronously create the index
client.createIndex(indexName, { primaryKey });
// Setup the index to search for this schema
const index = client.index(indexName);
const attributesToIndex = [
..._.reduce(
schema.obj,
function (results, value, key) {
return value.meiliIndex ? [...results, key] : results;
// }, []), '_id'];
},
[]
)
];
schema.loadClass(createMeiliMongooseModel({ index, indexName, client, attributesToIndex }));
// Register hooks
schema.post('save', function (doc) {
doc.postSaveHook();
});
schema.post('update', function (doc) {
doc.postUpdateHook();
});
schema.post('remove', function (doc) {
doc.postRemoveHook();
});
schema.post('deleteMany', function () {
// console.log('deleteMany hook', doc);
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
console.log('Syncing convos...');
mongoose.model('Conversation').syncWithMeili();
}
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
console.log('Syncing messages...');
mongoose.model('Message').syncWithMeili();
}
});
schema.post('findOneAndUpdate', function (doc) {
doc.postSaveHook();
});
};
+84
View File
@@ -0,0 +1,84 @@
module.exports = {
// endpoint: [azureOpenAI, openAI, bingAI, chatGPTBrowser]
endpoint: {
type: String,
default: null,
required: true
},
// for azureOpenAI, openAI, chatGPTBrowser only
model: {
type: String,
default: null,
required: false
},
// for azureOpenAI, openAI only
chatGptLabel: {
type: String,
default: null,
required: false
},
// for google only
modelLabel: {
type: String,
default: null,
required: false
},
promptPrefix: {
type: String,
default: null,
required: false
},
temperature: {
type: Number,
default: 1,
required: false
},
top_p: {
type: Number,
default: 1,
required: false
},
// for google only
topP: {
type: Number,
default: 0.95,
required: false
},
topK: {
type: Number,
default: 40,
required: false
},
maxOutputTokens: {
type: Number,
default: 1024,
required: false
},
presence_penalty: {
type: Number,
default: 0,
required: false
},
frequency_penalty: {
type: Number,
default: 0,
required: false
},
// for bingai only
jailbreak: {
type: Boolean,
default: false
},
context: {
type: String,
default: null
},
systemMessage: {
type: String,
default: null
},
toneStyle: {
type: String,
default: null
}
};
+58
View File
@@ -0,0 +1,58 @@
const mongoose = require('mongoose');
const mongoMeili = require('../plugins/mongoMeili');
const conversationPreset = require('./conversationPreset');
const convoSchema = mongoose.Schema(
{
conversationId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true
},
user: {
type: String,
default: null
},
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
// google only
examples: [{ type: mongoose.Schema.Types.Mixed }],
...conversationPreset,
// for bingAI only
jailbreakConversationId: {
type: String,
default: null
},
conversationSignature: {
type: String,
default: null
},
clientId: {
type: String,
default: null
},
invocationId: {
type: Number,
default: 1
}
},
{ timestamps: true }
);
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
convoSchema.plugin(mongoMeili, {
host: process.env.MEILI_HOST,
apiKey: process.env.MEILI_MASTER_KEY,
indexName: 'convos', // Will get created automatically if it doesn't exist already
primaryKey: 'conversationId'
});
}
const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
module.exports = Conversation;
+79
View File
@@ -0,0 +1,79 @@
const mongoose = require('mongoose');
const mongoMeili = require('../plugins/mongoMeili');
const messageSchema = mongoose.Schema(
{
messageId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true
},
conversationId: {
type: String,
required: true,
meiliIndex: true
},
conversationSignature: {
type: String
// required: true
},
clientId: {
type: String
},
invocationId: {
type: String
},
parentMessageId: {
type: String
// required: true
},
sender: {
type: String,
required: true,
meiliIndex: true
},
text: {
type: String,
required: true,
meiliIndex: true
},
isCreatedByUser: {
type: Boolean,
required: true,
default: false
},
unfinished: {
type: Boolean,
default: false
},
cancelled: {
type: Boolean,
default: false
},
error: {
type: Boolean,
default: false
},
_meiliIndex: {
type: Boolean,
required: false,
select: false,
default: false
}
},
{ timestamps: true }
);
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
messageSchema.plugin(mongoMeili, {
host: process.env.MEILI_HOST,
apiKey: process.env.MEILI_MASTER_KEY,
indexName: 'messages',
primaryKey: 'messageId'
});
}
const Message = mongoose.models.Message || mongoose.model('Message', messageSchema);
module.exports = Message;
+29
View File
@@ -0,0 +1,29 @@
const mongoose = require('mongoose');
const conversationPreset = require('./conversationPreset');
const presetSchema = mongoose.Schema(
{
presetId: {
type: String,
unique: true,
required: true,
index: true
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true
},
user: {
type: String,
default: null
},
// google only
examples: [{ type: mongoose.Schema.Types.Mixed }],
...conversationPreset
},
{ timestamps: true }
);
const Preset = mongoose.models.Preset || mongoose.model('Preset', presetSchema);
module.exports = Preset;
+22
View File
@@ -0,0 +1,22 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const tokenSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
required: true,
ref: "user",
},
token: {
type: String,
required: true,
},
createdAt: {
type: Date,
required: true,
default: Date.now,
expires: 900,
},
});
module.exports = mongoose.model("Token", tokenSchema);
+6
View File
@@ -0,0 +1,6 @@
{
"ignore": [
"api/data/",
"data"
]
}
+59
View File
@@ -0,0 +1,59 @@
{
"name": "chat-backend",
"version": "0.4.5",
"description": "",
"main": "server/index.js",
"scripts": {
"start": "node server/index.js",
"server-dev": "npx nodemon server/index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/danny-avila/chatgpt-clone.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/danny-avila/chatgpt-clone/issues"
},
"homepage": "https://github.com/danny-avila/chatgpt-clone#readme",
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",
"@keyv/mongo": "^2.1.8",
"@waylaidwanderer/chatgpt-api": "^1.36.0",
"axios": "^1.3.4",
"bcrypt": "^5.1.0",
"bcryptjs": "^2.4.3",
"cookie": "^0.5.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.0.3",
"eslint": "^8.36.0",
"express": "^4.18.2",
"googleapis": "^118.0.0",
"handlebars": "^4.7.7",
"html": "^1.0.0",
"joi": "^14.3.1",
"jsonwebtoken": "^9.0.0",
"keyv": "^4.5.2",
"keyv-file": "^0.2.0",
"lodash": "^4.17.21",
"meilisearch": "^0.31.1",
"mongoose": "^6.9.0",
"nodemailer": "^6.9.1",
"openai": "^3.1.0",
"passport": "^0.6.0",
"passport-facebook": "^3.0.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pino": "^8.12.1",
"sanitize": "^2.1.2"
},
"devDependencies": {
"nodemon": "^2.0.20",
"path": "^0.12.7"
}
}
+180
View File
@@ -0,0 +1,180 @@
const {
loginUser,
logoutUser,
registerUser,
requestPasswordReset,
resetPassword,
} = require("../services/auth.service");
const isProduction = process.env.NODE_ENV === 'production';
const loginController = async (req, res) => {
try {
const token = req.user.generateToken();
const user = await loginUser(req.user)
if(user) {
res.cookie('token', token, {
expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)),
httpOnly: false,
secure: isProduction
});
res.status(200).send({ token, user });
}
else {
return res.status(400).json({ message: 'Invalid credentials' });
}
}
catch (err) {
console.log(err);
return res.status(500).json({ message: err.message });
}
};
const logoutController = async (req, res) => {
const { signedCookies = {} } = req;
const { refreshToken } = signedCookies;
try {
const logout = await logoutUser(req.user, refreshToken);
console.log(logout)
const { status, message } = logout;
if (status === 200) {
res.clearCookie('token');
res.clearCookie('refreshToken');
res.status(status).send({ message });
}
else {
res.status(status).send({ message });
}
}
catch (err) {
console.log(err);
return res.status(500).json({ message: err.message });
}
}
const registrationController = async (req, res) => {
try {
const response = await registerUser(req.body);
if (response.status === 200) {
const { status, user } = response;
const token = user.generateToken();
//send token for automatic login
res.cookie('token', token, {
expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)),
httpOnly: false,
secure: isProduction
});
res.status(status).send({ user });
}
else {
const { status, message } = response;
res.status(status).send({ message });
}
}
catch (err) {
console.log(err);
return res.status(500).json({ message: err.message });
}
};
const getUserController = async (req, res) => {
return res.status(200).send(req.user);
};
const resetPasswordRequestController = async (req, res) => {
try {
const resetService = await requestPasswordReset(
req.body.email
);
if (resetService.link) {
return res.status(200).json(resetService);
}
else {
return res.status(400).json(resetService);
}
}
catch (e) {
console.log(e);
return res.status(400).json({ message: e.message });
}
};
const resetPasswordController = async (req, res) => {
try {
const resetPasswordService = await resetPassword(
req.body.userId,
req.body.token,
req.body.password
);
if(resetPasswordService instanceof Error) {
return res.status(400).json(resetPasswordService);
}
else {
return res.status(200).json(resetPasswordService);
}
}
catch (e) {
console.log(e);
return res.status(400).json({ message: e.message });
}
};
const refreshController = async (req, res, next) => {
const { signedCookies = {} } = req;
const { refreshToken } = signedCookies;
//TODO
// if (refreshToken) {
// try {
// const payload = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
// const userId = payload._id;
// User.findOne({ _id: userId }).then(
// (user) => {
// if (user) {
// // Find the refresh token against the user record in database
// const tokenIndex = user.refreshToken.findIndex(item => item.refreshToken === refreshToken);
// if (tokenIndex === -1) {
// res.statusCode = 401;
// res.send('Unauthorized');
// } else {
// const token = req.user.generateToken();
// // If the refresh token exists, then create new one and replace it.
// const newRefreshToken = req.user.generateRefreshToken();
// user.refreshToken[tokenIndex] = { refreshToken: newRefreshToken };
// user.save((err) => {
// if (err) {
// res.statusCode = 500;
// res.send(err);
// } else {
// // setTokenCookie(res, newRefreshToken);
// const user = req.user.toJSON();
// res.status(200).send({ token, user });
// }
// });
// }
// } else {
// res.statusCode = 401;
// res.send('Unauthorized');
// }
// },
// err => next(err)
// );
// } catch (err) {
// res.statusCode = 401;
// res.send('Unauthorized');
// }
// } else {
// res.statusCode = 401;
// res.send('Unauthorized');
// }
};
module.exports = {
getUserController,
loginController,
logoutController,
refreshController,
registrationController,
resetPasswordRequestController,
resetPasswordController,
};
@@ -0,0 +1,33 @@
//handle duplicates
const handleDuplicateKeyError = (err, res) => {
const field = Object.keys(err.keyValue);
const code = 409;
const error = `An document with that ${field} already exists.`;
console.log('congrats you hit the duped keys error');
res.status(code).send({ messages: error, fields: field });
};
//handle validation errors
const handleValidationError = (err, res) => {
console.log('congrats you hit the validation middleware');
let errors = Object.values(err.errors).map(el => el.message);
let fields = Object.values(err.errors).map(el => el.path);
let code = 400;
if (errors.length > 1) {
const formattedErrors = errors.join(' ');
res.status(code).send({ messages: formattedErrors, fields: fields });
} else {
res.status(code).send({ messages: errors, fields: fields });
}
};
// eslint-disable-next-line no-unused-vars
module.exports = (err, req, res, next) => {
try {
console.log('congrats you hit the error middleware');
if (err.name === 'ValidationError') return (err = handleValidationError(err, res));
if (err.code && err.code == 11000) return (err = handleDuplicateKeyError(err, res));
} catch (err) {
res.status(500).send('An unknown error occurred.');
}
};
+81
View File
@@ -0,0 +1,81 @@
const express = require('express');
const connectDb = require('../lib/db/connectDb');
const migrateDb = require('../lib/db/migrateDb');
const indexSync = require('../lib/db/indexSync');
const path = require('path');
const cors = require('cors');
const routes = require('./routes');
const errorController = require('./controllers/error.controller');
const passport = require('passport');
const port = process.env.PORT || 3080;
const host = process.env.HOST || 'localhost';
const projectPath = path.join(__dirname, '..', '..', 'client');
(async () => {
await connectDb();
console.log('Connected to MongoDB');
await migrateDb();
await indexSync();
const app = express();
app.use(errorController);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(projectPath, 'dist')));
app.set('trust proxy', 1); // trust first proxy
app.use(cors());
// OAUTH
app.use(passport.initialize());
require('../strategies/jwtStrategy');
require('../strategies/localStrategy');
if(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
require('../strategies/googleStrategy');
}
if(process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) {
require('../strategies/facebookStrategy');
}
app.use('/oauth', routes.oauth)
// api endpoint
app.use('/api/auth', routes.auth);
app.use('/api/search', routes.search);
app.use('/api/ask', routes.ask);
app.use('/api/messages', routes.messages);
app.use('/api/convos', routes.convos);
app.use('/api/presets', routes.presets);
app.use('/api/prompts', routes.prompts);
app.use('/api/tokenizer', routes.tokenizer);
app.use('/api/endpoints', routes.endpoints);
// static files
app.get('/*', function (req, res) {
res.sendFile(path.join(projectPath, 'dist', 'index.html'));
});
app.listen(port, host, () => {
if (host == '0.0.0.0')
console.log(
`Server listening on all interface at port ${port}. Use http://localhost:${port} to access it`
);
else console.log(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`);
});
})();
let messageCount = 0;
process.on('uncaughtException', (err) => {
if (!err.message.includes('fetch failed')) {
console.error('There was an uncaught error:', err.message);
}
if (err.message.includes('fetch failed')) {
if (messageCount === 0) {
console.error('Meilisearch error, search will be disabled');
messageCount++;
}
} else {
process.exit(1);
}
});
+64
View File
@@ -0,0 +1,64 @@
const Keyv = require('keyv');
const { KeyvFile } = require('keyv-file');
const addToCache = async ({ endpoint, endpointOption, userMessage, responseMessage }) => {
try {
const conversationsCache = new Keyv({
store: new KeyvFile({ filename: './data/cache.json' }),
namespace: 'chatgpt' // should be 'bing' for bing/sydney
});
const {
conversationId,
messageId: userMessageId,
parentMessageId: userParentMessageId,
text: userText
} = userMessage;
const {
messageId: responseMessageId,
parentMessageId: responseParentMessageId,
text: responseText
} = responseMessage;
let conversation = await conversationsCache.get(conversationId);
// used to generate a title for the conversation if none exists
// let isNewConversation = false;
if (!conversation) {
conversation = {
messages: [],
createdAt: Date.now()
};
// isNewConversation = true;
}
const roles = (options) => {
if (endpoint === 'openAI') {
return options?.chatGptLabel || 'ChatGPT';
} else if (endpoint === 'bingAI') {
return options?.jailbreak ? 'Sydney' : 'BingAI';
}
};
let _userMessage = {
id: userMessageId,
parentMessageId: userParentMessageId,
role: 'User',
message: userText
};
let _responseMessage = {
id: responseMessageId,
parentMessageId: responseParentMessageId,
role: roles(endpointOption),
message: responseText
};
conversation.messages.push(_userMessage, _responseMessage);
await conversationsCache.set(conversationId, conversation);
} catch (error) {
console.error('Trouble adding to cache', error);
}
};
module.exports = addToCache;
+255
View File
@@ -0,0 +1,255 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { titleConvo, askBing } = require('../../../app');
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const requireJwtAuth = require('../../../middleware/requireJwtAuth');
router.post('/', requireJwtAuth, async (req, res) => {
const {
endpoint,
text,
messageId,
overrideParentMessageId = null,
parentMessageId,
conversationId: oldConversationId
} = req.body;
if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' });
if (endpoint !== 'bingAI') return handleError(res, { text: 'Illegal request' });
// build user message
const conversationId = oldConversationId || crypto.randomUUID();
const isNewConversation = !oldConversationId;
const userMessageId = messageId;
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
let userMessage = {
messageId: userMessageId,
sender: 'User',
text,
parentMessageId: userParentMessageId,
conversationId,
isCreatedByUser: true
};
// build endpoint option
let endpointOption = {};
if (req.body?.jailbreak)
endpointOption = {
jailbreak: req.body?.jailbreak ?? false,
jailbreakConversationId: req.body?.jailbreakConversationId ?? null,
systemMessage: req.body?.systemMessage ?? null,
context: req.body?.context ?? null,
toneStyle: req.body?.toneStyle ?? 'fast',
token: req.body?.token ?? null
};
else
endpointOption = {
jailbreak: req.body?.jailbreak ?? false,
systemMessage: req.body?.systemMessage ?? null,
context: req.body?.context ?? null,
conversationSignature: req.body?.conversationSignature ?? null,
clientId: req.body?.clientId ?? null,
invocationId: req.body?.invocationId ?? null,
toneStyle: req.body?.toneStyle ?? 'fast',
token: req.body?.token ?? null
};
console.log('ask log', {
userMessage,
endpointOption,
conversationId
});
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req.user.id, {
...userMessage,
...endpointOption,
conversationId,
endpoint
});
}
// eslint-disable-next-line no-use-before-define
return await ask({
isNewConversation,
userMessage,
endpointOption,
conversationId,
preSendRequest: true,
overrideParentMessageId,
req,
res
});
});
const ask = async ({
isNewConversation,
userMessage,
endpointOption,
conversationId,
preSendRequest = true,
overrideParentMessageId = null,
req,
res
}) => {
let { text, parentMessageId: userParentMessageId, messageId: userMessageId } = userMessage;
let responseMessageId = crypto.randomUUID();
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
try {
let lastSavedTimestamp = 0;
const { onProgress: progressCallback, getPartialText } = createOnProgress({
onProgress: ({ text }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > 500) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: text,
unfinished: true,
cancelled: false,
error: false
});
}
}
});
const abortController = new AbortController();
let response = await askBing({
text,
parentMessageId: userParentMessageId,
conversationId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId
}),
abortController
});
console.log('BING RESPONSE', response);
const newConversationId = endpointOption?.jailbreak
? response.jailbreakConversationId
: response.conversationId || conversationId;
const newUserMassageId = response.parentMessageId || response.details.requestId || userMessageId;
const newResponseMessageId = response.messageId || response.details.messageId;
// STEP1 generate response message
response.text = response.response || response.details.spokenText || '**Bing refused to answer.**';
let responseMessage = {
conversationId: newConversationId,
messageId: responseMessageId,
newMessageId: newResponseMessageId,
parentMessageId: overrideParentMessageId || newUserMassageId,
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
text: await handleText(response, true),
suggestions:
response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text),
unfinished: false,
cancelled: false,
error: false
};
await saveMessage(responseMessage);
responseMessage.messageId = newResponseMessageId;
// STEP2 update the convosation.
// First update conversationId if needed
// Note!
// Bing API will not use our conversationId at the first time,
// so change the placeholder conversationId to the real one.
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
// but in this situation, don't change the conversationId, but create new convo.
let conversationUpdate = { conversationId: newConversationId, endpoint: 'bingAI' };
if (conversationId != newConversationId)
if (isNewConversation) {
// change the conversationId to new one
conversationUpdate = {
...conversationUpdate,
conversationId: conversationId,
newConversationId: newConversationId
};
} else {
// create new conversation
conversationUpdate = {
...conversationUpdate,
...endpointOption
};
}
if (endpointOption?.jailbreak) {
conversationUpdate.jailbreak = true;
conversationUpdate.jailbreakConversationId = response.jailbreakConversationId;
} else {
conversationUpdate.jailbreak = false;
conversationUpdate.conversationSignature = response.conversationSignature;
conversationUpdate.clientId = response.clientId;
conversationUpdate.invocationId = response.invocationId;
}
await saveConvo(req.user.id, conversationUpdate);
conversationId = newConversationId;
// STEP3 update the user message
userMessage.conversationId = newConversationId;
userMessage.messageId = newUserMassageId;
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId });
userMessageId = newUserMassageId;
sendMessage(res, {
title: await getConvoTitle(req.user.id, conversationId),
final: true,
conversation: await getConvo(req.user.id, conversationId),
requestMessage: userMessage,
responseMessage: responseMessage
});
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage });
await saveConvo(req.user.id, {
conversationId: conversationId,
title
});
}
} catch (error) {
console.log(error);
const errorMessage = {
messageId: responseMessageId,
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
unfinished: false,
cancelled: false,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;
+219
View File
@@ -0,0 +1,219 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { getChatGPTBrowserModels } = require('../endpoints');
const { browserClient } = require('../../../app/');
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const requireJwtAuth = require('../../../middleware/requireJwtAuth');
router.post('/', requireJwtAuth, async (req, res) => {
const {
endpoint,
text,
overrideParentMessageId = null,
parentMessageId,
conversationId: oldConversationId
} = req.body;
if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' });
if (endpoint !== 'chatGPTBrowser') return handleError(res, { text: 'Illegal request' });
// build user message
const conversationId = oldConversationId || crypto.randomUUID();
const isNewConversation = !oldConversationId;
const userMessageId = crypto.randomUUID();
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
const userMessage = {
messageId: userMessageId,
sender: 'User',
text,
parentMessageId: userParentMessageId,
conversationId,
isCreatedByUser: true
};
// build endpoint option
const endpointOption = {
model: req.body?.model ?? 'text-davinci-002-render-sha',
token: req.body?.token ?? null
};
const availableModels = getChatGPTBrowserModels();
if (availableModels.find((model) => model === endpointOption.model) === undefined)
return handleError(res, { text: 'Illegal request: model' });
console.log('ask log', {
userMessage,
endpointOption,
conversationId
});
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req.user.id, {
...userMessage,
...endpointOption,
conversationId,
endpoint
});
}
// eslint-disable-next-line no-use-before-define
return await ask({
isNewConversation,
userMessage,
endpointOption,
conversationId,
preSendRequest: true,
overrideParentMessageId,
req,
res
});
});
const ask = async ({
isNewConversation,
userMessage,
endpointOption,
conversationId,
preSendRequest = true,
overrideParentMessageId = null,
req,
res
}) => {
let { text, parentMessageId: userParentMessageId, messageId: userMessageId } = userMessage;
const userId = req.user.id;
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
let responseMessageId = crypto.randomUUID();
try {
let lastSavedTimestamp = 0;
const { onProgress: progressCallback, getPartialText } = createOnProgress({
onProgress: ({ text }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > 500) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: text,
unfinished: true,
cancelled: false,
error: false
});
}
}
});
const abortController = new AbortController();
let response = await browserClient({
text,
parentMessageId: userParentMessageId,
conversationId,
...endpointOption,
onProgress: progressCallback.call(null, { res, text }),
abortController,
userId
});
console.log('CLIENT RESPONSE', response);
const newConversationId = response.conversationId || conversationId;
const newUserMassageId = response.parentMessageId || userMessageId;
const newResponseMessageId = response.messageId;
// STEP1 generate response message
response.text = response.response || '**ChatGPT refused to answer.**';
let responseMessage = {
conversationId: newConversationId,
messageId: responseMessageId,
newMessageId: newResponseMessageId,
parentMessageId: overrideParentMessageId || newUserMassageId,
text: await handleText(response),
sender: endpointOption?.chatGptLabel || 'ChatGPT',
unfinished: false,
cancelled: false,
error: false
};
await saveMessage(responseMessage);
responseMessage.messageId = newResponseMessageId;
// STEP2 update the conversation
// First update conversationId if needed
let conversationUpdate = { conversationId: newConversationId, endpoint: 'chatGPTBrowser' };
if (conversationId != newConversationId)
if (isNewConversation) {
// change the conversationId to new one
conversationUpdate = {
...conversationUpdate,
conversationId: conversationId,
newConversationId: newConversationId
};
} else {
// create new conversation
conversationUpdate = {
...conversationUpdate,
...endpointOption
};
}
await saveConvo(req.user.id, conversationUpdate);
conversationId = newConversationId;
// STEP3 update the user message
userMessage.conversationId = newConversationId;
userMessage.messageId = newUserMassageId;
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId });
userMessageId = newUserMassageId;
sendMessage(res, {
title: await getConvoTitle(req.user.id, conversationId),
final: true,
conversation: await getConvo(req.user.id, conversationId),
requestMessage: userMessage,
responseMessage: responseMessage
});
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
// const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage });
const title = await response.details.title;
await saveConvo(req.user.id, {
conversationId: conversationId,
title
});
}
} catch (error) {
const errorMessage = {
messageId: responseMessageId,
sender: 'ChatGPT',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
unfinished: false,
cancelled: false,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;
+156
View File
@@ -0,0 +1,156 @@
const express = require('express');
const router = express.Router();
const { titleConvo } = require('../../../app/');
const GoogleClient = require('../../../app/google/GoogleClient');
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
const { handleError, sendMessage, createOnProgress } = require('./handlers');
const requireJwtAuth = require('../../../middleware/requireJwtAuth');
router.post('/', requireJwtAuth, async (req, res) => {
const { endpoint, text, parentMessageId, conversationId } = req.body;
if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' });
if (endpoint !== 'google') return handleError(res, { text: 'Illegal request' });
// build endpoint option
const endpointOption = {
examples: req.body?.examples ?? [{ input: { content: '' }, output: { content: '' } }],
promptPrefix: req.body?.promptPrefix ?? null,
token: req.body?.token ?? null,
modelOptions: {
model: req.body?.model ?? 'chat-bison',
modelLabel: req.body?.modelLabel ?? null,
temperature: req.body?.temperature ?? 0.2,
maxOutputTokens: req.body?.maxOutputTokens ?? 1024,
topP: req.body?.topP ?? 0.95,
topK: req.body?.topK ?? 40
}
};
const availableModels = ['chat-bison', 'text-bison'];
if (availableModels.find(model => model === endpointOption.modelOptions.model) === undefined) {
return handleError(res, { text: `Illegal request: model` });
}
// eslint-disable-next-line no-use-before-define
return await ask({
text,
endpointOption,
conversationId,
parentMessageId,
req,
res
});
});
const ask = async ({ text, endpointOption, parentMessageId = null, conversationId, req, res }) => {
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
let userMessage;
let userMessageId;
let responseMessageId;
let lastSavedTimestamp = 0;
try {
const getIds = (data) => {
userMessage = data.userMessage;
userMessageId = userMessage.messageId;
responseMessageId = data.responseMessageId;
if (!conversationId) {
conversationId = data.conversationId;
}
};
const { onProgress: progressCallback } = createOnProgress({
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > 500) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender: 'PaLM2',
conversationId,
parentMessageId: userMessageId,
text: partialText,
unfinished: true,
cancelled: false,
error: false
});
}
}
});
const abortController = new AbortController();
let key;
if (endpointOption.token) {
key = JSON.parse(endpointOption.token);
delete endpointOption.token;
console.log('Using service account key provided by User for PaLM models');
}
try {
if (!key) {
key = require('../../../data/auth.json');
}
} catch (e) {
console.log("No 'auth.json' file (service account key) found in /api/data/ for PaLM models");
}
const clientOptions = {
// debug: true, // for testing
reverseProxyUrl: process.env.GOOGLE_REVERSE_PROXY || null,
proxy: process.env.PROXY || null,
...endpointOption
};
const client = new GoogleClient(key, clientOptions);
let response = await client.sendMessage(text, {
getIds,
user: req.user.id,
parentMessageId,
conversationId,
onProgress: progressCallback.call(null, { res, text, parentMessageId: userMessageId }),
abortController
});
await saveMessage(response);
sendMessage(res, {
title: await getConvoTitle(req.user.id, conversationId),
final: true,
conversation: await getConvo(req.user.id, conversationId),
requestMessage: userMessage,
responseMessage: response
});
res.end();
if (parentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ text, response });
await saveConvo(req.user.id, {
conversationId: conversationId,
title
});
}
} catch (error) {
console.error(error);
const errorMessage = {
messageId: responseMessageId,
sender: 'PaLM2',
conversationId,
parentMessageId,
unfinished: false,
cancelled: false,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;
+275
View File
@@ -0,0 +1,275 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const addToCache = require('./addToCache');
const { getOpenAIModels } = require('../endpoints');
const { titleConvo, askClient } = require('../../../app/');
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const requireJwtAuth = require('../../../middleware/requireJwtAuth');
const abortControllers = new Map();
router.post('/abort', requireJwtAuth, async (req, res) => {
const { abortKey } = req.body;
console.log(`req.body`, req.body);
if (!abortControllers.has(abortKey)) {
return res.status(404).send('Request not found');
}
const { abortController } = abortControllers.get(abortKey);
abortControllers.delete(abortKey);
const ret = await abortController.abortAsk();
console.log('Aborted request', abortKey);
console.log('Aborted message:', ret);
res.send(JSON.stringify(ret));
});
router.post('/', requireJwtAuth, async (req, res) => {
const {
endpoint,
text,
overrideParentMessageId = null,
parentMessageId,
conversationId: oldConversationId
} = req.body;
if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' });
if (endpoint !== 'openAI') return handleError(res, { text: 'Illegal request' });
// build user message
const conversationId = oldConversationId || crypto.randomUUID();
const isNewConversation = !oldConversationId;
const userMessageId = crypto.randomUUID();
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
const userMessage = {
messageId: userMessageId,
sender: 'User',
text,
parentMessageId: userParentMessageId,
conversationId,
isCreatedByUser: true
};
// build endpoint option
const endpointOption = {
model: req.body?.model ?? 'gpt-3.5-turbo',
chatGptLabel: req.body?.chatGptLabel ?? null,
promptPrefix: req.body?.promptPrefix ?? null,
temperature: req.body?.temperature ?? 1,
top_p: req.body?.top_p ?? 1,
presence_penalty: req.body?.presence_penalty ?? 0,
frequency_penalty: req.body?.frequency_penalty ?? 0
};
const availableModels = getOpenAIModels();
if (availableModels.find(model => model === endpointOption.model) === undefined)
return handleError(res, { text: 'Illegal request: model' });
console.log('ask log', {
userMessage,
endpointOption,
conversationId
});
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req.user.id, {
...userMessage,
...endpointOption,
conversationId,
endpoint
});
}
// eslint-disable-next-line no-use-before-define
return await ask({
isNewConversation,
userMessage,
endpointOption,
conversationId,
preSendRequest: true,
overrideParentMessageId,
req,
res
});
});
const ask = async ({
isNewConversation,
userMessage,
endpointOption,
conversationId,
preSendRequest = true,
overrideParentMessageId = null,
req,
res
}) => {
let { text, parentMessageId: userParentMessageId, messageId: userMessageId } = userMessage;
const userId = req.user.id;
let responseMessageId = crypto.randomUUID();
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
try {
let lastSavedTimestamp = 0;
const { onProgress: progressCallback, getPartialText } = createOnProgress({
onProgress: ({ text }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > 500) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender: endpointOption?.chatGptLabel || 'ChatGPT',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: text,
unfinished: true,
cancelled: false,
error: false
});
}
}
});
let abortController = new AbortController();
abortController.abortAsk = async function () {
this.abort();
const responseMessage = {
messageId: responseMessageId,
sender: endpointOption?.chatGptLabel || 'ChatGPT',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: getPartialText(),
unfinished: false,
cancelled: true,
error: false
};
saveMessage(responseMessage);
await addToCache({ endpoint: 'openAI', endpointOption, userMessage, responseMessage });
return {
title: await getConvoTitle(req.user.id, conversationId),
final: true,
conversation: await getConvo(req.user.id, conversationId),
requestMessage: userMessage,
responseMessage: responseMessage
};
};
const abortKey = conversationId;
abortControllers.set(abortKey, { abortController, ...endpointOption });
let response = await askClient({
text,
parentMessageId: userParentMessageId,
conversationId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId
}),
abortController,
userId
});
abortControllers.delete(abortKey);
console.log('CLIENT RESPONSE', response);
const newConversationId = response.conversationId || conversationId;
const newUserMassageId = response.parentMessageId || userMessageId;
const newResponseMessageId = response.messageId;
// STEP1 generate response message
response.text = response.response || '**ChatGPT refused to answer.**';
let responseMessage = {
conversationId: newConversationId,
messageId: responseMessageId,
newMessageId: newResponseMessageId,
parentMessageId: overrideParentMessageId || newUserMassageId,
text: await handleText(response),
sender: endpointOption?.chatGptLabel || 'ChatGPT',
unfinished: false,
cancelled: false,
error: false
};
await saveMessage(responseMessage);
responseMessage.messageId = newResponseMessageId;
// STEP2 update the conversation
let conversationUpdate = { conversationId: newConversationId, endpoint: 'openAI' };
if (conversationId != newConversationId)
if (isNewConversation) {
// change the conversationId to new one
conversationUpdate = {
...conversationUpdate,
conversationId: conversationId,
newConversationId: newConversationId
};
} else {
// create new conversation
conversationUpdate = {
...conversationUpdate,
...endpointOption
};
}
await saveConvo(req.user.id, conversationUpdate);
conversationId = newConversationId;
// STEP3 update the user message
userMessage.conversationId = newConversationId;
userMessage.messageId = newUserMassageId;
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId });
userMessageId = newUserMassageId;
sendMessage(res, {
title: await getConvoTitle(req.user.id, conversationId),
final: true,
conversation: await getConvo(req.user.id, conversationId),
requestMessage: userMessage,
responseMessage: responseMessage
});
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage });
await saveConvo(req.user.id, {
conversationId: conversationId,
title
});
}
} catch (error) {
console.error(error);
const errorMessage = {
messageId: responseMessageId,
sender: endpointOption?.chatGptLabel || 'ChatGPT',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
unfinished: false,
cancelled: false,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;
+102
View File
@@ -0,0 +1,102 @@
const _ = require('lodash');
const citationRegex = /\[\^\d+?\^]/g;
const backtick = /(?<!`)[`](?!`)/g;
// const singleBacktick = /(?<!`)[`](?!`)/;
const cursorDefault = '<span className="result-streaming">█</span>';
const { getCitations, citeText } = require('../../../app');
const handleError = (res, message) => {
res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`);
res.end();
};
const sendMessage = (res, message) => {
if (message.length === 0) {
return;
}
res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
};
const createOnProgress = ({ onProgress: _onProgress }) => {
let i = 0;
let code = '';
let tokens = '';
let precode = '';
let blockCount = 0;
let codeBlock = false;
let cursor = cursorDefault;
const progressCallback = async (partial, { res, text, bing = false, ...rest }) => {
let chunk = partial === text ? '' : partial;
tokens += chunk;
precode += chunk;
tokens = tokens.replaceAll('[DONE]', '');
if (codeBlock) {
code += chunk;
}
if (precode.includes('```') && codeBlock) {
codeBlock = false;
cursor = cursorDefault;
precode = precode.replace(/```/g, '');
code = '';
}
if (precode.includes('```') && code === '') {
precode = precode.replace(/```/g, '');
codeBlock = true;
blockCount++;
cursor = blockCount > 1 ? '█\n\n```' : '█\n\n';
}
const backticks = precode.match(backtick);
if (backticks && !codeBlock && cursor === cursorDefault) {
precode = precode.replace(backtick, '');
cursor = '█';
}
if (tokens.match(/^\n/)) {
tokens = tokens.replace(/^\n/, '');
}
if (bing) {
tokens = citeText(tokens, true);
}
sendMessage(res, { text: tokens + cursor, message: true, initial: i === 0, ...rest });
_onProgress && _onProgress({ text: tokens, message: true, initial: i === 0, ...rest });
i++;
};
const onProgress = (opts) => {
return _.partialRight(progressCallback, opts);
};
const getPartialText = () => {
return tokens;
};
return { onProgress, getPartialText };
};
const handleText = async (response, bing = false) => {
let { text } = response;
// text = await detectCode(text);
response.text = text;
if (bing) {
// const hasCitations = response.response.match(citationRegex)?.length > 0;
const links = getCitations(response);
if (response.text.match(citationRegex)?.length > 0) {
text = citeText(response);
}
text += links?.length > 0 ? `\n<small>${links}</small>` : '';
}
return text;
};
module.exports = { handleError, sendMessage, createOnProgress, handleText };
+15
View File
@@ -0,0 +1,15 @@
const express = require('express');
const router = express.Router();
// const askAzureOpenAI = require('./askAzureOpenAI';)
const askOpenAI = require('./askOpenAI');
const askGoogle = require('./askGoogle');
const askBingAI = require('./askBingAI');
const askChatGPTBrowser = require('./askChatGPTBrowser');
// router.use('/azureOpenAI', askAzureOpenAI);
router.use('/openAI', askOpenAI);
router.use('/google', askGoogle);
router.use('/bingAI', askBingAI);
router.use('/chatGPTBrowser', askChatGPTBrowser);
module.exports = router;
+25
View File
@@ -0,0 +1,25 @@
const express = require('express');
const {
resetPasswordRequestController,
resetPasswordController,
getUserController,
loginController,
logoutController,
refreshController,
registrationController,
} = require('../controllers/auth.controller');
const requireJwtAuth = require('../../middleware/requireJwtAuth');
const requireLocalAuth = require('../../middleware/requireLocalAuth');
const router = express.Router();
//Local
router.get('/user', requireJwtAuth, getUserController);
router.post('/logout', requireJwtAuth, logoutController);
router.post('/login', requireLocalAuth, loginController);
router.post('/refresh', requireJwtAuth, refreshController);
router.post('/register', registrationController);
router.post('/requestPasswordReset', resetPasswordRequestController);
router.post('/resetPassword', resetPasswordController);
module.exports = router;
+54
View File
@@ -0,0 +1,54 @@
const express = require('express');
const router = express.Router();
const { getConvo, saveConvo } = require('../../models');
const { getConvosByPage, deleteConvos } = require('../../models/Conversation');
const requireJwtAuth = require('../../middleware/requireJwtAuth');
router.get('/', requireJwtAuth, async (req, res) => {
const pageNumber = req.query.pageNumber || 1;
res.status(200).send(await getConvosByPage(req.user.id, pageNumber));
});
router.get('/:conversationId', requireJwtAuth, async (req, res) => {
const { conversationId } = req.params;
const convo = await getConvo(req.user.id, conversationId);
if (convo) res.status(200).send(convo.toObject());
else res.status(404).end();
});
router.post('/clear', requireJwtAuth, async (req, res) => {
let filter = {};
const { conversationId, source } = req.body.arg;
if (conversationId) {
filter = { conversationId };
}
console.log('source:', source);
if (source === 'button' && !conversationId) {
return res.status(200).send('No conversationId provided');
}
try {
const dbResponse = await deleteConvos(req.user.id, filter);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
router.post('/update', requireJwtAuth, async (req, res) => {
const update = req.body.arg;
try {
const dbResponse = await saveConvo(req.user.id, update);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
module.exports = router;
+58
View File
@@ -0,0 +1,58 @@
const express = require('express');
const router = express.Router();
const getOpenAIModels = () => {
let models = ['gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301'];
if (process.env.OPENAI_MODELS) models = String(process.env.OPENAI_MODELS).split(',');
return models;
};
const getChatGPTBrowserModels = () => {
let models = ['text-davinci-002-render-sha', 'gpt-4'];
if (process.env.CHATGPT_MODELS) models = String(process.env.CHATGPT_MODELS).split(',');
return models;
};
let i = 0;
router.get('/', async function (req, res) {
let key, palmUser;
try {
key = require('../../data/auth.json');
} catch (e) {
if (i === 0) {
console.log("No 'auth.json' file (service account key) found in /api/data/ for PaLM models");
i++;
}
}
if (process.env.PALM_KEY === 'user_provided') {
palmUser = true;
if (i <= 1) {
console.log('User will provide key for PaLM models');
i++;
}
}
const google =
key || palmUser ? { userProvide: palmUser, availableModels: ['chat-bison', 'text-bison'] } : false;
const azureOpenAI = !!process.env.AZURE_OPENAI_KEY;
const openAI =
process.env.OPENAI_KEY || process.env.AZURE_OPENAI_API_KEY
? { availableModels: getOpenAIModels() }
: false;
const bingAI = process.env.BINGAI_TOKEN
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }
: false;
const chatGPTBrowser = process.env.CHATGPT_TOKEN
? {
userProvide: process.env.CHATGPT_TOKEN == 'user_provided',
availableModels: getChatGPTBrowserModels()
}
: false;
res.send(JSON.stringify({ azureOpenAI, openAI, google, bingAI, chatGPTBrowser }));
});
module.exports = { router, getOpenAIModels, getChatGPTBrowserModels };
+23
View File
@@ -0,0 +1,23 @@
const ask = require('./ask');
const messages = require('./messages');
const convos = require('./convos');
const presets = require('./presets');
const prompts = require('./prompts');
const search = require('./search');
const tokenizer = require('./tokenizer');
const auth = require('./auth');
const oauth = require('./oauth');
const { router: endpoints } = require('./endpoints');
module.exports = {
search,
ask,
messages,
convos,
presets,
prompts,
auth,
oauth,
tokenizer,
endpoints,
};
+11
View File
@@ -0,0 +1,11 @@
const express = require('express');
const router = express.Router();
const { getMessages } = require('../../models/Message');
const requireJwtAuth = require('../../middleware/requireJwtAuth');
router.get('/:conversationId', requireJwtAuth, async (req, res) => {
const { conversationId } = req.params;
res.status(200).send(await getMessages({ conversationId }));
});
module.exports = router;
+64
View File
@@ -0,0 +1,64 @@
const passport = require('passport');
const express = require('express');
const router = express.Router();
const isProduction = process.env.NODE_ENV === 'production';
const clientUrl = isProduction ? process.env.CLIENT_URL_PROD : process.env.CLIENT_URL_DEV;
// Social
router.get(
'/google',
passport.authenticate('google', {
scope: ['openid', 'profile', 'email'],
session: false
})
);
router.get(
'/google/callback',
passport.authenticate('google', {
failureRedirect: `${clientUrl}/login`,
failureMessage: true,
session: false,
scope: ['openid', 'profile', 'email']
}),
(req, res) => {
const token = req.user.generateToken();
res.cookie('token', token, {
expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)),
httpOnly: false,
secure: isProduction
});
res.redirect(clientUrl);
}
);
router.get(
'/facebook',
passport.authenticate('facebook', {
scope: ['public_profile', 'email'],
session: false
})
);
router.get(
'/facebook/callback',
passport.authenticate('facebook', {
failureRedirect: `${clientUrl}/login`,
failureMessage: true,
session: false,
scope: ['public_profile', 'email']
}),
(req, res) => {
const token = req.user.generateToken();
res.cookie('token', token, {
expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)),
httpOnly: false,
secure: isProduction
});
res.redirect(clientUrl);
}
);
module.exports = router;
+54
View File
@@ -0,0 +1,54 @@
const express = require('express');
const router = express.Router();
const { getPresets, savePreset, deletePresets } = require('../../models');
const crypto = require('crypto');
const requireJwtAuth = require('../../middleware/requireJwtAuth');
router.get('/', requireJwtAuth, async (req, res) => {
const presets = (await getPresets(req.user.id)).map((preset) => {
return preset.toObject();
});
res.status(200).send(presets);
});
router.post('/', requireJwtAuth, async (req, res) => {
const update = req.body || {};
update.presetId = update?.presetId || crypto.randomUUID();
try {
await savePreset(req.user.id, update);
const presets = (await getPresets(req.user.id)).map((preset) => {
return preset.toObject();
});
res.status(201).send(presets);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
router.post('/delete', requireJwtAuth, async (req, res) => {
let filter = {};
const { presetId } = req.body.arg || {};
if (presetId) filter = { presetId };
console.log('delete preset filter', filter);
try {
await deletePresets(req.user.id, filter);
const presets = (await getPresets(req.user.id)).map(preset => preset.toObject());
// console.log('delete preset response', presets);
res.status(201).send(presets);
// res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
module.exports = router;
+14
View File
@@ -0,0 +1,14 @@
const express = require('express');
const router = express.Router();
const { savePrompt, getPrompts, deletePrompts } = require('../../models/Prompt');
router.get('/', async (req, res) => {
let filter = {};
// const { search } = req.body.arg;
// if (!!search) {
// filter = { conversationId };
// }
res.status(200).send(await getPrompts(filter));
});
module.exports = router;
+125
View File
@@ -0,0 +1,125 @@
const express = require('express');
const router = express.Router();
const { MeiliSearch } = require('meilisearch');
const { Message } = require('../../models/Message');
const { Conversation, getConvosQueried } = require('../../models/Conversation');
const { reduceHits } = require('../../lib/utils/reduceHits');
const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc');
const requireJwtAuth = require('../../middleware/requireJwtAuth');
const cache = new Map();
router.get('/sync', async function (req, res) {
await Message.syncWithMeili();
await Conversation.syncWithMeili();
res.send('synced');
});
router.get('/', requireJwtAuth, async function (req, res) {
try {
let user = req.user.id;
user = user ?? null;
const { q } = req.query;
const pageNumber = req.query.pageNumber || 1;
const key = `${user || ''}${q}`;
if (cache.has(key)) {
console.log('cache hit', key);
const cached = cache.get(key);
const { pages, pageSize, messages } = cached;
res.status(200).send({ conversations: cached[pageNumber], pages, pageNumber, pageSize, messages });
return;
} else {
cache.clear();
}
// const message = await Message.meiliSearch(q);
const messages = (
await Message.meiliSearch(
q,
{
attributesToHighlight: ['text'],
highlightPreTag: '**',
highlightPostTag: '**'
},
true
)
).hits.map(message => {
const { _formatted, ...rest } = message;
return {
...rest,
searchResult: true,
text: _formatted.text
};
});
const titles = (await Conversation.meiliSearch(q)).hits;
const sortedHits = reduceHits(messages, titles);
// debugging:
// console.log('user:', user, 'message hits:', messages.length, 'convo hits:', titles.length);
// console.log('sorted hits:', sortedHits.length);
const result = await getConvosQueried(user, sortedHits, pageNumber);
const activeMessages = [];
for (let i = 0; i < messages.length; i++) {
let message = messages[i];
if (message.conversationId.includes('--')) {
message.conversationId = cleanUpPrimaryKeyValue(message.conversationId);
}
if (result.convoMap[message.conversationId] && !message.error) {
const convo = result.convoMap[message.conversationId];
const { title, chatGptLabel, model } = convo;
message = { ...message, ...{ title, chatGptLabel, model } };
activeMessages.push(message);
}
}
result.messages = activeMessages;
if (result.cache) {
result.cache.messages = activeMessages;
cache.set(key, result.cache);
delete result.cache;
}
delete result.convoMap;
// for debugging
// console.log(result, messages.length);
res.status(200).send(result);
} catch (error) {
console.log(error);
res.status(500).send({ message: 'Error searching' });
}
});
router.get('/clear', async function (req, res) {
await Message.resetIndex();
res.send('cleared');
});
router.get('/test', async function (req, res) {
const { q } = req.query;
const messages = (await Message.meiliSearch(q, { attributesToHighlight: ['text'] }, true)).hits.map(
message => {
const { _formatted, ...rest } = message;
return { ...rest, searchResult: true, text: _formatted.text };
}
);
res.send(messages);
});
router.get('/enable', async function (req, res) {
let result = false;
try {
const client = new MeiliSearch({
host: process.env.MEILI_HOST,
apiKey: process.env.MEILI_MASTER_KEY
});
const { status } = await client.health();
// console.log(`Meilisearch: ${status}`);
result = status === 'available' && !!process.env.SEARCH;
return res.send(result);
} catch (error) {
// console.error(error);
return res.send(false);
}
});
module.exports = router;
+26
View File
@@ -0,0 +1,26 @@
const express = require('express');
const router = express.Router();
const { Tiktoken } = require('@dqbd/tiktoken/lite');
const { load } = require('@dqbd/tiktoken/load');
const registry = require('@dqbd/tiktoken/registry.json');
const models = require('@dqbd/tiktoken/model_to_encoding.json');
const requireJwtAuth = require('../../middleware/requireJwtAuth');
router.post('/', requireJwtAuth, async (req, res) => {
try {
const { arg } = req.body;
// console.log('context:', arg, req.body);
// console.log(typeof req.body === 'object' ? { ...req.body, ...req.query } : req.query);
const model = await load(registry[models['gpt-3.5-turbo']]);
const encoder = new Tiktoken(model.bpe_ranks, model.special_tokens, model.pat_str);
const tokens = encoder.encode(arg.text);
encoder.free();
res.send({ count: tokens.length });
} catch (e) {
console.error(e);
res.status(500).send(e.message);
}
});
module.exports = router;
+197
View File
@@ -0,0 +1,197 @@
const User = require('../../models/User');
const Token = require('../../models/schema/tokenSchema');
const sendEmail = require('../../utils/sendEmail');
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const DebugControl = require('../../utils/debug.js');
const Joi = require('joi');
const { registerSchema } = require('../../strategies/validators');
const migrateDataToFirstUser = require('../../utils/migrateDataToFirstUser');
function log({ title, parameters }) {
DebugControl.log.functionName(title);
DebugControl.log.parameters(parameters);
}
const isProduction = process.env.NODE_ENV === 'production';
const clientUrl = isProduction ? process.env.CLIENT_URL_PROD : process.env.CLIENT_URL_DEV;
const loginUser = async (user) => {
// const refreshToken = req.user.generateRefreshToken();
const dbUser = await User.findById(user._id);
//todo: save refresh token
return dbUser;
};
const logoutUser = async (user, refreshToken) => {
User.findById(user._id).then((user) => {
const tokenIndex = user.refreshToken.findIndex(item => item.refreshToken === refreshToken);
if (tokenIndex !== -1) {
user.refreshToken.id(user.refreshToken[tokenIndex]._id).remove();
}
user.save((err) => {
if (err) {
return { status: 500, message: err.message };
} else {
//res.clearCookie('refreshToken', COOKIE_OPTIONS);
// removeTokenCookie(res);
return { status: 200, message: 'Logout successful' };
}
});
});
return { status: 200, message: 'Logout successful' };
};
const registerUser = async (user) => {
let response = {};
const { error } = Joi.validate(user, registerSchema);
if (error) {
log({
title: 'Route: register - Joi Validation Error',
parameters: [
{ name: 'Request params:', value: user },
{ name: 'Validation error:', value: error.details }
]
});
response = { status: 422, message: error.details[0].message };
return response;
}
const { email, password, name, username } = user;
try {
const existingUser = await User.findOne({ email });
if (existingUser) {
log({
title: 'Register User - Email in use',
parameters: [
{ name: 'Request params:', value: user },
{ name: 'Existing user:', value: existingUser }
]
});
response = { status: 422, message: 'Email is in use' };
return response;
}
//determine if this is the first registered user (not counting anonymous_user)
const isFirstRegisteredUser = await User.countDocuments({}) === 0;
try {
const newUser = await new User({
provider: 'local',
email,
password,
username,
name,
avatar: null,
role: isFirstRegisteredUser ? 'ADMIN' : 'USER',
});
// todo: implement refresh token
// const refreshToken = newUser.generateRefreshToken();
// newUser.refreshToken.push({ refreshToken });
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (errh, hash) => {
if (err) {
console.log(err);
}
// set pasword to hash
newUser.password = hash;
newUser.save();
});
});
console.log('newUser', newUser)
if (isFirstRegisteredUser) {
migrateDataToFirstUser(newUser);
// console.log(migrate);
}
response = { status: 200, user: newUser };
return response;
} catch (err) {
response = { status: 500, message: err.message };
return response;
}
} catch (err) {
response = { status: 500, message: err.message };
return response;
}
};
const requestPasswordReset = async (email) => {
const user = await User.findOne({ email });
if (!user) {
return new Error('Email does not exist');
}
let token = await Token.findOne({ userId: user._id });
if (token) await token.deleteOne();
let resetToken = crypto.randomBytes(32).toString('hex');
const hash = await bcrypt.hash(resetToken, 10);
await new Token({
userId: user._id,
token: hash,
createdAt: Date.now()
}).save();
const link = `${clientUrl}/reset-password?token=${resetToken}&userId=${user._id}`;
sendEmail(
user.email,
'Password Reset Request',
{
name: user.name,
link: link
},
'./template/requestResetPassword.handlebars'
);
return { link };
};
const resetPassword = async (userId, token, password) => {
let passwordResetToken = await Token.findOne({ userId });
if (!passwordResetToken) {
return new Error('Invalid or expired password reset token');
}
const isValid = await bcrypt.compare(token, passwordResetToken.token);
if (!isValid) {
return new Error('Invalid or expired password reset token');
}
const hash = await bcrypt.hash(password, 10);
await User.updateOne({ _id: userId }, { $set: { password: hash } }, { new: true });
const user = await User.findById({ _id: userId });
sendEmail(
user.email,
'Password Reset Successfnodeully',
{
name: user.name
},
'./template/resetPassword.handlebars'
);
await passwordResetToken.deleteOne();
return { message: 'Password reset was successful' };
};
module.exports = {
// signup,
registerUser,
loginUser,
logoutUser,
requestPasswordReset,
resetPassword,
};
+60
View File
@@ -0,0 +1,60 @@
const passport = require('passport');
const FacebookStrategy = require('passport-facebook').Strategy;
const User = require('../models/User');
const serverUrl =
process.env.NODE_ENV === 'production' ? process.env.SERVER_URL_PROD : process.env.SERVER_URL_DEV;
// facebook strategy
const facebookLogin = new FacebookStrategy(
{
clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_SECRET,
callbackURL: `${serverUrl}${process.env.FACEBOOK_CALLBACK_URL}`,
proxy: true,
// profileFields: [
// 'id',
// 'email',
// 'gender',
// 'profileUrl',
// 'displayName',
// 'locale',
// 'name',
// 'timezone',
// 'updated_time',
// 'verified',
// 'picture.type(large)'
// ]
},
async (accessToken, refreshToken, profile, done) => {
console.log('facebookLogin => profile', profile);
try {
const oldUser = await User.findOne({ email: profile.emails[0].value });
if (oldUser) {
console.log('FACEBOOK LOGIN => found user', oldUser);
return done(null, oldUser);
}
} catch (err) {
console.log(err);
}
// register user
try {
const newUser = await new User({
provider: 'facebook',
facebookId: profile.id,
username: profile.name.givenName + profile.name.familyName,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value
}).save();
done(null, newUser);
} catch (err) {
console.log(err);
}
}
);
passport.use(facebookLogin);
+44
View File
@@ -0,0 +1,44 @@
const passport = require('passport');
const { Strategy: GoogleStrategy } = require('passport-google-oauth20');
const User = require('../models/User');
const serverUrl =
process.env.NODE_ENV === 'production' ? process.env.SERVER_URL_PROD : process.env.SERVER_URL_DEV;
// google strategy
const googleLogin = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `${serverUrl}${process.env.GOOGLE_CALLBACK_URL}`,
proxy: true
},
async (accessToken, refreshToken, profile, cb) => {
try {
const oldUser = await User.findOne({ email: profile.emails[0].value });
if (oldUser) {
return cb(null, oldUser);
}
} catch (err) {
console.log(err);
}
try {
const newUser = await new User({
provider: 'google',
googleId: profile.id,
username: profile.name.givenName,
email: profile.emails[0].value,
emailVerified: profile.emails[0].verified,
name: `${profile.name.givenName} ${profile.name.familyName}`,
avatar: profile.photos[0].value
}).save();
cb(null, newUser);
} catch (err) {
console.log(err);
}
}
);
passport.use(googleLogin);
+29
View File
@@ -0,0 +1,29 @@
const passport = require('passport');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const User = require('../models/User');
const isProduction = process.env.NODE_ENV === 'production';
const secretOrKey = isProduction ? process.env.JWT_SECRET_PROD : process.env.JWT_SECRET_DEV;
// JWT strategy
const jwtLogin = new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey
},
async (payload, done) => {
try {
const user = await User.findById(payload.id);
if (user) {
done(null, user);
} else {
console.log('JwtStrategy => no user found');
done(null, false);
}
} catch (err) {
done(err, false);
}
}
);
passport.use(jwtLogin);
+68
View File
@@ -0,0 +1,68 @@
const passport = require('passport');
const PassportLocalStrategy = require('passport-local').Strategy;
const Joi = require('joi');
const User = require('../models/User');
const { loginSchema } = require('./validators');
const DebugControl = require('../utils/debug.js');
const passportLogin = new PassportLocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
session: false,
passReqToCallback: true
},
async (req, email, password, done) => {
const { error } = Joi.validate(req.body, loginSchema);
if (error) {
log({
title: 'Passport Local Strategy - Validation Error',
parameters: [{ name: 'req.body', value: req.body }]
});
return done(null, false, { message: error.details[0].message });
}
try {
const user = await User.findOne({ email: email.trim() });
if (!user) {
log({
title: 'Passport Local Strategy - User Not Found',
parameters: [{ name: 'email', value: email }]
});
return done(null, false, { message: 'Email does not exists.' });
}
user.comparePassword(password, function (err, isMatch) {
if (err) {
log({
title: 'Passport Local Strategy - Compare password error',
parameters: [{ name: 'error', value: err }]
});
return done(err);
}
if (!isMatch) {
log({
title: 'Passport Local Strategy - Password does not match',
parameters: [{ name: 'isMatch', value: isMatch }]
});
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
} catch (err) {
return done(err);
}
}
);
passport.use(passportLogin);
function log({ title, parameters }) {
DebugControl.log.functionName(title);
if (parameters) {
DebugControl.log.parameters(parameters);
}
}
+24
View File
@@ -0,0 +1,24 @@
const Joi = require('joi');
const loginSchema = Joi.object().keys({
email: Joi.string().trim().email().required(),
password: Joi.string().trim().min(6).max(20).required()
});
const registerSchema = Joi.object().keys({
name: Joi.string().trim().min(2).max(30).required(),
username: Joi.string()
.trim()
.min(2)
.max(20)
.regex(/^[a-zA-Z0-9_]+$/)
.required(),
email: Joi.string().trim().email().required(),
password: Joi.string().trim().min(6).max(20).required(),
confirm_password: Joi.string().trim().min(6).max(20).required()
});
module.exports = {
loginSchema,
registerSchema
};
+125
View File
@@ -0,0 +1,125 @@
const pino = require('pino');
const logger = pino({
level: 'info',
redact: {
paths: [ // List of Paths to redact from the logs (https://getpino.io/#/docs/redaction)
'env.OPENAI_KEY',
'env.BINGAI_TOKEN',
'env.CHATGPT_TOKEN',
'env.MEILI_MASTER_KEY',
'env.GOOGLE_CLIENT_SECRET',
'env.JWT_SECRET_DEV',
'env.JWT_SECRET_PROD',
'newUser.password'], // See example to filter object class instances
censor: '***', // Redaction character
},
});
// Sanitize outside the logger paths. This is useful for sanitizing variables directly with Regex and patterns.
const redactPatterns = [ // Array of regular expressions for redacting patterns
/api[-_]?key/i,
/password/i,
/token/i,
/secret/i,
/key/i,
/certificate/i,
/client[-_]?id/i,
/authorization[-_]?code/i,
/authorization[-_]?login[-_]?hint/i,
/authorization[-_]?acr[-_]?values/i,
/authorization[-_]?response[-_]?mode/i,
/authorization[-_]?nonce/i
];
/*
// Example of redacting sensitive data from object class instances
function redactSensitiveData(obj) {
if (obj instanceof User) {
return {
...obj.toObject(),
password: '***', // Redact the password field
};
}
return obj;
}
// Example of redacting sensitive data from object class instances
logger.info({ newUser: redactSensitiveData(newUser) }, 'newUser');
*/
const levels = {
TRACE: 10,
DEBUG: 20,
INFO: 30,
WARN: 40,
ERROR: 50,
FATAL: 60
};
let level = levels.INFO;
module.exports = {
levels,
setLevel: (l) => (level = l),
log: {
trace: (msg) => {
if (level <= levels.TRACE) return;
logger.trace(msg);
},
debug: (msg) => {
if (level <= levels.DEBUG) return;
logger.debug(msg);
},
info: (msg) => {
if (level <= levels.INFO) return;
logger.info(msg);
},
warn: (msg) => {
if (level <= levels.WARN) return;
logger.warn(msg);
},
error: (msg) => {
if (level <= levels.ERROR) return;
logger.error(msg);
},
fatal: (msg) => {
if (level <= levels.FATAL) return;
logger.fatal(msg);
},
// Custom loggers
parameters: (parameters) => {
if (level <= levels.TRACE) return;
logger.debug({ parameters }, 'Function Parameters');
},
functionName: (name) => {
if (level <= levels.TRACE) return;
logger.debug(`EXECUTING: ${name}`);
},
flow: (flow) => {
if (level <= levels.INFO) return;
logger.debug(`BEGIN FLOW: ${flow}`);
},
variable: ({ name, value }) => {
if (level <= levels.DEBUG) return;
// Check if the variable name matches any of the redact patterns and redact the value
let sanitizedValue = value;
for (const pattern of redactPatterns) {
if (pattern.test(name)) {
sanitizedValue = '***';
break;
}
}
logger.debug({ variable: { name, value: sanitizedValue } }, `VARIABLE ${name}`);
},
request: () => (req, res, next) => {
if (level < levels.DEBUG) return next();
logger.debug({ query: req.query, body: req.body }, `Hit URL ${req.url} with following`);
return next();
}
}
};
+46
View File
@@ -0,0 +1,46 @@
const levels = {
NONE: 0,
LOW: 1,
MEDIUM: 2,
HIGH: 3
};
let level = levels.HIGH;
module.exports = {
levels,
setLevel: (l) => (level = l),
log: {
parameters: (parameters) => {
if (levels.HIGH > level) return;
console.group();
parameters.forEach((p) => console.log(`${p.name}:`, p.value));
console.groupEnd();
},
functionName: (name) => {
if (levels.MEDIUM > level) return;
console.log(`\nEXECUTING: ${name}\n`);
},
flow: (flow) => {
if (levels.LOW > level) return;
console.log(`\n\n\nBEGIN FLOW: ${flow}\n\n\n`);
},
variable: ({ name, value }) => {
if (levels.HIGH > level) return;
console.group();
console.group();
console.log(`VARIABLE ${name}:`, value);
console.groupEnd();
console.groupEnd();
},
request: () => (req, res, next) => {
if (levels.HIGH > level) return next();
console.log('Hit URL', req.url, 'with following:');
console.group();
console.log('Query:', req.query);
console.log('Body:', req.body);
console.groupEnd();
return next();
}
}
};
+11
View File
@@ -0,0 +1,11 @@
<html>
<head>
<style>
</style>
</head>
<body>
<p>Hi {{name}},</p>
<p>Your password has been changed successfully.</p>
</body>
</html>
@@ -0,0 +1,13 @@
<html>
<head>
<style>
</style>
</head>
<body>
<p>Hi {{name}},</p>
<h1>You have requested to reset your password.</h1>
<p> Please click the link below to reset your password.</p>
<a href="{{link}}">Reset Password</a>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
function genAzureEndpoint({ azureOpenAIApiInstanceName, azureOpenAIApiDeploymentName, azureOpenAIApiVersion }) {
return `https://${azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${azureOpenAIApiDeploymentName}/chat/completions?api-version=${azureOpenAIApiVersion}`;
}
module.exports = { genAzureEndpoint };
+30
View File
@@ -0,0 +1,30 @@
const Conversation = require('../models/schema/convoSchema');
const Preset = require('../models/schema/presetSchema');
const migrateConversations = async (userId) => {
try {
return await Conversation.updateMany({ user: null }, { $set: { user: userId }}).exec();
} catch (error) {
console.log(error);
return { message: 'Error saving conversation' };
}
}
const migratePresets = async (userId) => {
try {
return await Preset.updateMany({ user: null }, { $set: { user: userId }}).exec();
} catch (error) {
console.log(error);
return { message: 'Error saving conversation' };
}
}
const migrateDataToFirstUser = async (user) => {
const conversations = await migrateConversations(user.id);
console.log(conversations);
const presets = await migratePresets(user.id);
console.log(presets);
}
module.exports = migrateDataToFirstUser;
+54
View File
@@ -0,0 +1,54 @@
const nodemailer = require("nodemailer");
const handlebars = require("handlebars");
const fs = require("fs");
const path = require("path");
const sendEmail = async (email, subject, payload, template) => {
try {
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: 465,
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
});
const source = fs.readFileSync(path.join(__dirname, template), "utf8");
const compiledTemplate = handlebars.compile(source);
const options = () => {
return {
from: process.env.FROM_EMAIL,
to: email,
subject: subject,
html: compiledTemplate(payload),
};
};
// Send email
transporter.sendMail(options(), (error, info) => {
if (error) {
return error;
} else {
return res.status(200).json({
success: true,
});
}
});
} catch (error) {
return error;
}
};
/*
Example:
sendEmail(
"youremail@gmail.com,
"Email subject",
{ name: "Eze" },
"./templates/layouts/main.handlebars"
);
*/
module.exports = sendEmail;
-21
View File
@@ -1,21 +0,0 @@
meta {
name: Get Messages
type: http
seq: 1
}
get {
url: {{url}}:{{port}}/api/method/raven.api.chat_stream.get_messages?channel_id=general
body: none
auth: none
}
query {
channel_id: general
~channel_id: admin-private
~channel_id: does-not-exist
}
headers {
Authorization: token {{api_key}}:{{api_secret}}
}
-21
View File
@@ -1,21 +0,0 @@
meta {
name: Get Channel Members
type: http
seq: 1
}
get {
url: {{url}}:{{port}}/api/method/raven.api.chat.get_channel_members?channel_id=general
body: none
auth: none
}
query {
channel_id: general
~channel_id: admin-private
~channel_id: does-not-exist
}
headers {
Authorization: token {{api_key}}:{{api_secret}}
}
@@ -1,20 +0,0 @@
meta {
name: Get Preview Link
type: http
seq: 1
}
get {
url: {{url}}:{{port}}/api/method/raven.api.preview_links.get_preview_link?urls=["https://ravenapp.info"]
body: none
auth: none
}
query {
urls: ["https://ravenapp.info"]
~urls: ["http://192.168.0.0"]
}
headers {
Authorization: token {{api_key}}:{{api_secret}}
}

Some files were not shown because too many files have changed in this diff Show More