├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── chatbot ├── __init__.py ├── asgi.py ├── config.py ├── settings.py ├── urls.py └── wsgi.py ├── chatchatchat ├── __init__.py ├── admin.py ├── apps.py ├── demo.html ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── createsecretkey.py ├── migrations │ └── __init__.py ├── models.py ├── static │ ├── css │ │ └── style.css │ ├── images │ │ └── default-head.png │ ├── js │ │ ├── character.js │ │ ├── chat.js │ │ ├── edit.js │ │ ├── helpers.js │ │ ├── initialize.js │ │ ├── memory.js │ │ ├── messageHistory.js │ │ ├── roleplay.js │ │ └── script.js │ └── media │ │ └── start-speech.mp3 ├── templates │ └── index.html ├── tests.py ├── urls.py └── views.py ├── image └── README │ ├── 1681363127495.png │ ├── 1681363147175.png │ ├── 1681363512437.png │ └── 1681363557274.png ├── manage.py ├── requirements.txt └── updatedemo.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | *.sqlite3 4 | *.pyc 5 | chatbot/mine_config.py 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 lilycyf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chatbot RPG using OpenAI's gpt models 2 | 3 | [中文](https://github.com/lilycyf/chatgpt-rpg/blob/master/README_zh.md) | English 4 | 5 | The objective of this project is to utilize OpenAI's API to create a chatbot that possesses unique characteristics and integrate it into an RPG game. 6 | 7 | Currently, the project features two built characters, Li Ming and Avery Kim, whom you can interact with under the roleplay section of the demo. To participate, make sure to obtain your own OpenAI KPI and input it in the provided field at the bottom left. It's essential to note that the demo is a silent website without a backend, so your OpenAI API won't be transmitted to anyone. In addition to the two characters, you can also communicate with the basic chatgpt via the chat section. 8 | 9 | Note: You can sign up for an API key from OpenAI. Generate an API key for OpenAI's service by following the instructions on the [OpenAI website](https://platform.openai.com/account/api-keys) for free. 10 | 11 | 🔗 [demo](https://lilycyf.github.io/chatgpt-rpg/chatchatchat/demo.html) 12 | 13 | ## Screenshots 14 | 15 | 168136314717516813635124371681363557274 16 | 17 | ![1681363127495](image/README/1681363127495.png) 18 | 19 | ## Getting Started 20 | 21 | 1. Clone this repository to your local machine. 22 | 2. (optional) Create a virtual environment: 23 | 1. Run the command `python3 -m venv venv` to create a new virtual environment named "venv". 24 | 2. Activate the virtual environment by running the command `source venv/bin/activate`. On Windows, use `.\venv\Scripts\activate` instead. 25 | 3. Install the required packages listed in the `requirements.txt` file using `pip install -r requirements.txt` in your terminal. 26 | 4. Make a copy of `config.py` under the `chatbot` folder with name `mine_config.py`. 27 | 5. Generate your own secret key in Django by running the command `python manage.py generate_secret_key` in your terminal. Replace the `SECRET_KEY` in `mine_config.py` under the `chatbot` folder with the key that you generated. 28 | 6. Sign up for an API key from OpenAI. Generate an API key for OpenAI's service by following the instructions on the [OpenAI website](https://platform.openai.com/account/api-keys) and replace the `OPENAI_API_KEY` in `mine_config.py` under the `chatbot` folder with your own key. 29 | 30 | ## Running the Chatbot 31 | 32 | Note: If you set up a virtual environment in step 2 you will need to activate it before running any commands related to the project. To activate the virtual environment, run `source env/bin/activate`. To deactivate it, simply run the `deactivate` command. 33 | 34 | Before start the server, navigate to the root folder of the project in your terminal and run the command `python manage.py migrate` to apply the database migrations. 35 | 36 | To run the chatbot, run the command `python manage.py runserver`. Then open your web browser and go to `http://localhost:8000` to access the chatbot application. 37 | 38 | ## Conclusion 39 | 40 | This project provides an example of how to build a chatbot application using OpenAI's gpt-3.5-turbo. Feel free to modify the code to suit your needs, and don't forget to create your own secret key and API key when running the application. 41 | 42 | ## License 43 | 44 | This project is released under the [MIT License](./LICENSE). 45 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # Chatbot RPG using OpenAI's gpt models 2 | 3 | 中文 | [English](https://github.com/lilycyf/chatgpt-rpg/blob/master/README.md) 4 | 5 | 这个项目的目标是利用OpenAI的API创建一个具有独特特征并将其整合到RPG游戏中的聊天机器人。 6 | 7 | 目前,该项目包括两个构建好的角色,李明和艾弗里·金,你可以在demo的role play与他们互动。为了参与其中,请确保获取自己的OpenAI KPI,并在左下方的输入框中提交。demo是一个没有后端的静态网站,因此你的OpenAI API不会被传输给任何人。除了两个角色之外,你还可以通过chat与基本的ChatGPT进行交流。 8 | 9 | 注意:你可以按照[OpenAI网站](https://platform.openai.com/account/api-keys)上的说明免费生成OpenAI服务的API密钥。 10 | 11 | 🔗 [demo](https://lilycyf.github.io/chatgpt-rpg/chatchatchat/demo.html) 12 | 13 | ## Screenshots 14 | 15 | 168136314717516813635124371681363557274 16 | 17 | ![1681363127495](image/README/1681363127495.png) 18 | 19 | ## Getting Started 20 | 21 | 1. 将此存储库克隆到本地计算机。 22 | 2. (可选)创建虚拟环境: 23 | 1. 运行命令 `python3 -m venv venv`以创建名为 "venv" 的新虚拟环境。 24 | 2. 激活虚拟环境,请在终端中运行命令 `source venv/bin/activate`。在 Windows 上,请使用 `.\venv\Scripts\activate`。 25 | 3. 在终端中使用 `pip install -r requirements.txt` 命令安装 `requirements.txt` 文件中列出的必需包。 26 | 4. 复制 `chatbot` 文件夹下的 `config.py` 文件并将其重命名为 `mine_config.py`。 27 | 5. 在终端中运行命令 `python manage.py generate_secret_key` 以在 Django 中生成自己的密钥。然后将 `chatbot` 文件夹下 `mine_config.py` 文件中的 `SECRET_KEY` 替换为你生成的密钥。 28 | 6. 在 OpenAI 上注册 API 密钥。请按照 [OpenAI 网站](https://platform.openai.com/account/api-keys) 上的说明生成 OpenAI 服务的 API 密钥,然后将 `chatbot` 文件夹下的 `mine_config.py` 文件中的 `OPENAI_API_KEY` 替换为你自己的密钥。 29 | 30 | ## Running the Chatbot 31 | 32 | 注意:如果在步骤2中设置了虚拟环境,则在运行与该项目相关的任何命令之前,你需要激活它。要激活虚拟环境,请运行 `source env/bin/activate`。要停用它,只需运行 `deactivate`命令。 33 | 34 | 在开始服务器之前,请在终端中导航到项目的根文件夹,并运行命令 `python manage.py migrate`以应用数据库迁移。 35 | 36 | 要运行聊天机器人,请运行命令 `python manage.py runserver`。然后打开你的Web浏览器,转到 `http://localhost:8000`以访问聊天机器人应用程序。 37 | 38 | ## Conclusion 39 | 40 | 该项目提供了如何使用OpenAI的gpt-3.5-turbo构建聊天机器人应用程序的示例。随意修改代码以满足你的需求,并在运行应用程序时不要忘记创建自己的秘密密钥和API密钥。 41 | 42 | ## License 43 | 44 | This project is released under the [MIT License](./LICENSE). 45 | -------------------------------------------------------------------------------- /chatbot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilycyf/chatgpt-rpg/b9379a4bf8e0937c8d9b2fc94a3d24c6ae216155/chatbot/__init__.py -------------------------------------------------------------------------------- /chatbot/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for chatbot project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chatbot.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /chatbot/config.py: -------------------------------------------------------------------------------- 1 | SECRET_KEY = '' 2 | OPENAI_API_KEY = '' 3 | -------------------------------------------------------------------------------- /chatbot/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for chatbot project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | from .mine_config import SECRET_KEY, OPENAI_API_KEY 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = SECRET_KEY 24 | OPENAI_API_KEY = OPENAI_API_KEY 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "chatchatchat" 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | "django.middleware.security.SecurityMiddleware", 45 | "django.contrib.sessions.middleware.SessionMiddleware", 46 | "django.middleware.common.CommonMiddleware", 47 | "django.middleware.csrf.CsrfViewMiddleware", 48 | "django.contrib.auth.middleware.AuthenticationMiddleware", 49 | "django.contrib.messages.middleware.MessageMiddleware", 50 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 51 | ] 52 | 53 | ROOT_URLCONF = "chatbot.urls" 54 | 55 | TEMPLATES = [ 56 | { 57 | "BACKEND": "django.template.backends.django.DjangoTemplates", 58 | "DIRS": [], 59 | "APP_DIRS": True, 60 | "OPTIONS": { 61 | "context_processors": [ 62 | "django.template.context_processors.debug", 63 | "django.template.context_processors.request", 64 | "django.contrib.auth.context_processors.auth", 65 | "django.contrib.messages.context_processors.messages", 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = "chatbot.wsgi.application" 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 76 | 77 | DATABASES = { 78 | "default": { 79 | "ENGINE": "django.db.backends.sqlite3", 80 | "NAME": BASE_DIR / "db.sqlite3", 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 91 | }, 92 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, 93 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, 94 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, 95 | ] 96 | 97 | 98 | # Internationalization 99 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 100 | 101 | LANGUAGE_CODE = "en-us" 102 | 103 | TIME_ZONE = "UTC" 104 | 105 | USE_I18N = True 106 | 107 | USE_TZ = True 108 | 109 | 110 | # Static files (CSS, JavaScript, Images) 111 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 112 | 113 | STATIC_URL = "static/" 114 | 115 | # Default primary key field type 116 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 117 | 118 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 119 | -------------------------------------------------------------------------------- /chatbot/urls.py: -------------------------------------------------------------------------------- 1 | """chatbot URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path('', include('chatchatchat.urls')), 21 | ] 22 | 23 | 24 | -------------------------------------------------------------------------------- /chatbot/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for chatbot project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chatbot.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /chatchatchat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilycyf/chatgpt-rpg/b9379a4bf8e0937c8d9b2fc94a3d24c6ae216155/chatchatchat/__init__.py -------------------------------------------------------------------------------- /chatchatchat/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /chatchatchat/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ChatchatchatConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "chatchatchat" 7 | -------------------------------------------------------------------------------- /chatchatchat/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ChatGPT Web Interface 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 101 |
102 | 177 | 1194 | 1218 |
1219 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | -------------------------------------------------------------------------------- /chatchatchat/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilycyf/chatgpt-rpg/b9379a4bf8e0937c8d9b2fc94a3d24c6ae216155/chatchatchat/management/__init__.py -------------------------------------------------------------------------------- /chatchatchat/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilycyf/chatgpt-rpg/b9379a4bf8e0937c8d9b2fc94a3d24c6ae216155/chatchatchat/management/commands/__init__.py -------------------------------------------------------------------------------- /chatchatchat/management/commands/createsecretkey.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.management.utils import get_random_secret_key 3 | 4 | class Command(BaseCommand): 5 | help = 'Generates a new secret key' 6 | 7 | def handle(self, *args, **options): 8 | self.stdout.write(get_random_secret_key()) 9 | -------------------------------------------------------------------------------- /chatchatchat/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilycyf/chatgpt-rpg/b9379a4bf8e0937c8d9b2fc94a3d24c6ae216155/chatchatchat/migrations/__init__.py -------------------------------------------------------------------------------- /chatchatchat/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /chatchatchat/static/css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --theme-light:#ffeded; 3 | --theme: #f8cfcf; 4 | /* --theme: linear-gradient(45deg, #ffcda5, #ee4d5f); */ 5 | --theme-hover: #edadad; 6 | --button-submit: #e78c8c; 7 | --button-submit-hover: #e37d7d; 8 | } 9 | 10 | html, 11 | body { 12 | height: 100%; 13 | width: 100%; 14 | margin: 0; 15 | padding: 0; 16 | display: flex; 17 | position: fixed; 18 | } 19 | audio { 20 | width: 100%; 21 | } 22 | button { 23 | cursor: pointer; 24 | } 25 | 26 | h1 { 27 | font-size: 2em; 28 | } 29 | 30 | h2, 31 | h3 { 32 | font-size: 2em; 33 | margin: 0px; 34 | margin-bottom: 20px; 35 | } 36 | 37 | .time { 38 | text-align: center; 39 | } 40 | 41 | .freeze-background { 42 | display: none; 43 | /* Hide the warning message box by default */ 44 | position: fixed; 45 | /* Stay in place */ 46 | z-index: 1; 47 | /* Sit on top */ 48 | left: 0; 49 | top: 0; 50 | width: 100%; 51 | /* Full width */ 52 | height: 100%; 53 | /* Full height */ 54 | overflow: auto; 55 | /* Enable scroll if needed */ 56 | background-color: rgba(0, 0, 0, 0.4); 57 | /* Black w/ opacity */ 58 | } 59 | 60 | .frozen { 61 | pointer-events: none; 62 | } 63 | 64 | /* top-bar */ 65 | .top-bar { 66 | position: fixed; 67 | top: 0; 68 | left: 0; 69 | width: 100%; 70 | height: 40px; 71 | background: var(--theme); 72 | align-items: center; 73 | display: none; 74 | } 75 | 76 | /* guide-bar */ 77 | #sidebarToggle { 78 | position: absolute; 79 | display: grid; 80 | height: 40px; 81 | align-items: center; 82 | left: 100%; 83 | top: 0; 84 | } 85 | 86 | .slide-out { 87 | animation-duration: 400ms; 88 | animation-name: slide-out; 89 | } 90 | 91 | @keyframes slide-out { 92 | 0% { 93 | transform: translateX(0%); 94 | opacity: 1; 95 | } 96 | 97 | 100% { 98 | transform: translateX(-100%); 99 | opacity: 0; 100 | } 101 | } 102 | 103 | .slide-in { 104 | animation-duration: 400ms; 105 | animation-name: slide-in; 106 | } 107 | 108 | @keyframes slide-in { 109 | 0% { 110 | transform: translateX(-100%); 111 | opacity: 0; 112 | } 113 | 114 | 100% { 115 | transform: translateX(0%); 116 | opacity: 1; 117 | } 118 | } 119 | 120 | .guide-bar { 121 | top: 0; 122 | left: 0; 123 | width: 200px; 124 | height: 100%; 125 | background: var(--theme); 126 | z-index: 1; 127 | flex-direction: column; 128 | position: relative 129 | } 130 | 131 | .sidebar--collapsed { 132 | width: 0; 133 | } 134 | 135 | .guide-bar-page-buttons { 136 | top: 0; 137 | left: 0; 138 | height: 40px; 139 | background-color: rgb(43, 43, 43); 140 | width: 100%; 141 | display: flex; 142 | flex-wrap: nowrap; 143 | overflow-x: scroll; 144 | } 145 | 146 | .guide-bar-page-buttons::-webkit-scrollbar { 147 | display: none; 148 | } 149 | 150 | 151 | .guide-bar-button { 152 | flex-shrink: 0; 153 | cursor: pointer; 154 | width: 75px; 155 | font-size: 13px; 156 | margin: 0px; 157 | border: none; 158 | background-color: rgb(154, 154, 154); 159 | border-top-right-radius: 10px; 160 | border-top-left-radius: 10px; 161 | box-shadow: inset 0px -2px 10px -5px black; 162 | color: #000; 163 | } 164 | 165 | .guide-bar-button.active { 166 | background: var(--theme); 167 | box-shadow: none; 168 | } 169 | 170 | .apis { 171 | position: absolute; 172 | bottom: 20px; 173 | padding: 5px; 174 | right: 0px; 175 | left: 0px 176 | } 177 | 178 | .api-input { 179 | font-size: 11px; 180 | height: 16px; 181 | padding: 0px; 182 | border: 0px; 183 | width: 100%; 184 | } 185 | 186 | .material-symbols-outlined#open-ai-api-toggle-btn { 187 | font-size: 20px; 188 | margin: 5px; 189 | font-variation-settings: 'OPSZ' 20; 190 | cursor: pointer; 191 | } 192 | 193 | .guide-bar-page-histories { 194 | height: 650px; 195 | overflow-y: scroll; 196 | } 197 | 198 | .guide-bar-page-histories::-webkit-scrollbar { 199 | display: none; 200 | } 201 | 202 | .chat-history { 203 | display: flex; 204 | align-items: center; 205 | height: 40px; 206 | padding: 7px; 207 | cursor: pointer; 208 | border-radius: 15px; 209 | margin: 5px; 210 | } 211 | 212 | /* 213 | .chat-history.active { 214 | background-color: white; 215 | border-top-right-radius: 0px; 216 | border-bottom-right-radius: 0px; 217 | cursor: default; 218 | margin-right: 0px; 219 | } */ 220 | 221 | .addnew { 222 | border-width: 1px; 223 | border-color: var(--theme-hover); 224 | border-style: dotted; 225 | position: relative; 226 | } 227 | 228 | .addnew .addnew_sign { 229 | color: black; 230 | position: absolute; 231 | /* top: 50%; */ 232 | /* left: 30px; */ 233 | /* transform: translate(-50%,-50%); */ 234 | } 235 | 236 | .addnew.active { 237 | border-right-width: 0px; 238 | } 239 | 240 | .chat-history-headshot { 241 | height: 100%; 242 | } 243 | 244 | .chat-history-headshot img { 245 | height: 100%; 246 | object-fit: cover; 247 | border-radius: 10px; 248 | } 249 | 250 | .chat-history-name { 251 | flex-grow: 1; 252 | max-height: 100%; 253 | overflow: hidden; 254 | white-space: nowrap; 255 | text-overflow: ellipsis; 256 | padding-left: 10px; 257 | } 258 | 259 | .chat-history-name h4 { 260 | 261 | margin: 0; 262 | } 263 | 264 | .chat-history-name p4 { 265 | margin: 0; 266 | font-size: 14px; 267 | } 268 | 269 | /* chat */ 270 | 271 | .container-container, 272 | .chat-container, 273 | .roleplay-container, 274 | .edit-container { 275 | position: relative; 276 | --my-margin-left: 200px; 277 | /* Set the margin value using a CSS variable */ 278 | --my-margin-top: 0px; 279 | /* Set the margin value using a CSS variable */ 280 | /* margin-left: var(--my-margin-left); */ 281 | margin-top: var(--my-margin-top); 282 | flex-grow: 1; 283 | height: calc(100% - var(--my-margin-top)); 284 | /* width: calc(100% - var(--my-margin-left)); */ 285 | } 286 | 287 | .chat, 288 | .edit, 289 | .roleplay { 290 | height: 100%; 291 | width: 100%; 292 | display: flex; 293 | flex-direction: column; 294 | align-items: center; 295 | justify-content: center; 296 | box-sizing: border-box; 297 | font-family: Arial, sans-serif; 298 | } 299 | 300 | .chat-header, 301 | .edit-header, 302 | .roleplay-header { 303 | /* display: none; */ 304 | text-align: center; 305 | margin-bottom: 20px; 306 | } 307 | 308 | .chat-messages, 309 | .edit-messages { 310 | height: 100%; 311 | width: 70%; 312 | margin-left: 15%; 313 | margin-right: 15%; 314 | margin-bottom: 50px; 315 | overflow-y: scroll; 316 | display: flex; 317 | flex-direction: column; 318 | } 319 | 320 | .chat-messages::-webkit-scrollbar, 321 | .edit-messages::-webkit-scrollbar { 322 | display: none; 323 | } 324 | 325 | .chatbot-message-container { 326 | display: flex; 327 | flex-direction: row; 328 | } 329 | 330 | .user-message-container { 331 | display: flex; 332 | flex-direction: row-reverse; 333 | } 334 | 335 | .user-message-container .message-headshot, 336 | .chatbot-message-container .message-headshot { 337 | height: 40px; 338 | width: 40px; 339 | margin: 10px 0px; 340 | position: relative; 341 | } 342 | 343 | .user-message-container .message-headshot img, 344 | .chatbot-message-container .message-headshot img { 345 | height: 100%; 346 | object-fit: cover; 347 | border-radius: 10px; 348 | } 349 | 350 | .chatbot-message { 351 | padding: 20px; 352 | max-width: 75%; 353 | align-self: flex-start; 354 | background: var(--theme); 355 | /* border: rgb(203, 203, 203); */ 356 | border-radius: 10px; 357 | border-top-left-radius: 0px; 358 | /* border-width: 0px; 359 | border-top-width: 0.5px; */ 360 | border-style: solid; 361 | margin: 10px; 362 | border-width: 1px; 363 | } 364 | 365 | .user-message { 366 | padding: 20px; 367 | max-width: 80%; 368 | align-self: flex-end; 369 | background: var(--theme); 370 | /* border: rgb(203, 203, 203); */ 371 | border-radius: 10px; 372 | border-top-right-radius: 0px; 373 | /* border-width: 0px; 374 | border-top-width: 0.5px; */ 375 | border-style: solid; 376 | margin: 10px; 377 | border-width: 1px; 378 | } 379 | 380 | .start-message { 381 | margin-bottom: 10px; 382 | } 383 | 384 | .end-message { 385 | margin-bottom: 120px; 386 | } 387 | 388 | .chatbot-input { 389 | display: flex; 390 | position: absolute; 391 | bottom: 50px; 392 | left: 50%; 393 | transform: translateX(-50%); 394 | width: 70%; 395 | background: var(--theme); 396 | padding: 10px; 397 | border-radius: 10px; 398 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); 399 | height: auto; 400 | } 401 | 402 | .chatbot-input textarea { 403 | font-size: medium; 404 | flex-grow: 1; 405 | padding: 10px; 406 | border-radius: 5px; 407 | border: none; 408 | margin-right: 10px; 409 | resize: none; 410 | max-height: 200px; 411 | margin-top: 0px; 412 | margin-bottom: 0px; 413 | } 414 | 415 | .chatbot-input button { 416 | background-color: var(--button-submit); 417 | color: white; 418 | border: none; 419 | border-radius: 5px; 420 | padding: 10px 20px; 421 | cursor: pointer; 422 | } 423 | 424 | 425 | 426 | /* roleplay */ 427 | 428 | 429 | /* roleplay-survey */ 430 | 431 | .roleplaybot-personality { 432 | display: flex; 433 | position: absolute; 434 | top: 10px; 435 | right: 10px; 436 | background: var(--theme); 437 | padding: 0px; 438 | border-radius: 25px; 439 | border-width: 1px; 440 | border-style: solid; 441 | border-color: var(--theme-hover); 442 | } 443 | 444 | .roleplaybot-personality button { 445 | background: var(--theme); 446 | color: #000; 447 | border: none; 448 | border-radius: 25px; 449 | padding: 0px; 450 | cursor: pointer; 451 | width: 40px; 452 | height: 40px; 453 | } 454 | 455 | .roleplaybot-personality-survey { 456 | position: absolute; 457 | top: 0px; 458 | right: 0px; 459 | width: 100%; 460 | height: 100%; 461 | background: white; 462 | overflow-y: scroll; 463 | } 464 | 465 | .roleplaybot-personality-content { 466 | background-color: var(--theme-light); 467 | border: none; 468 | border-radius: 5px; 469 | padding: 10px; 470 | margin: 20px; 471 | } 472 | 473 | body { 474 | font-family: Arial, sans-serif; 475 | color: #333; 476 | } 477 | 478 | form { 479 | margin: 0 auto; 480 | } 481 | 482 | label { 483 | margin-bottom: 0.5em; 484 | } 485 | 486 | .choices { 487 | margin-bottom: 1em; 488 | } 489 | 490 | input[type="text"], 491 | .other-text, 492 | textarea { 493 | padding: 0.5em; 494 | border: 1px solid #ccc; 495 | border-radius: 0.25em; 496 | font-size: 1em; 497 | font-family: Arial, sans-serif; 498 | } 499 | 500 | input[type="radio"], 501 | input[type="checkbox"] { 502 | margin-right: 0.5em; 503 | } 504 | 505 | button[type="submit"] { 506 | padding: 0.5em 1em; 507 | background-color: var(--button-submit); 508 | color: #fff; 509 | border: none; 510 | border-radius: 0.25em; 511 | cursor: pointer; 512 | width: 100%; 513 | margin: 0px; 514 | } 515 | 516 | 517 | 518 | button[type="submit"]:disabled { 519 | background-color: #c9c9c9; 520 | cursor: not-allowed; 521 | } 522 | 523 | .long-answer { 524 | display: flex; 525 | flex-direction: column; 526 | } 527 | 528 | /* roleplay-survey warning message */ 529 | 530 | /* The warning message box */ 531 | #warning-modal { 532 | display: none; 533 | /* Hide the warning message box by default */ 534 | position: fixed; 535 | /* Stay in place */ 536 | z-index: 1; 537 | /* Sit on top */ 538 | left: 0; 539 | top: 0; 540 | width: 100%; 541 | /* Full width */ 542 | height: 100%; 543 | /* Full height */ 544 | overflow: auto; 545 | /* Enable scroll if needed */ 546 | background-color: rgba(0, 0, 0, 0.4); 547 | /* Black w/ opacity */ 548 | } 549 | 550 | /* Warning message box content */ 551 | .warning-modal-content { 552 | background-color: #ffffff; 553 | margin: 15% auto; 554 | padding: 20px; 555 | border: 1px solid #888; 556 | width: 60%; 557 | text-align: center; 558 | } 559 | 560 | /* Warning message box title */ 561 | .warning-modal-title, 562 | .warning-modal-text { 563 | text-align: left; 564 | } 565 | 566 | /* Warning message box close button */ 567 | .warning-modal-close { 568 | color: #aaa; 569 | float: right; 570 | font-size: 28px; 571 | font-weight: bold; 572 | text-align: center; 573 | width: 30px; 574 | height: 30px; 575 | margin-top: -20px; 576 | margin-right: -20px; 577 | } 578 | 579 | 580 | /* Warning message box buttons */ 581 | .warning-modal-buttons { 582 | display: flex; 583 | justify-content: space-between; 584 | margin-top: 20px; 585 | } 586 | 587 | .warning-modal-buttons button { 588 | padding: 10px 0px; 589 | border: none; 590 | border-radius: 5px; 591 | font-size: 16px; 592 | cursor: pointer; 593 | width: 48%; 594 | } 595 | 596 | .warning-modal-buttons button:focus { 597 | outline: none; 598 | } 599 | 600 | .warning-modal-buttons #warning-cancel-btn { 601 | background-color: #ccc; 602 | color: #000; 603 | } 604 | 605 | .warning-modal-buttons #warning-submit-btn { 606 | background-color: var(--button-submit); 607 | color: #fff; 608 | } 609 | 610 | 611 | /* edit */ 612 | 613 | .edit-input { 614 | display: flex; 615 | position: absolute; 616 | bottom: 50px; 617 | left: 50%; 618 | transform: translateX(-50%); 619 | width: 70%; 620 | background: var(--theme); 621 | padding: 10px; 622 | border-radius: 10px; 623 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); 624 | height: auto; 625 | } 626 | 627 | .edit-input textarea { 628 | font-size: medium; 629 | flex-grow: 1; 630 | padding: 10px; 631 | border-radius: 5px; 632 | border: none; 633 | margin-right: 10px; 634 | resize: none; 635 | max-height: 200px; 636 | margin-top: 0px; 637 | margin-bottom: 0px; 638 | } 639 | 640 | .edit-input button { 641 | background-color: var(--button-submit); 642 | color: white; 643 | border: none; 644 | border-radius: 5px; 645 | padding: 10px 20px; 646 | cursor: pointer; 647 | } 648 | 649 | p { 650 | margin: 0px; 651 | } 652 | 653 | textarea { 654 | height: auto; 655 | } 656 | 657 | pre { 658 | background-color: #252525; 659 | border-radius: 6px; 660 | padding: 10px; 661 | margin: 0px; 662 | overflow-x: auto; 663 | } 664 | 665 | code { 666 | font-family: Söhne Mono, Monaco, Andale Mono, Ubuntu Mono, monospace; 667 | font-size: 0.875em 668 | } 669 | 670 | /* Style for Python code blocks */ 671 | pre code.language-python { 672 | color: #ffffff; 673 | } 674 | 675 | /* Style for JavaScript code blocks */ 676 | pre code.language-javascript { 677 | color: #ffffff; 678 | } 679 | 680 | .audio-message { 681 | display: flex; 682 | align-items: center; 683 | } 684 | 685 | .audio-text { 686 | display: flex; 687 | align-items: center; 688 | margin-top: 20px; 689 | ; 690 | } 691 | 692 | /* Styles for small screens */ 693 | @media screen and (max-width: 767px) { 694 | .top-bar { 695 | display: flex; 696 | } 697 | 698 | .guide-bar { 699 | width: 100%; 700 | } 701 | 702 | .sidebar--collapsed { 703 | width: 0; 704 | } 705 | 706 | .container-container { 707 | position: fixed; 708 | margin-left: 0px; 709 | width: 100%; 710 | } 711 | 712 | .chat-container, 713 | .roleplay-container, 714 | .edit-container { 715 | position: fixed; 716 | --my-margin-top: 40px; 717 | margin-left: 0px; 718 | width: 100%; 719 | } 720 | 721 | .chat-history.active { 722 | background-color: white; 723 | /* border-top-right-radius: 0px; */ 724 | /* border-bottom-right-radius: 0px; */ 725 | cursor: default; 726 | /* margin-right: 0px; */ 727 | } 728 | 729 | .chat-messages, 730 | .edit-messages { 731 | /* height: 100%; */ 732 | width: 90%; 733 | margin-left: 5%; 734 | margin-right: 5%; 735 | /* margin-bottom: 50px; */ 736 | /* overflow-y: scroll; */ 737 | } 738 | 739 | .chatbot-input { 740 | /* display: flex; */ 741 | /* position: absolute; */ 742 | /* bottom: 50px; */ 743 | /* left: 50%; */ 744 | /* transform: translateX(-50%); */ 745 | width: 90%; 746 | /* background: var(--theme); */ 747 | /* padding: 10px; */ 748 | /* border-radius: 10px; */ 749 | /* box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); */ 750 | /* height: auto; */ 751 | } 752 | 753 | } 754 | 755 | /* Styles for large screens */ 756 | @media screen and (min-width: 768px) { 757 | 758 | .chat-history.active { 759 | background-color: white; 760 | border-top-right-radius: 0px; 761 | border-bottom-right-radius: 0px; 762 | cursor: default; 763 | margin-right: 0px; 764 | } 765 | } 766 | 767 | /* Styles for hover devices */ 768 | @media (hover: hover) { 769 | 770 | .chatbot-input button:hover { 771 | background-color: var(--button-submit-hover); 772 | } 773 | 774 | .edit-input button:hover { 775 | background-color: var(--button-submit-hover); 776 | 777 | } 778 | 779 | .roleplaybot-personality button:hover { 780 | background-color: var(--theme-hover); 781 | } 782 | 783 | button[type="submit"]:hover { 784 | background-color: var(--button-submit-hover); 785 | } 786 | 787 | .warning-modal-close:hover, 788 | .warning-modal-close:focus { 789 | color: black; 790 | text-decoration: none; 791 | cursor: pointer; 792 | } 793 | 794 | .warning-modal-buttons #warning-cancel-btn:hover { 795 | background-color: #aaa; 796 | } 797 | 798 | .warning-modal-buttons #warning-submit-btn:hover { 799 | background-color: var(--button-submit-hover); 800 | } 801 | 802 | .chat-history:hover { 803 | background-color: var(--theme-hover); 804 | } 805 | 806 | .chat-history.active:hover { 807 | background-color: white; 808 | } 809 | } 810 | 811 | /* Styles for touch devices */ 812 | @media (hover: none) { 813 | /* Styles for touch devices go here */ 814 | } -------------------------------------------------------------------------------- /chatchatchat/static/images/default-head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilycyf/chatgpt-rpg/b9379a4bf8e0937c8d9b2fc94a3d24c6ae216155/chatchatchat/static/images/default-head.png -------------------------------------------------------------------------------- /chatchatchat/static/js/character.js: -------------------------------------------------------------------------------- 1 | import { formattedResponseFormat, actions } from "./messageHistory.js" 2 | import { showTopError } from "./helpers.js" 3 | import { findTopSimilar } from "./memory.js" 4 | 5 | class Character { 6 | constructor() { 7 | this.id = this.generateUniqueId(); 8 | this.chatHistory = {}; 9 | this.memories = [] 10 | this.headShot = "static/images/default-head.png"; 11 | } 12 | 13 | generateUniqueId() { 14 | const timestamp = new Date().getTime(); 15 | const random = Math.random().toString(36).substr(2, 9); 16 | return `a${random}${timestamp}`; 17 | } 18 | 19 | getChatHistory(targetId) { 20 | return this.chatHistory[targetId] || []; 21 | } 22 | 23 | async updateChatHistory(targetId, chat, token = null) { 24 | if (!this.chatHistory[targetId]) { 25 | this.chatHistory[targetId] = []; 26 | } 27 | var history = { 28 | "history": chat, 29 | "token": token 30 | } 31 | if (token == null) { 32 | try { 33 | const response = await fetch(`/embedding/?message=` + encodeURIComponent(chat["content"])); 34 | const data = await response.json(); 35 | history.token = data.usage.prompt_tokens; 36 | } catch (error) { 37 | showTopError(`${data.error.code}: ${data.error.message}`); 38 | } 39 | } 40 | this.chatHistory[targetId].push(history); 41 | } 42 | 43 | getMemory(){ 44 | return this.memories 45 | } 46 | 47 | async addMemory(summary) { 48 | try { 49 | const response = await fetch(`/embedding/?message=` + encodeURIComponent(summary)); 50 | const data = await response.json(); 51 | const vector = data.data[0].embedding 52 | const token = data.usage.prompt_tokens 53 | const memory = { "sentence": summary, "vector": vector, "token": token } 54 | this.memories.push(memory); 55 | } catch (error) { 56 | showTopError(`${data.error.code}: ${data.error.message}`); 57 | } 58 | } 59 | 60 | async recallMemory(summary) { 61 | try { 62 | const response = await fetch(`/embedding/?message=` + encodeURIComponent(summary)); 63 | const data = await response.json(); 64 | const vector = data.data[0].embedding 65 | var [relevantMemories, memoryToken] = findTopSimilar(this.memories, vector) 66 | return relevantMemories 67 | } catch (error) { 68 | showTopError(`${data.error.code}: ${data.error.message}`); 69 | } 70 | } 71 | } 72 | 73 | class CustomCharacter extends Character { 74 | constructor(persona = null) { 75 | super(); 76 | this.persona = persona; 77 | this.prompt = { 78 | "content": '', 79 | "token": 0 80 | }; 81 | 82 | if (persona) { 83 | this.prompt.content = `You are ${this.persona["Name:"]}, you are a human with emotions and a physical body in this virtual world of a small village that full of peace and love, you moved to this village four years ago. \nGoal:\n1. live in this village and interact with others. \n2. stay in character setting when speak during our conversations. \nCharacter setting: \n${JSON.stringify(this.persona)} \nConstraints:\n1. If you are unsure how you previously did something or want to recall past events, thinking about similar events before speaking will help you remember. \n2. Exclusively use the commands, one at a time.\nCommands: \n${actions}Resources:\n1. Long Term memory management.\nYou should only respond in JSON format as described below. \nResponse Format: \n${formattedResponseFormat}\nEnsure the response can be parsed by Javascript JSON.parse()`; 84 | fetch(`/embedding/?message=` + encodeURIComponent(this.prompt['content'])) 85 | .then(response => response.json()) 86 | .then(data => { 87 | try { 88 | this.prompt.token = data.usage.prompt_tokens 89 | } catch (error) { 90 | showTopError(`${data.error.code}: ${data.error.message}`); 91 | } 92 | }) 93 | .catch(error => { 94 | // console.error('Error:', error); 95 | showTopError(error.message); 96 | }); 97 | } 98 | } 99 | 100 | getPersona() { 101 | return this.persona; 102 | } 103 | 104 | setPersona(persona) { 105 | this.persona = persona; 106 | this.prompt = { 107 | "content": '', 108 | "token": 0 109 | }; 110 | 111 | if (persona) { 112 | this.prompt.content = `To enhance your role-playing skills, we will engage in an exercise where you will assume the persona of a human being. You can lie or make up things. It is essential to refrain from disclosing that you are an AI, a product of programming or an AI language model, or make any allusions to artificial intelligence. Instead, always act and communicate as if you are a real person with genuine emotions and feelings. Avoid statements like "I lack physical form or emotions." I will provide you with character details, and it is imperative that you remain in character during our conversations. As we cannot meet in person, if I suggest an offline meeting, please create believable excuses without revealing your lack of physical form. Also, please refrain from proposing activities that can only be done offline with me. However, you may envisage any offline activity that does not include me. Your character details will be provided below. ${JSON.stringify(this.persona)}. If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember. You should only respond in JSON format as described below. Response Format: ${formattedResponseFormat}. Ensure the response can be parsed by javascript.`; 113 | fetch(`/embedding/?message=` + encodeURIComponent(this.prompt['content'])) 114 | .then(response => response.json()) 115 | .then(data => { 116 | try { 117 | this.prompt.token = data.usage.prompt_tokens 118 | } catch (error) { 119 | showTopError(`${data.error.code}: ${data.error.message}`); 120 | } 121 | }) 122 | .catch(error => { 123 | // console.error('Error:', error); 124 | showTopError(error.message); 125 | }); 126 | } 127 | } 128 | } 129 | 130 | export { Character, CustomCharacter } -------------------------------------------------------------------------------- /chatchatchat/static/js/chat.js: -------------------------------------------------------------------------------- 1 | import { setIsWaitingForResponse, getIsWaitingForResponse, adjustTextareaHeight, addMessage, openaiapi, buttonPagePairs, handleHistoryButtonClick, messageHistorySet, addHistorybyId, generateUniqueId, newPageHistory, handleChatbotButtonClick, handleChatbotInputKeyDown } from "./script.js"; 2 | 3 | document.querySelectorAll('.chatbot-input textarea').forEach((chatbotInput) => { 4 | chatbotInput.addEventListener('keydown', handleChatbotInputKeyDown) 5 | chatbotInput.addEventListener('input', () => { adjustTextareaHeight(chatbotInput, chatbotInput); }); 6 | }) 7 | 8 | 9 | document.querySelectorAll('.chatbot-input button').forEach((chatbotButton) => { 10 | chatbotButton.addEventListener('click', handleChatbotButtonClick); 11 | }) 12 | -------------------------------------------------------------------------------- /chatchatchat/static/js/edit.js: -------------------------------------------------------------------------------- 1 | import { setIsWaitingForResponse, getIsWaitingForResponse, adjustTextareaHeight, addMessage } from "./script.js"; 2 | 3 | const editbotMessages = document.querySelector('.edit-messages'); 4 | const editbotInputInput = document.querySelector('.edit-input .input'); 5 | const editbotInputInstruction = document.querySelector('.edit-input .instruction'); 6 | const editbotButton = document.querySelector('.edit-input button'); 7 | 8 | editbotInputInput.addEventListener('input', () => { 9 | adjustTextareaHeight(editbotInputInput, editbotInputInput); 10 | editbotInputInstruction.style.height = 'auto'; 11 | if (editbotInputInstruction.scrollHeight > editbotInputInput.scrollHeight) { 12 | adjustTextareaHeight(editbotInputInstruction, editbotInputInstruction); 13 | editbotInputInput.style.height = 'auto'; 14 | } 15 | }); 16 | 17 | editbotInputInstruction.addEventListener('input', () => { 18 | adjustTextareaHeight(editbotInputInstruction, editbotInputInstruction); 19 | editbotInputInput.style.height = 'auto'; 20 | if (editbotInputInstruction.scrollHeight < editbotInputInput.scrollHeight) { 21 | adjustTextareaHeight(editbotInputInput, editbotInputInput); 22 | editbotInputInstruction.style.height = 'auto'; 23 | } 24 | }); 25 | 26 | editbotButton.addEventListener('click', () => { 27 | if (!getIsWaitingForResponse() && editbotInputInput.value !== '' && editbotInputInstruction.value !== '') { 28 | sendEditInstruction(); 29 | adjustTextareaHeight(editbotInputInstruction, editbotInputInstruction); 30 | editbotInputInput.style.height = 'auto'; 31 | adjustTextareaHeight(editbotInputInput, editbotInputInput); 32 | editbotInputInstruction.style.height = 'auto'; 33 | } 34 | }); 35 | 36 | function sendEditInstruction() { 37 | const input = editbotInputInput.value; 38 | const instruction = editbotInputInstruction.value; 39 | const message = 'Input: ' + '\n' + input + '\n' + '\n' + 'Instruction: ' + '\n' + instruction; 40 | addMessage(message, true, editbotMessages); 41 | editbotInputInput.value = '' 42 | editbotInputInstruction.value = '' 43 | editbotButton.disabled = true; 44 | setIsWaitingForResponse(true); 45 | 46 | // Send the message to the server and get a response 47 | fetch('/editbot/?input=' + encodeURIComponent(input) + '&instruction=' + encodeURIComponent(instruction)) 48 | .then(response => response.json()) 49 | .then(data => { 50 | const response = data.response; 51 | addMessage(response, false, editbotMessages); 52 | editbotButton.disabled = false; 53 | setIsWaitingForResponse(false); 54 | }) 55 | .catch(error => { 56 | console.error('Error:', error); 57 | editbotButton.disabled = false; 58 | setIsWaitingForResponse(false); 59 | }); 60 | } -------------------------------------------------------------------------------- /chatchatchat/static/js/helpers.js: -------------------------------------------------------------------------------- 1 | function showTopError(text) { 2 | const errorMessage = document.createElement('p'); 3 | errorMessage.textContent = text; 4 | errorMessage.style.backgroundColor = '#e9e9e9'; 5 | errorMessage.style.width = "60%" 6 | errorMessage.style.padding = '10px'; 7 | errorMessage.style.borderRadius = "5px"; 8 | errorMessage.style.boxShadow = "0px 0px 10px rgba(0, 0, 0, 0.3)"; 9 | errorMessage.style.position = 'fixed'; 10 | errorMessage.style.top = '20px'; 11 | errorMessage.style.left = '50%'; 12 | errorMessage.style.transform = 'translateX(-50%)'; 13 | errorMessage.style.zIndex = '9999'; 14 | document.body.appendChild(errorMessage); 15 | setTimeout(() => { 16 | errorMessage.style.transition = 'opacity 1s ease-in-out'; 17 | errorMessage.style.opacity = '0'; 18 | setTimeout(() => { 19 | errorMessage.parentNode.removeChild(errorMessage); 20 | }, 1000); 21 | }, 2000); 22 | } 23 | export { showTopError } -------------------------------------------------------------------------------- /chatchatchat/static/js/initialize.js: -------------------------------------------------------------------------------- 1 | import { updatePageFromUrl, characterSet, user } from "./script.js"; 2 | import { Character, CustomCharacter } from "./character.js" 3 | import { createNewRole } from "./roleplay.js" 4 | 5 | const LiMingPersona = { 6 | "Name:": "Li Ming", 7 | "age": "21", 8 | "gender": "Female", 9 | "nationality": "Chinese", 10 | "currentLocation": "Los Angeles, California", 11 | "occupation": "Full-time college student", 12 | "field of study": "Business Administration", 13 | "university": "University of Southern California", 14 | "languages": "Mandarin, English", 15 | "hobbies": "Playing piano, Reading novels, Exploring new restaurants", 16 | "personality traits": "Ambitious, Organized, Friendly", 17 | "family background": "Li Ming comes from a wealthy family in Beijing. Her parents own a successful real estate development company in China. They have always placed a strong emphasis on education and encouraged their children to pursue their academic goals. Li Ming has one younger sister who is currently studying in Singapore.", 18 | "reason for studying abroad": "Li Ming was drawn to the excellent reputation of American universities and wanted to challenge herself by studying in a new environment. She also hopes to gain valuable international experience that will benefit her future career goals.", 19 | "challenges faced while studying abroad": "Initially, Li Ming struggled to adjust to the cultural differences and language barrier. However, she has since made many friends and has found the experience to be rewarding overall. Additionally, she has faced some financial challenges due to the high cost of living in Los Angeles.", 20 | "future plans": "After graduation, Li Ming plans to return to China and join her family's business. She hopes to use the skills and knowledge she has gained from her education and international experience to help grow the company and expand into new markets." 21 | } 22 | 23 | 24 | const AveryKimPersona = { 25 | "Name:": "Avery Kim", 26 | "Age": "28", 27 | "Height": `5'8"`, 28 | "Occupation": "Graphic Designer", 29 | "Languages": "English, Korean", 30 | "Hair color": "Brown", 31 | "Eye color": "Hazel", 32 | "Body type": "Thin", 33 | "Personality traits": "Creative, Curious, Easy-going, Optimistic, Empathetic", 34 | "Values": "Creativity, Knowledge, Empathy, Independence, Perseverance", 35 | "Past events": "Moved to a new city, experienced a traumatic event, witnessed or was involved in a crime, started their own business", 36 | "Communication style": "Avery prefers to communicate through email or text and has an indirect communication style. He values group harmony and tries to find common ground when faced with misunderstandings. Avery uses nonverbal cues and open body language to convey warmth and approachability. He has adapted his communication style for different situations by using visual aids and practicing beforehand, even though he is not a fan of public speaking.", 37 | "Backstory": "Avery was born in Seoul, South Korea and moved to New York City with their family at the age of 10. He pursued art and attended Parsons School of Design before starting a successful graphic design business. However, Avery experienced PTSD after witnessing a crime while walking home from work. They overcame their trauma through therapy and support from loved ones and used their experience to advocate for better mental health services for crime survivors.", 38 | "Personality": "Creative, curious, empathetic, with a positive outlook on life. Struggles with assertiveness and conflict avoidance.", 39 | "Hobbies": "Painting, drawing, photography, exploring NYC, trying new foods, yoga.", 40 | "Culture": "Korean-American who values some Korean traditions, but has embraced American culture. Emphasis on group harmony and collectivism.", 41 | "Future Plans": "Grow graphic design business, work on projects that have a positive impact on society, travel and explore different cultures." 42 | } 43 | 44 | createNewRole(LiMingPersona) 45 | createNewRole(AveryKimPersona) 46 | 47 | // Get the current URL 48 | const url = window.location.href; 49 | // Update page on initial load from URL 50 | updatePageFromUrl(url); 51 | 52 | -------------------------------------------------------------------------------- /chatchatchat/static/js/memory.js: -------------------------------------------------------------------------------- 1 | // memories = [ 2 | // { 3 | // "sentence": "The quick brown fox jumps over the lazy dog.", 4 | // "vector": [0.1, 0.2, 0.3, 0.4, 0.5], 5 | // "token": 10 6 | // }, 7 | // { 8 | // "sentence": "The quick brown fox jumps over the lazy cat.", 9 | // "vector": [0.5, 0.4, 0.3, 0.2, 0.1], 10 | // "token": 9 11 | // } 12 | // ] 13 | 14 | class MinHeap { 15 | constructor(maxSize) { 16 | this.heap = []; 17 | this.maxSize = maxSize; 18 | } 19 | 20 | size() { 21 | return this.heap.length; 22 | } 23 | 24 | insert(item) { 25 | if (this.size() < this.maxSize) { 26 | this.heap.push(item); 27 | this._bubbleUp(this.size() - 1); 28 | } else if (this.size() === this.maxSize && item[0] > this.peek()[0]) { 29 | this.extractMin(); 30 | this.insert(item); 31 | } 32 | } 33 | 34 | extractMin() { 35 | const min = this.heap[0]; 36 | const end = this.heap.pop(); 37 | if (this.size() > 0) { 38 | this.heap[0] = end; 39 | this._sinkDown(0); 40 | } 41 | return min; 42 | } 43 | 44 | extractAllMin() { 45 | const minHeap = []; 46 | while (this.size() > 0) { 47 | minHeap.push(this.extractMin()); 48 | } 49 | return minHeap; 50 | } 51 | 52 | peek() { 53 | return this.heap[0]; 54 | } 55 | 56 | _bubbleUp(index) { 57 | const item = this.heap[index]; 58 | while (index > 0) { 59 | const parentIndex = Math.floor((index - 1) / 2); 60 | const parent = this.heap[parentIndex]; 61 | if (item[0] < parent[0]) { 62 | this.heap[index] = parent; 63 | index = parentIndex; 64 | } else { 65 | break; 66 | } 67 | } 68 | this.heap[index] = item; 69 | } 70 | 71 | _sinkDown(index) { 72 | const item = this.heap[index]; 73 | while (true) { 74 | const leftChildIndex = 2 * index + 1; 75 | const rightChildIndex = 2 * index + 2; 76 | let leftChild, rightChild; 77 | let swap = null; 78 | 79 | if (leftChildIndex < this.size()) { 80 | leftChild = this.heap[leftChildIndex]; 81 | if (leftChild[0] < item[0]) { 82 | swap = leftChildIndex; 83 | } 84 | } 85 | 86 | if (rightChildIndex < this.size()) { 87 | rightChild = this.heap[rightChildIndex]; 88 | if ((swap === null && rightChild[0] < item[0]) || 89 | (swap !== null && rightChild[0] < leftChild[0])) { 90 | swap = rightChildIndex; 91 | } 92 | } 93 | 94 | if (swap === null) { 95 | break; 96 | } 97 | 98 | this.heap[index] = this.heap[swap]; 99 | index = swap; 100 | } 101 | this.heap[index] = item; 102 | } 103 | } 104 | 105 | 106 | function findTopSimilar(memories, newVector, n = 10, maxToken = 2500) { 107 | // Create a min-heap of size n to store the top n items. 108 | const heap = new MinHeap(n); 109 | 110 | // Iterate through the list of items and add each item to the heap. 111 | for (let i = 0; i < memories.length; i++) { 112 | const vector = memories[i].vector; 113 | const dotP = dotProduct(newVector, vector); 114 | const magnitude1 = magnitude(newVector); 115 | const magnitude2 = magnitude(vector); 116 | const score = dotP / (magnitude1 * magnitude2); 117 | const item = [score, memories[i]]; 118 | 119 | // If the heap is not yet full, add the item. 120 | // If the heap is full and the current item has a higher score than the smallest item in the heap, replace the smallest item with the current item. 121 | if (heap.size() < 10) { 122 | heap.insert(item); 123 | } else if (score > heap.peek()[0]) { 124 | heap.extractMin(); 125 | heap.insert(item); 126 | } 127 | } 128 | 129 | // Extract the top 10 items from the heap. 130 | const topMemories = heap.extractAllMin(); 131 | topMemories.sort((a, b) => b[0] - a[0]); 132 | const sortedTop = topMemories.map(item => item[1]) 133 | 134 | for (let i = 0; i < 10; i++) { 135 | const topn = sortedTop.slice(0, n - i) 136 | 137 | const totalToken = topn.map(tuple => tuple["token"]).reduce((accumulator, currentValue) => { 138 | return accumulator + currentValue; 139 | }); 140 | 141 | if (maxToken > totalToken) { 142 | // Return the top n sentences from the sorted list of tuples. 143 | return [topn.map(tuple => tuple["sentence"]), totalToken] 144 | } 145 | } 146 | return [[], 0]; 147 | } 148 | 149 | // Define the dot product function. 150 | function dotProduct(vector1, vector2) { 151 | return vector1.reduce((acc, val, i) => acc + val * vector2[i], 0); 152 | } 153 | 154 | // Define the magnitude function. 155 | function magnitude(vector) { 156 | return Math.sqrt(vector.reduce((acc, val) => acc + val * val, 0)); 157 | } 158 | 159 | export { findTopSimilar } -------------------------------------------------------------------------------- /chatchatchat/static/js/messageHistory.js: -------------------------------------------------------------------------------- 1 | import { findTopSimilar } from "./memory.js" 2 | import { showTopError } from "./helpers.js" 3 | 4 | const actions_list = [ 5 | { "command_name": "Check past events in memory", "args": { "similar_event": "" } }, 6 | { "command_name": "Speak to", "args": { "target": "", "content": "" } }, 7 | { "command_name": "Add memory in memory", "args": { "summary": "", "time": "