├── .gitignore ├── .gitpod.yml ├── .vscode ├── extensions.json └── tasks.json ├── LICENSE ├── README.md ├── apps ├── backend │ ├── package.json │ └── server.js └── frontend │ ├── .env.example │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── favicon.png │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.animations.css │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── components │ │ ├── AboutInfoModal.tsx │ │ ├── CommandStringBar.tsx │ │ ├── EditAIProfileModal.tsx │ │ ├── ListOfAIProfiles.tsx │ │ ├── OutputSegmentsList.tsx │ │ ├── SettingsDrawer.tsx │ │ ├── SettingsDrawerContent.tsx │ │ ├── StartNewProcessMenu.tsx │ │ ├── TheAreaAtTheBottom.tsx │ │ ├── TheAreaAtTheTop.tsx │ │ ├── TheAreaInTheMiddle.tsx │ │ ├── TheHiddenServiceRunner.tsx │ │ └── smol │ │ │ └── ColorModeSwitcher.tsx │ ├── config │ │ ├── BackendConfigurationKeys.ts │ │ ├── BackendUrl.ts │ │ ├── SHELL_COMMANDS.ts │ │ └── theme.ts │ ├── data │ │ └── example-profiles.yaml │ ├── entities │ │ ├── AIProfile.d.ts │ │ └── BackendServiceState.d.ts │ ├── hooks │ │ ├── useApiService.ts │ │ ├── useAutoGPTStarter.ts │ │ ├── useRemoteConsoleOutput.ts │ │ ├── useToastShortcuts.ts │ │ ├── useUpdateLocalStorage.ts │ │ └── useWebSocketConnection.ts │ ├── main.tsx │ ├── services │ │ └── APIService.ts │ ├── store │ │ ├── useContextStore.ts │ │ └── useSettingsStore.ts │ ├── utils │ │ ├── createSimpleZustandStore.ts │ │ ├── createStoreWrappedWithProxy.ts │ │ ├── generateSimpleUniqueId.ts │ │ ├── getPseudoRandomColorFromString.ts │ │ └── getRandomProfileDataFiller.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package-lock.json ├── package.json ├── scripts ├── mock-continuous.sh ├── mock-spinner.sh ├── mock-user-input.sh └── setup-auto-gpt.sh └── turbo.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .pnpm-debug.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # turbo 33 | .turbo 34 | 35 | # auto-gpt-webui 36 | .ignore 37 | /auto-gpt* 38 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # Commands to start on workspace startup 2 | tasks: 3 | - name: Initialize & Start Development Server 4 | init: npm run setup-auto-gpt && npm install 5 | before: pip install -r auto-gpt/requirements.txt 6 | command: npm run dev 7 | 8 | # Ports to expose on workspace startup 9 | ports: 10 | - port: 7070 11 | onOpen: open-browser 12 | name: Frontend 13 | description: Frontend 14 | - port: 2200 15 | name: Backend 16 | description: Backend 17 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "actboy168.tasks", 4 | "esbenp.prettier-vscode" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "dev", 7 | "label": "🔰", 8 | "detail": "turbo run dev", 9 | "presentation": { 10 | "echo": true, 11 | "reveal": "always", 12 | "focus": false, 13 | "panel": "shared", 14 | "showReuseMessage": true, 15 | "clear": true 16 | } 17 | }, 18 | { 19 | "type": "shell", 20 | "label": "💀", 21 | "command": "npx --yes kill-port 2200 ; npx --yes kill-port 3000", 22 | "presentation": { 23 | "echo": true, 24 | "reveal": "always", 25 | "focus": false, 26 | "panel": "shared", 27 | "showReuseMessage": true, 28 | "clear": true 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Toran Bruce Richards 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 | # Auto-GPT WebUI 2 | 3 | This project is a frontend web application that runs and interacts with [Auto-GPT](https://github.com/Torantulino/Auto-GPT). The backend application provides the core logic and functionality, while this frontend application wraps over it and offers a user-friendly interface. 4 | 5 | ### 🌟 Special thanks to [the original Auto-GPT project](https://github.com/Torantulino/Auto-GPT) and its creator, [Torantulino](https://github.com/Torantulino), for his hard work and for making this project possible! 🌟 6 | 7 | --- 8 | 9 | ## Disclaimer: Limited Availability for Maintenance 10 | 11 | Please note that I developed this project primarily for personal amusement and as a learning experience. 12 | My availability to address issues, review pull requests, or implement new features may be limited, as I have other full-time commitments. 13 | 14 | If you find this project useful and would like to contribute, I welcome and highly appreciate community involvement. 💛 15 | 16 | Thank you for your understanding, and I hope you find this helpful! 17 | 18 | --- 19 | 20 | ## 🛠️ Installation & Usage 21 | 22 | Clone the repo, then enter the root directory and run the folling commands: 23 | 24 | ```bash 25 | npm install 26 | npm run setup-auto-gpt 27 | ``` 28 | 29 | - The first command will install the required dependencies. 30 | - The second command will clone the Auto-GPT repository and install its dependencies. 31 | 32 | Run the following command in order to start both the frontend application and the Node.js server, which will in turn start and stop the python script, and inform the frontend of the script's output: 33 | 34 | ```bash 35 | npm start 36 | ``` 37 | 38 | When you first open up the web app, you will see a few alerts about missing API Keys. You need to fill these in in order for the application to work correctly. Look below for instructions. 39 | 40 | --- 41 | 42 | ## ⚙ Requirements and Configuration 43 | 44 | ### OpenAI API key 45 | 46 | Follow these steps to obtain an OpenAI API key: 47 | 48 | 1. Visit the [**OpenAI Platform**](https://platform.openai.com/signup) website. 49 | 1. If you don't have an account yet, sign up by providing your email, name, and creating a password. 50 | 1. After signing up or logging in, go to the [**API Keys**](https://platform.openai.com/account/api-keys) section in your account. 51 | 1. Click the **Create an API key** button to generate a new API key. 52 | 1. Copy the API key and paste it in the WebUI to use it with Auto-GPT. 53 | 54 | - ⚠️ Keep your API key secure and never share it with anyone. Treat it like a password. 55 | 56 | ### Pinecone API key 57 | 58 | Follow these steps to obtain a Pinecone API key: 59 | 60 | 1. Visit the [**Pinecone**](https://app.pinecone.io/register) website. 61 | 1. If you don't have an account yet, sign up by providing your email and creating a password. 62 | 1. After signing up or logging in, go to the [**Projects**](https://app.pinecone.io/projects) page. 63 | 1. Click on the default project, or create a new one if desired. 64 | 1. In the left sidebar, click on the **API Keys** section. 65 | 1. Create a new API key and copy-paste it in the WebUI to use it with Auto-GPT. 66 | 67 | - ⚠️ Keep your API key secure and never share it with anyone. Treat it like a password. 68 | 69 | ### Python environment 70 | 71 | You will also need both [Python 3.8 or later](https://www.tutorialspoint.com/how-to-install-python-in-windows) and [Node.js](https://nodejs.org/en) installed on your system. 72 | 73 | --- 74 | 75 | ## 🛠 Tools and Libraries 76 | 77 | - [**Auto-GPT**](https://github.com/Torantulino/Auto-GPT) - As mentioned, the backend logic and the meat of this application is provided by the Auto-GPT project. Check out the repo for more information if you'd like to [contribute to](https://github.com/Torantulino/Auto-GPT/blob/master/CONTRIBUTING.md) or [sponsor](https://github.com/sponsors/Torantulino) the project. 78 | - [**Turborepo**](https://turborepo.org/) - A high-performance build system and repository organizer for JavaScript and TypeScript codebases. 79 | - [**Vite**](https://vitejs.dev/) - A build tool and development server for web applications, designed to be fast and lightweight. 80 | - [**React**](https://reactjs.org/) - A JavaScript library for building user interfaces, developed and maintained by Facebook. 81 | - [**Chakra UI**](https://chakra-ui.com/) - A simple, modular, and accessible component library for React. 82 | - [**Zustand**](https://github.com/pmndrs/zustand) - A lightweight and easy-to-use state management library for React. 83 | - [**Express**](https://expressjs.com/) - A fast, unopinionated, minimalist web framework for Node.js. 84 | - [**dotenv**](https://www.npmjs.com/package/dotenv) - A library that loads environment variables from a `.env` file into your Node.js application. 85 | - [**Emotion**](https://emotion.sh/docs/introduction) - A library for CSS-in-JS, allowing you to style your React components using JavaScript. 86 | - [**TypeScript**](https://www.typescriptlang.org/) - A popular superset of JavaScript that adds static types and other features for better code maintainability. 87 | - [**ESLint**](https://eslint.org/) - A tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. 88 | - [**Prettier**](https://prettier.io/) - A widely-used code formatter that enforces a consistent style by parsing your code and re-printing it with its own rules. 89 | -------------------------------------------------------------------------------- /apps/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon server.js", 8 | "start": "node server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "child_process": "^1.0.2", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.0.3", 17 | "express": "^4.18.2", 18 | "install": "^0.13.0", 19 | "js-yaml": "^4.1.0", 20 | "nodemon": "^3.1.0", 21 | "npm": "^9.6.4", 22 | "tree-kill": "^1.2.2", 23 | "ws": "^8.13.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/backend/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const { exec } = require('child_process'); 4 | const WebSocket = require('ws'); 5 | 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const dotenv = require('dotenv'); 9 | const yaml = require('js-yaml'); 10 | const kill = require('tree-kill'); 11 | 12 | const app = express(); 13 | app.use(cors()); 14 | 15 | const PORT = process.env.PORT || 2200; 16 | const RELATIVE_PATH_TO_AUTOGPT = '../../auto-gpt'; 17 | // const PATH_TO_AI_SETTINGS_FILE = fs.existsSync( 18 | // path.join(__dirname, RELATIVE_PATH_TO_AUTOGPT, 'ai_settings.yaml') 19 | // ) 20 | // ? 'ai_settings.yaml' 21 | // : 'last_run_ai_settings.yaml'; 22 | const PATH_TO_AI_SETTINGS_FILE = 'ai_settings.yaml'; 23 | 24 | const OBFUSCATE_ENV_VARS_BEFORE_SENDING_TO_CLIENT = true; 25 | 26 | const wss = new WebSocket.Server({ noServer: true }); 27 | 28 | //////////////// 29 | 30 | const state = { 31 | activeProcess: null, 32 | activeCommandString: null, 33 | consoleOutputLines: [], 34 | }; 35 | 36 | let configuration = loadConfiguration(); 37 | 38 | function obfuscateObjectProperties(object) { 39 | return Object.fromEntries( 40 | Object.entries(object).map(([key, value]) => [ 41 | key, 42 | value.replace(/./g, '*'), 43 | ]), 44 | ); 45 | } 46 | 47 | function loadConfiguration() { 48 | const envFilePath = path.join(__dirname, RELATIVE_PATH_TO_AUTOGPT, '.env'); 49 | if (fs.existsSync(envFilePath)) { 50 | const envFileContents = fs.readFileSync(envFilePath, 'utf8'); 51 | const parsedEnvFileContents = dotenv.parse(envFileContents); 52 | return parsedEnvFileContents; 53 | } 54 | return {}; 55 | } 56 | 57 | function loadConfiguration() { 58 | const envFilePath = path.join(__dirname, RELATIVE_PATH_TO_AUTOGPT, '.env'); 59 | if (fs.existsSync(envFilePath)) { 60 | const envFileContents = fs.readFileSync(envFilePath, 'utf8'); 61 | const parsedEnvFileContents = dotenv.parse(envFileContents); 62 | const result = OBFUSCATE_ENV_VARS_BEFORE_SENDING_TO_CLIENT 63 | ? obfuscateObjectProperties(parsedEnvFileContents) 64 | : parsedEnvFileContents; 65 | return result; 66 | } 67 | return {}; 68 | } 69 | 70 | //////////////// 71 | 72 | wss.on('connection', (socket) => { 73 | console.log(`Client connected, sending command log`); 74 | updateClients([socket]); 75 | }); 76 | 77 | function updateClients(sockets, latestChunk = null) { 78 | if (sockets.length === 0) { 79 | sockets = wss.clients; 80 | } 81 | 82 | if (sockets.length === 0) { 83 | console.warn('No clients connected, not sending anything.'); 84 | return; 85 | } 86 | 87 | const data = JSON.stringify({ 88 | fullConsoleOutput: state.consoleOutputLines.join('\n'), 89 | latestChunk, 90 | configuration, 91 | state: { 92 | activeProcessRunning: !!state.activeProcess, 93 | activeCommandString: state.activeCommandString, 94 | }, 95 | }); 96 | 97 | for (const client of sockets) { 98 | if (client.readyState === WebSocket.OPEN) { 99 | client.send(data); 100 | } 101 | } 102 | } 103 | 104 | const appendOutputChunkAndUpdateClients = (data) => { 105 | function deleteLastLineIfCarrotMovedToBeginning() { 106 | const lastSavedLine = 107 | state.consoleOutputLines[state.consoleOutputLines.length - 1]; 108 | if (!lastSavedLine?.endsWith('\r')) { 109 | return false; 110 | } 111 | 112 | state.consoleOutputLines.pop(); 113 | return true; 114 | } 115 | 116 | const lines = data.split('\n'); 117 | 118 | if (lines.length == 0) { 119 | return; 120 | } 121 | 122 | const [firstLine, ...restOfLines] = lines; 123 | 124 | const deletedLastLine = deleteLastLineIfCarrotMovedToBeginning(); 125 | if (deletedLastLine) { 126 | state.consoleOutputLines.push(firstLine); 127 | } else { 128 | state.consoleOutputLines[state.consoleOutputLines.length - 1] = 129 | state.consoleOutputLines[state.consoleOutputLines.length - 1] + firstLine; 130 | } 131 | 132 | if (restOfLines.length > 0) { 133 | for (const line of restOfLines) { 134 | deleteLastLineIfCarrotMovedToBeginning(); 135 | state.consoleOutputLines.push(line); 136 | } 137 | } 138 | 139 | updateClients(wss.clients, data); 140 | 141 | // console.log(` 142 | // Added ${lines.length} lines from data: ${data} resulting in ${commandLog.length} lines. 143 | // Last line: ${commandLog[commandLog.length - 1]}. 144 | // Sending to ${wss.clients.size} clients. 145 | // Full thing: ${commandLog}`); 146 | }; 147 | 148 | app.use(express.json()); 149 | 150 | app.post('/execute', (req, res) => { 151 | const { command, inputs } = req.body; 152 | 153 | console.log(`Received command: ${command}`); 154 | 155 | if (!command) { 156 | return res.status(400).json({ error: 'Command is required.' }); 157 | } 158 | 159 | if (state.activeProcess) { 160 | return res 161 | .status(400) 162 | .json({ error: 'Another command is already running.' }); 163 | } 164 | 165 | state.consoleOutputLines.push('\n'); 166 | state.consoleOutputLines.push('[[COMMAND]] ' + command); 167 | state.consoleOutputLines.push('\n'); 168 | 169 | const options = { cwd: RELATIVE_PATH_TO_AUTOGPT }; 170 | 171 | state.activeCommandString = command; 172 | state.activeProcess = exec(command, options, (error) => { 173 | if (error) { 174 | console.error(`Error executing command: ${error.message}`); 175 | } 176 | }); 177 | 178 | state.activeProcess.stdout.on('data', appendOutputChunkAndUpdateClients); 179 | state.activeProcess.stderr.on('data', appendOutputChunkAndUpdateClients); 180 | 181 | state.activeProcess.stdout.on('data', (data) => process.stdout.write(data)); 182 | state.activeProcess.stderr.on('data', (data) => 183 | console.log(`💔 Error: ${data}`), 184 | ); 185 | 186 | state.activeProcess.stderr.on('close', (code) => { 187 | console.log(`Process exited with code ${code}`); 188 | state.activeProcess = null; 189 | state.activeCommandString = null; 190 | updateClients(wss.clients); 191 | }); 192 | 193 | if (inputs?.length > 0) { 194 | for (const input of inputs) { 195 | state.activeProcess.stdin.write(input + '\n'); 196 | } 197 | } 198 | 199 | updateClients(wss.clients); 200 | 201 | res.status(200).json({ message: 'Command received, processing...' }); 202 | }); 203 | 204 | app.all('/clear', (req, res) => { 205 | state.consoleOutputLines.length = 0; 206 | updateClients(wss.clients); 207 | 208 | exec('cls'); 209 | 210 | res.status(200).json({ message: 'Output cleared...' }); 211 | }); 212 | 213 | app.post('/input', (req, res) => { 214 | const { input } = req.body; 215 | 216 | if (input === undefined || input === null) { 217 | return res.status(400).json({ error: 'Input is required.' }); 218 | } 219 | 220 | if (!state.activeProcess) { 221 | return res.status(400).json({ error: 'No command is currently running.' }); 222 | } 223 | 224 | console.log(`Sending input to active process: ${input}`); 225 | 226 | state.activeProcess.stdin.write(input + '\n'); 227 | res.status(200).json({ message: 'Input sent to the active command.' }); 228 | }); 229 | 230 | app.all('/kill', (req, res) => { 231 | if (!state.activeProcess) { 232 | return res.status(400).json({ error: 'No command is currently running.' }); 233 | } 234 | 235 | console.log('Killing active process...'); 236 | 237 | kill(state.activeProcess.pid, 'SIGTERM', (err) => { 238 | if (err) { 239 | console.error('Error while killing the process:', err); 240 | return res.status(500).json({ error: 'Failed to kill the process.' }); 241 | } 242 | state.activeProcess = null; 243 | res.status(200).json({ message: 'Active command killed.' }); 244 | }); 245 | }); 246 | 247 | app.post('/setenv', (req, res) => { 248 | const { key, value } = req.body; 249 | 250 | if (!key || !value) { 251 | return res.status(400).json({ error: 'Key and value are required.' }); 252 | } 253 | 254 | const envFilePath = path.join(__dirname, RELATIVE_PATH_TO_AUTOGPT, '.env'); 255 | 256 | // Check if the .env file exists, create it if not 257 | if (!fs.existsSync(envFilePath)) { 258 | fs.writeFileSync(envFilePath, ''); 259 | } 260 | 261 | // Read the contents of the .env file and parse it 262 | const envFileContents = fs.readFileSync(envFilePath, 'utf8'); 263 | const envVars = dotenv.parse(envFileContents); 264 | 265 | // Update the env variable or add a new one 266 | envVars[key] = value; 267 | 268 | // Convert the envVars object back to a string 269 | const updatedEnvFileContents = Object.entries(envVars) 270 | .map(([k, v]) => `${k}=${v}`) 271 | .join('\n'); 272 | 273 | // Write the updated contents back to the .env file 274 | fs.writeFileSync(envFilePath, updatedEnvFileContents); 275 | 276 | // Reload the configuration 277 | configuration = loadConfiguration(); 278 | 279 | // Update clients with the new configuration 280 | updateClients(wss.clients); 281 | 282 | res.status(200).json({ message: 'Environment variable set.' }); 283 | }); 284 | 285 | app.post('/applyprofile', (req, res) => { 286 | const { data } = req.body; 287 | 288 | if (!data) { 289 | return res.status(400).json({ error: 'Data is required.' }); 290 | } 291 | 292 | try { 293 | const yamlString = yaml.dump(data); 294 | const pathToSettingsFile = path.join( 295 | __dirname, 296 | RELATIVE_PATH_TO_AUTOGPT, 297 | PATH_TO_AI_SETTINGS_FILE, 298 | ); 299 | fs.writeFileSync(pathToSettingsFile, yamlString, 'utf8'); 300 | res.status(200).json({ message: 'AI profile saved to ai_settings.yml.' }); 301 | } catch (error) { 302 | res 303 | .status(500) 304 | .json({ error: `Error saving AI profile: ${error.message}` }); 305 | } 306 | }); 307 | 308 | app.use((error, req, res, next) => { 309 | console.error(`Error: ${error.message}`); 310 | res.status(error.status || 500).json({ error: error.message }); 311 | }); 312 | 313 | const server = app.listen(PORT, () => { 314 | console.log(`Server is running on port ${PORT}`); 315 | }); 316 | 317 | // Handle the upgrade request 318 | server.on('upgrade', (request, socket, head) => { 319 | wss.handleUpgrade(request, socket, head, (ws) => { 320 | wss.emit('connection', ws, request); 321 | }); 322 | }); 323 | 324 | ////// 325 | -------------------------------------------------------------------------------- /apps/frontend/.env.example: -------------------------------------------------------------------------------- 1 | BACKEND_URL=http://localhost:2200 2 | -------------------------------------------------------------------------------- /apps/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /apps/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Auto-GPT WebUI 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host 0.0.0.0", 8 | "start": "vite --host 0.0.0.0", 9 | "build": "tsc && vite build", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@chakra-ui/icons": "^2.0.18", 14 | "@chakra-ui/react": "^2.5.5", 15 | "@emotion/react": "^11.10.6", 16 | "@emotion/styled": "^11.10.6", 17 | "@formkit/auto-animate": "^1.0.0-beta.6", 18 | "ansi-to-html": "^0.7.2", 19 | "dotenv": "^16.0.3", 20 | "framer-motion": "^10.11.2", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "react-icons": "^4.8.0", 24 | "zustand": "^4.3.7" 25 | }, 26 | "devDependencies": { 27 | "@modyfi/vite-plugin-yaml": "^1.0.4", 28 | "@types/react": "^18.0.28", 29 | "@types/react-dom": "^18.0.11", 30 | "@vitejs/plugin-react": "^3.1.0", 31 | "typescript": "^4.9.3", 32 | "vite": "^4.2.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choephix/auto-gpt-webui/9475d298d10f71c4ec3b09f323161c1fa3770b7c/apps/frontend/public/favicon.ico -------------------------------------------------------------------------------- /apps/frontend/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choephix/auto-gpt-webui/9475d298d10f71c4ec3b09f323161c1fa3770b7c/apps/frontend/public/favicon.png -------------------------------------------------------------------------------- /apps/frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choephix/auto-gpt-webui/9475d298d10f71c4ec3b09f323161c1fa3770b7c/apps/frontend/public/logo192.png -------------------------------------------------------------------------------- /apps/frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choephix/auto-gpt-webui/9475d298d10f71c4ec3b09f323161c1fa3770b7c/apps/frontend/public/logo512.png -------------------------------------------------------------------------------- /apps/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /apps/frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /apps/frontend/src/App.animations.css: -------------------------------------------------------------------------------- 1 | @keyframes fade-in { 2 | from { 3 | opacity: 0; 4 | } 5 | } 6 | .withFadeInAnimation { 7 | animation-name: fade-in; 8 | animation-duration: 0.47s; 9 | } 10 | 11 | @keyframes pop-in { 12 | from { 13 | opacity: 0; 14 | transform: scale(0.8); 15 | } 16 | } 17 | .withPopInAnimation { 18 | animation-name: pop-in; 19 | animation-duration: 0.47s; 20 | } 21 | 22 | @keyframes slide-right { 23 | from { 24 | opacity: 0; 25 | transform: translateX(-40px); 26 | } 27 | } 28 | .withSlideRightAnimation { 29 | animation-name: slide-right; 30 | animation-duration: 0.47s; 31 | } 32 | 33 | @keyframes slide-down { 34 | from { 35 | opacity: 0; 36 | transform: translateY(-20px); 37 | } 38 | } 39 | .withSlideDownAnimation { 40 | animation-name: slide-down; 41 | animation-duration: 0.47s; 42 | } 43 | -------------------------------------------------------------------------------- /apps/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Azeret+Mono:ital,wght@0,400;0,700;1,400&family=Overpass+Mono:wght@400;700&display=swap'); 2 | 3 | :root { 4 | --app-background-color: #83ebc8; 5 | --app-fade-color: #97ebcf; 6 | --segment-background-color: #ffffff; 7 | --insignificant-color: #ffff; 8 | 9 | --text-color: #000000; 10 | --output-segment-bg-color: #080c14c0; 11 | --output-segment-border-color: #999e; 12 | } 13 | 14 | .dark-mode { 15 | --app-background-color: #164636; 16 | --app-fade-color: #0a2b20; 17 | --segment-background-color: #080c14c0; 18 | --insignificant-color: #455; 19 | 20 | --text-color: #ffffff; 21 | --output-segment-bg-color: #080c14c0; 22 | --output-segment-border-color: #fffe; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 28 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 29 | sans-serif; 30 | -webkit-font-smoothing: antialiased; 31 | -moz-osx-font-smoothing: grayscale; 32 | } 33 | 34 | .AppBackground { 35 | background-color: var(--app-background-color); 36 | box-shadow: inset 0 0 256px 0 #0008, inset 0 0 32px 0 #0088; 37 | transition: background-color 0.17s ease-out !important; 38 | } 39 | 40 | .OutputSegmentBox { 41 | background-color: var(--segment-background-color); 42 | transition: background-color 0.17s ease-out !important; 43 | 44 | border: 2px solid #fffe; 45 | border: 1px solid #999e; 46 | border-radius: 1rem; 47 | padding: 20px 28px; 48 | margin: 12px 0; 49 | 50 | text-align: start; 51 | 52 | overflow: hidden; 53 | } 54 | 55 | .OutputSegmentBox pre { 56 | /* font-family: 'Azeret Mono', monospace !important; */ 57 | font-family: 'Overpass Mono', monospace !important; 58 | 59 | /* color: white; */ 60 | font-size: 14px; 61 | font-family: monospace; 62 | white-space: pre-wrap; 63 | margin-bottom: 12px; 64 | 65 | filter: brightness(1.2); 66 | font-weight: bold; 67 | } 68 | 69 | .OutputCommandHeadingBox { 70 | background: #ffd000; 71 | border: 1px solid #999e; 72 | overflow: hidden; 73 | } 74 | 75 | .OutputCommandHeadingBox > * { 76 | animation-name: slide-right; 77 | animation-duration: 0.47s; 78 | } 79 | 80 | .ScrollToBottomButton { 81 | background-color: var(--insignificant-color) !important; 82 | } 83 | 84 | /* Add this to your CSS file */ 85 | .TheFooter { 86 | position: fixed; 87 | bottom: 0; 88 | left: 0; 89 | right: 0; 90 | height: 64px; 91 | display: flex; 92 | justify-content: center; 93 | align-items: center; 94 | z-index: 10; 95 | padding-left: 4px; 96 | padding-right: 4px; 97 | pointer-events: none; 98 | background-color: var(--app-fade-color); 99 | } 100 | 101 | .TheFooter:before { 102 | content: ''; 103 | position: absolute; 104 | top: -100px; 105 | left: 0; 106 | right: 0; 107 | height: 100px; 108 | background-repeat: repeat-x; 109 | background-position: bottom; 110 | background-size: 100% 100px; 111 | background: linear-gradient(to bottom, #fff0 0%, var(--app-fade-color) 100%); 112 | } 113 | 114 | .FancyBox { 115 | backdrop-filter: blur(4px); 116 | -webkit-backdrop-filter: blur(4px); 117 | box-shadow: 0 6px 16px 0 #0005; 118 | animation-name: pop-in; 119 | animation-duration: 0.47s; 120 | animation-timing-function: ease-out; 121 | } 122 | 123 | .glass { 124 | backdrop-filter: blur(8px); 125 | } 126 | 127 | .withShadow { 128 | box-shadow: 0 6px 16px 0 #0045; 129 | } 130 | 131 | .withSmallShadow { 132 | box-shadow: 0 3px 8px 0 #0002; 133 | } 134 | 135 | .fancyFont { 136 | font-family: 'pragmatapro-fraktur', serif; 137 | font-weight: 400; 138 | font-style: normal; 139 | } 140 | 141 | button { 142 | pointer-events: all; 143 | } 144 | 145 | .CommandStip { 146 | background-image: linear-gradient(180deg, #455555, #546464); 147 | color: #fffd; 148 | font-weight: bold; 149 | text-shadow: 0 0 3px #000, 0 0 8px #0009; 150 | border: 1px solid #0004; 151 | transition: background-color 0.2s ease-in-out, 152 | background-image 0.2s ease-in-out, color 0.2s ease-in-out; 153 | } 154 | 155 | .CommandStip.continuous { 156 | background: linear-gradient(180deg, #a51225, #861221); 157 | } 158 | 159 | .CommandStip.active { 160 | background-color: #5ba828; 161 | background-image: repeating-linear-gradient( 162 | -45deg, 163 | transparent, 164 | transparent 20px, 165 | #367a08 20px, 166 | #367a08 40px 167 | ); 168 | background-size: 200% 200%; 169 | animation: barberpole 3s linear infinite; 170 | } 171 | 172 | .CommandStip.active.continuous { 173 | background-color: #a90f2e; 174 | background-image: repeating-linear-gradient( 175 | -45deg, 176 | transparent, 177 | transparent 20px, 178 | Crimson 20px, 179 | Crimson 40px 180 | ); 181 | } 182 | 183 | @keyframes barberpole { 184 | 100% { 185 | background-position: -56.568px 0; 186 | } 187 | } 188 | 189 | /****************************************************** 190 | * CSS3 Pretty Scrollbar 191 | ******************************************************/ 192 | 193 | .pretty-scrollbar::-webkit-scrollbar-track { 194 | background-color: #0001; 195 | } 196 | 197 | .pretty-scrollbar::-webkit-scrollbar { 198 | width: 8px; 199 | background-color: #0000; 200 | } 201 | 202 | .pretty-scrollbar::-webkit-scrollbar-thumb { 203 | border-radius: 10px; 204 | background-color: #3336; 205 | } 206 | -------------------------------------------------------------------------------- /apps/frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | 3 | import { ChevronDownIcon } from '@chakra-ui/icons'; 4 | import { 5 | Box, 6 | Button, 7 | Center, 8 | Flex, 9 | Modal, 10 | ModalBody, 11 | ModalContent, 12 | ModalOverlay, 13 | Spinner, 14 | Text, 15 | useColorMode, 16 | } from '@chakra-ui/react'; 17 | 18 | import { TheAreaAtTheBottom } from './components/TheAreaAtTheBottom'; 19 | import { TheAreaAtTheTop } from './components/TheAreaAtTheTop'; 20 | import { TheAreaInTheMiddle } from './components/TheAreaInTheMiddle'; 21 | import { ServicesRunner } from './components/TheHiddenServiceRunner'; 22 | import { useContextStore } from './store/useContextStore'; 23 | 24 | import './App.css'; 25 | import './App.animations.css'; 26 | 27 | function App() { 28 | const { socket } = useContextStore(); 29 | 30 | const { colorMode } = useColorMode(); 31 | 32 | return ( 33 | 38 | 39 | void null}> 40 | 41 | 42 | 43 | 44 | 45 | Connecting to server... 46 | 47 | 48 | 49 | 50 | {socket && ( 51 | <> 52 | 53 | 54 | 55 | 56 | )} 57 | 58 | ); 59 | } 60 | 61 | function TheHeader() { 62 | return ( 63 | 76 | 77 | 78 | ); 79 | } 80 | 81 | function TheFooter() { 82 | return ( 83 | 96 | 97 | 98 | ); 99 | } 100 | 101 | function TheMiddle() { 102 | const consoleLogContainerRef = useRef(null); 103 | const { outputSegments } = useContextStore(); 104 | const [showScrollButton, setShowScrollButton] = useState(false); 105 | 106 | function scrollToBottom() { 107 | const container = consoleLogContainerRef.current; 108 | 109 | if (!container) { 110 | console.log('no container'); 111 | return; 112 | } 113 | 114 | const maxScrollTop = container.scrollHeight - container.clientHeight; 115 | container.scrollTo({ top: maxScrollTop, behavior: 'smooth' }); 116 | 117 | setShowScrollButton(false); 118 | } 119 | 120 | function handleScroll() { 121 | const container = consoleLogContainerRef.current; 122 | 123 | if (!container) { 124 | console.log('no container'); 125 | return; 126 | } 127 | 128 | const maxScrollTop = container.scrollHeight - container.clientHeight; 129 | const delta = maxScrollTop - container.scrollTop; 130 | 131 | const maxDeltaForAutoScroll = container.clientHeight * 0.5; 132 | setShowScrollButton(delta > maxDeltaForAutoScroll); 133 | } 134 | 135 | useEffect(() => { 136 | const container = consoleLogContainerRef.current; 137 | 138 | if (container) { 139 | container.addEventListener('scroll', handleScroll); 140 | } 141 | 142 | return () => { 143 | if (container) { 144 | container.removeEventListener('scroll', handleScroll); 145 | } 146 | }; 147 | }, []); 148 | 149 | useEffect(() => { 150 | const container = consoleLogContainerRef.current; 151 | 152 | if (!container) { 153 | console.log('no container'); 154 | return; 155 | } 156 | 157 | const maxScrollTop = container.scrollHeight - container.clientHeight; 158 | const delta = maxScrollTop - container.scrollTop; 159 | const shouldAutoScroll = delta < 10 || maxScrollTop <= 0; 160 | 161 | if (shouldAutoScroll) { 162 | scrollToBottom(); 163 | } 164 | }, [outputSegments]); 165 | 166 | return ( 167 | <> 168 | {showScrollButton && ( 169 |
176 | 188 |
189 | )} 190 | 199 | 200 | 201 | 202 | ); 203 | } 204 | 205 | export default App; 206 | -------------------------------------------------------------------------------- /apps/frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/frontend/src/components/AboutInfoModal.tsx: -------------------------------------------------------------------------------- 1 | import { InfoOutlineIcon } from '@chakra-ui/icons'; 2 | import { 3 | useDisclosure, 4 | Button, 5 | Modal, 6 | ModalOverlay, 7 | ModalContent, 8 | ModalHeader, 9 | ModalCloseButton, 10 | ModalBody, 11 | Text, 12 | IconButton, 13 | Spacer, 14 | Link, 15 | Divider, 16 | } from '@chakra-ui/react'; 17 | 18 | export function AboutInfoModal() { 19 | const { isOpen, onOpen, onClose } = useDisclosure(); 20 | return ( 21 | <> 22 | } 29 | onClick={onOpen} 30 | /> 31 | 32 | 33 | 34 | 35 | About this project 36 | 37 | 38 | 39 | Auto-GPT WebUI is a frontend web application built to 40 | interact seamlessly with the{' '} 41 | 46 | Auto-GPT 47 | {' '} 48 | in the backend. 49 | 50 | 51 | 🌟 This project wouldn't be possible without the amazing work of{' '} 52 | 57 | Torantulino 58 | 59 | , the author of the{' '} 60 | 65 | original project. 66 | {' '} 67 | 68 | 69 | This project was built primarily for the developer's personal 70 | amusement, so availability to maintain and update the project may 71 | be limited. However, community involvement and contributions are 72 | always welcome and highly appreciated. 73 | 74 | 75 | 76 | 77 | 78 | Thank you for using Auto-GPT WebUI, and I hope you find it 79 | helpful! 80 | {' '} 81 | 🤍 82 | 83 | 84 | 85 | 86 | 87 | 88 | ); 89 | } 90 | import React from 'react'; 91 | -------------------------------------------------------------------------------- /apps/frontend/src/components/CommandStringBar.tsx: -------------------------------------------------------------------------------- 1 | import { Center, Text, useColorModeValue } from '@chakra-ui/react'; 2 | import { useAutoGPTStarter } from '../hooks/useAutoGPTStarter'; 3 | import { useContextStore } from '../store/useContextStore'; 4 | import { useSettingsStore } from '../store/useSettingsStore'; 5 | 6 | export function CommandStringBar() { 7 | const { backendState } = useContextStore(); 8 | const { command: aiStarterCommand } = useAutoGPTStarter(); 9 | const { autoContinuous } = useSettingsStore(); 10 | 11 | const commandStringToDisplay = 12 | backendState?.activeCommandString ?? aiStarterCommand; 13 | const comandIsRunning = !!backendState?.activeProcessRunning; 14 | 15 | return ( 16 |
27 | 28 | {commandStringToDisplay} 29 | 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /apps/frontend/src/components/EditAIProfileModal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | FormControl, 4 | FormLabel, 5 | Input, 6 | Modal, 7 | ModalBody, 8 | ModalCloseButton, 9 | ModalContent, 10 | ModalFooter, 11 | ModalHeader, 12 | ModalOverlay, 13 | Textarea, 14 | VStack, 15 | } from '@chakra-ui/react'; 16 | import React, { useEffect, useState } from 'react'; 17 | import { AIProfile } from '../entities/AIProfile'; 18 | 19 | interface EditAIProfileModalProps { 20 | isOpen: boolean; 21 | onClose: () => void; 22 | onSave?: (profile: AIProfile) => void; 23 | initialValues: AIProfile; 24 | isCreatingNewProfile: boolean; 25 | } 26 | 27 | export const EditAIProfileModal: React.FC = ({ 28 | isOpen, 29 | onClose, 30 | onSave, 31 | initialValues, 32 | isCreatingNewProfile, 33 | }) => { 34 | const [profile, setProfile] = useState(initialValues); 35 | 36 | useEffect(() => { 37 | setProfile({ ...initialValues }); 38 | updateGoals(); 39 | }, [initialValues.uid]); 40 | 41 | const updateGoals = (modifyBoforeSaving?: (goals: string[]) => string[]) => { 42 | let goals = [...profile.goals]; 43 | if (modifyBoforeSaving) { 44 | goals = modifyBoforeSaving(goals); 45 | } 46 | while (goals[goals.length - 1] === '') goals.pop(); 47 | goals.push(''); 48 | setProfile({ ...profile, goals }); 49 | }; 50 | 51 | const setGoal = (index: number, value: string) => { 52 | updateGoals((goals) => { 53 | goals[index] = value; 54 | return goals; 55 | }); 56 | }; 57 | 58 | const handleSave = () => { 59 | profile.goals = profile.goals.filter((goal) => goal !== ''); 60 | onSave?.(profile); 61 | }; 62 | 63 | return ( 64 | 65 | 66 | 67 | 68 | {isCreatingNewProfile ? 'New' : 'Edit'} AI Profile 69 | 70 | 71 | 72 | 73 | 74 | Name your AI 75 | 78 | setProfile({ ...profile, name: e.target.value }) 79 | } 80 | placeholder='Entrepreneur-GPT' 81 | /> 82 | 83 | 84 | Describe your AI's Role 85 |