diff --git a/.env.example b/.env.example index 27abccbd..99140160 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,9 @@ NEXT_PUBLIC_USE_USER_API_KEY="false" # GOOGLE_SEARCH_API_KEY="Your Google Search API Key" # GOOGLE_CUSTOM_INDEX_ID="Your Custom Index ID" +# 7. Set your BASE_URL, only used for production +BASE_URL="https://your-project.vercel.app/" + # if you want to use the auto-translation feature, set the following variables # and npm run translate TRANSLATOR_SERVICE="google" # possible values: "google", "openai" diff --git a/README.md b/README.md index 58f24679..62df2ff6 100644 --- a/README.md +++ b/README.md @@ -25,19 +25,12 @@ This is a port of [babyagi](https://github.com/yoheinakajima/babyagi) with [Lang ## 🚗 Roadmap -- [x] The BabyAGI can search and scrape the web. ([🐝 BabyBeeAGI](https://twitter.com/yoheinakajima/status/1652732735344246784)) -- [x] Exporting Execution Results -- [x] Execution history -- [x] Faster speeds and fewer errors. ([😺 BabyCatAGI](https://twitter.com/yoheinakajima/status/1657448504112091136)) -- [x] i18n support ( 🇧🇷, 🇩🇪, 🇺🇸, 🇪🇸, 🇫🇷, 🇮🇳, 🇭🇺, 🇯🇵, 🇷🇺, 🇹🇭, ... and much more) -- [x] User feedback -- [x] Improv UX for task creation (only BabyCatAGI🐱 & Client request) -- [x] Notification that all tasks have been completed. 🔔 -- [x] Display the current task and task list. 📌 - [x] Collapsible Sidebar ⏩️ - [x] User input & parallel tasking. ([🦌 BabyDeerAGI](https://twitter.com/yoheinakajima/status/1666313838868992001)) - [x] API updates support (gpt-3.5-turbo-0613/gpt-3.5-turbo-16k-0613/gpt-4-0613) - [x] Skills Class allows for easy skill creation ([🧝 BabyElfAGI](https://twitter.com/yoheinakajima/status/1678443482866933760)) +- [x] Aggregate the logic of the agent in the backend. +- [x] Add hooks to make it easier to handle the agent on the frontend. - [ ] Support the Llama2 model 🦙 and more ... diff --git a/package-lock.json b/package-lock.json index 5ad5ad24..8d307de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@types/react": "18.0.33", "@types/react-dom": "18.0.11", "@vercel/analytics": "^1.0.0", + "ai": "^2.1.31", "airtable": "^0.12.1", "axios": "^1.4.0", "cheerio": "^1.0.0-rc.12", @@ -36,7 +37,7 @@ "install": "^0.13.0", "langchain": "^0.0.64", "lodash": "^4.17.21", - "next": "13.2.4", + "next": "^13.4.16", "next-i18next": "^13.2.2", "next-themes": "^0.2.1", "npm": "^9.6.6", @@ -64,6 +65,19 @@ "tailwindcss": "^3.3.1" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@anthropic-ai/sdk": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.4.3.tgz", @@ -189,6 +203,18 @@ "node": ">=4" } }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", @@ -336,10 +362,64 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "peer": true + }, "node_modules/@next/env": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.2.4.tgz", - "integrity": "sha512-+Mq3TtpkeeKFZanPturjcXt+KHfKYnLlX6jMLyCrmpq6OOs4i1GqBOAauSkii9QeKCMTYzGppar21JU57b/GEA==" + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.16.tgz", + "integrity": "sha512-pCU0sJBqdfKP9mwDadxvZd+eLz3fZrTlmmDHY12Hdpl3DD0vy8ou5HWKVfG0zZS6tqhL4wnQqRbspdY5nqa7MA==" }, "node_modules/@next/eslint-plugin-next": { "version": "13.2.4", @@ -349,40 +429,10 @@ "glob": "7.1.7" } }, - "node_modules/@next/swc-android-arm-eabi": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz", - "integrity": "sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-android-arm64": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz", - "integrity": "sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.4.tgz", - "integrity": "sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.16.tgz", + "integrity": "sha512-Rl6i1uUq0ciRa3VfEpw6GnWAJTSKo9oM2OrkGXPsm7rMxdd2FR5NkKc0C9xzFCI4+QtmBviWBdF2m3ur3Nqstw==", "cpu": [ "arm64" ], @@ -395,9 +445,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.4.tgz", - "integrity": "sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.16.tgz", + "integrity": "sha512-o1vIKYbZORyDmTrPV1hApt9NLyWrS5vr2p5hhLGpOnkBY1cz6DAXjv8Lgan8t6X87+83F0EUDlu7klN8ieZ06A==", "cpu": [ "x64" ], @@ -409,40 +459,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-freebsd-x64": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz", - "integrity": "sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm-gnueabihf": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz", - "integrity": "sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.4.tgz", - "integrity": "sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.16.tgz", + "integrity": "sha512-JRyAl8lCfyTng4zoOmE6hNI2f1MFUr7JyTYCHl1RxX42H4a5LMwJhDVQ7a9tmDZ/yj+0hpBn+Aan+d6lA3v0UQ==", "cpu": [ "arm64" ], @@ -455,9 +475,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.4.tgz", - "integrity": "sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.16.tgz", + "integrity": "sha512-9gqVqNzUMWbUDgDiND18xoUqhwSm2gmksqXgCU0qaOKt6oAjWz8cWYjgpPVD0WICKFylEY/gvPEP1fMZDVFZ/g==", "cpu": [ "arm64" ], @@ -470,9 +490,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.4.tgz", - "integrity": "sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.16.tgz", + "integrity": "sha512-KcQGwchAKmZVPa8i5PLTxvTs1/rcFnSltfpTm803Tr/BtBV3AxCkHLfhtoyVtVzx/kl/oue8oS+DSmbepQKwhw==", "cpu": [ "x64" ], @@ -485,9 +505,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.4.tgz", - "integrity": "sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.16.tgz", + "integrity": "sha512-2RbMZNxYnJmW8EPHVBsGZPq5zqWAyBOc/YFxq/jIQ/Yn3RMFZ1dZVCjtIcsiaKmgh7mjA/W0ApbumutHNxRqqQ==", "cpu": [ "x64" ], @@ -500,9 +520,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.4.tgz", - "integrity": "sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.16.tgz", + "integrity": "sha512-thDcGonELN7edUKzjzlHrdoKkm7y8IAdItQpRvvMxNUXa4d9r0ElofhTZj5emR7AiXft17hpen+QAkcWpqG7Jg==", "cpu": [ "arm64" ], @@ -515,9 +535,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.4.tgz", - "integrity": "sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.16.tgz", + "integrity": "sha512-f7SE1Mo4JAchUWl0LQsbtySR9xCa+x55C0taetjUApKtcLR3AgAjASrrP+oE1inmLmw573qRnE1eZN8YJfEBQw==", "cpu": [ "ia32" ], @@ -530,9 +550,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.4.tgz", - "integrity": "sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.16.tgz", + "integrity": "sha512-WamDZm1M/OEM4QLce3lOmD1XdLEl37zYZwlmOLhmF7qYJ2G6oYm9+ejZVv+LakQIsIuXhSpVlOvrxIAHqwRkPQ==", "cpu": [ "x64" ], @@ -1139,9 +1159,9 @@ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" }, "node_modules/@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", "dependencies": { "tslib": "^2.4.0" } @@ -1180,6 +1200,12 @@ "@types/ms": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "peer": true + }, "node_modules/@types/hast": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", @@ -1393,6 +1419,136 @@ "react": "^16.8||^17||^18" } }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "peer": true, + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "peer": true, + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/reactivity-transform/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "peer": true, + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "peer": true, + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "peer": true, + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "peer": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1410,9 +1566,9 @@ "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -1441,6 +1597,43 @@ "node": ">= 6.0.0" } }, + "node_modules/ai": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/ai/-/ai-2.1.31.tgz", + "integrity": "sha512-ihkimg9iP/C/OaxjYj9KgA4gPC9Rzw60Xv3+AWYrQ807L/NEF8sgZBmA1hv8/DC8KzMv/7N+a5HCQPDZXNUs+g==", + "dependencies": { + "eventsource-parser": "1.0.0", + "nanoid": "3.3.6", + "solid-swr-store": "0.10.7", + "sswr": "2.0.0", + "swr": "2.2.0", + "swr-store": "0.10.6", + "swrv": "1.0.4" + }, + "engines": { + "node": ">=14.6" + }, + "peerDependencies": { + "react": "^18.2.0", + "solid-js": "^1.7.7", + "svelte": "^3.0.0 || ^4.0.0", + "vue": "^3.3.4" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/airtable": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/airtable/-/airtable-0.12.1.tgz", @@ -1537,11 +1730,11 @@ } }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -1701,11 +1894,11 @@ } }, "node_modules/axobject-query": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", - "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/bail": { @@ -1861,6 +2054,17 @@ "node": "*" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2080,6 +2284,19 @@ "node": ">=6" } }, + "node_modules/code-red": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.3.tgz", + "integrity": "sha512-kVwJELqiILQyG5aeuyKFbdsI1fmQy1Cmf7dQ8eGmVuJoaRVdwey7WaMknr2ZFeVSYSKT0rExsa8EGw0aoI/1QQ==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "@types/estree": "^1.0.0", + "acorn": "^8.8.2", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2194,6 +2411,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "peer": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -2254,33 +2484,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", - "dependencies": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2538,25 +2741,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -3025,6 +3209,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3046,6 +3239,14 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, + "node_modules/eventsource-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.0.0.tgz", + "integrity": "sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==", + "engines": { + "node": ">=14.18" + } + }, "node_modules/expr-eval": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz", @@ -3413,6 +3614,11 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, "node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -3856,21 +4062,6 @@ "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.0.tgz", "integrity": "sha512-WdPV58rT3aOWXvvyuBydnCq4S2BM1Yz8shKxlEpk/6x+GX202XRvXOycEFtNgnHVLoc46hpexPFx8Pz1/sMS0w==" }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -4047,14 +4238,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -4107,6 +4290,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-reference": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz", + "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==", + "peer": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -4122,14 +4314,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", @@ -4187,14 +4371,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -4206,18 +4382,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -4229,11 +4393,6 @@ "node": ">=8" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4508,6 +4667,12 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4586,6 +4751,18 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -4809,6 +4986,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5503,41 +5686,38 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/next": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/next/-/next-13.2.4.tgz", - "integrity": "sha512-g1I30317cThkEpvzfXujf0O4wtaQHtDCLhlivwlTJ885Ld+eOgcz7r3TGQzeU+cSRoNHtD8tsJgzxVdYojFssw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.16.tgz", + "integrity": "sha512-1xaA/5DrfpPu0eV31Iro7JfPeqO8uxQWb1zYNTe+KDKdzqkAGapLcDYHMLNKXKB7lHjZ7LfKUOf9dyuzcibrhA==", "dependencies": { - "@next/env": "13.2.4", - "@swc/helpers": "0.4.14", + "@next/env": "13.4.16", + "@swc/helpers": "0.5.1", + "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.1", + "watchpack": "2.4.0", + "zod": "3.21.4" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=14.6.0" + "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-android-arm-eabi": "13.2.4", - "@next/swc-android-arm64": "13.2.4", - "@next/swc-darwin-arm64": "13.2.4", - "@next/swc-darwin-x64": "13.2.4", - "@next/swc-freebsd-x64": "13.2.4", - "@next/swc-linux-arm-gnueabihf": "13.2.4", - "@next/swc-linux-arm64-gnu": "13.2.4", - "@next/swc-linux-arm64-musl": "13.2.4", - "@next/swc-linux-x64-gnu": "13.2.4", - "@next/swc-linux-x64-musl": "13.2.4", - "@next/swc-win32-arm64-msvc": "13.2.4", - "@next/swc-win32-ia32-msvc": "13.2.4", - "@next/swc-win32-x64-msvc": "13.2.4" + "@next/swc-darwin-arm64": "13.4.16", + "@next/swc-darwin-x64": "13.4.16", + "@next/swc-linux-arm64-gnu": "13.4.16", + "@next/swc-linux-arm64-musl": "13.4.16", + "@next/swc-linux-x64-gnu": "13.4.16", + "@next/swc-linux-x64-musl": "13.4.16", + "@next/swc-win32-arm64-msvc": "13.4.16", + "@next/swc-win32-ia32-msvc": "13.4.16", + "@next/swc-win32-x64-msvc": "13.4.16" }, "peerDependencies": { - "@opentelemetry/api": "^1.4.0", - "fibers": ">= 3.1.0", - "node-sass": "^6.0.0 || ^7.0.0", + "@opentelemetry/api": "^1.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -5546,12 +5726,6 @@ "@opentelemetry/api": { "optional": true }, - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, "sass": { "optional": true } @@ -8748,21 +8922,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -9099,6 +9258,17 @@ "optional": true, "peer": true }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9927,6 +10097,15 @@ "node": ">=10" } }, + "node_modules/seroval": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-0.5.1.tgz", + "integrity": "sha512-ZfhQVB59hmIauJG5Ydynupy8KHyr5imGNtdDhbZG68Ufh1Ynkv9KOYOAABf71oVbQxJ8VkWnMHAjEHE7fWkH5g==", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9967,6 +10146,28 @@ "node": ">=8" } }, + "node_modules/solid-js": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.9.tgz", + "integrity": "sha512-p1orXnauMQmwYULZtuPAXyKNRGEN2qh60kLX4YURa3jvulxAqjlh2kWEljXCtAVR6UZPC16NXdj9ASHcH383Fg==", + "peer": true, + "dependencies": { + "csstype": "^3.1.0", + "seroval": "^0.5.0" + } + }, + "node_modules/solid-swr-store": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/solid-swr-store/-/solid-swr-store-0.10.7.tgz", + "integrity": "sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "solid-js": "^1.2", + "swr-store": "^0.10" + } + }, "node_modules/sonner": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/sonner/-/sonner-0.3.5.tgz", @@ -9993,15 +10194,23 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "node_modules/sswr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sswr/-/sswr-2.0.0.tgz", + "integrity": "sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==", "dependencies": { - "internal-slot": "^1.0.4" + "swrev": "^4.0.0" }, + "peerDependencies": { + "svelte": "^4.0.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "engines": { - "node": ">= 0.4" + "node": ">=10.0.0" } }, "node_modules/string_decoder": { @@ -10217,6 +10426,65 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svelte": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.1.2.tgz", + "integrity": "sha512-/evA8U6CgOHe5ZD1C1W3va9iJG7mWflcCdghBORJaAhD2JzrVERJty/2gl0pIPrJYBGZwZycH6onYf+64XXF9g==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^3.2.1", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.0", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/swr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", + "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/swr-store": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/swr-store/-/swr-store-0.10.6.tgz", + "integrity": "sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==", + "dependencies": { + "dequal": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz", + "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==" + }, + "node_modules/swrv": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz", + "integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==", + "peerDependencies": { + "vue": ">=3.2.26 < 4" + } + }, "node_modules/synckit": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", @@ -10727,6 +10995,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10794,6 +11070,31 @@ "node": ">=0.10.0" } }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -10837,20 +11138,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", diff --git a/package.json b/package.json index f8268137..ad468fd6 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/react": "18.0.33", "@types/react-dom": "18.0.11", "@vercel/analytics": "^1.0.0", + "ai": "^2.1.31", "airtable": "^0.12.1", "axios": "^1.4.0", "cheerio": "^1.0.0-rc.12", @@ -38,7 +39,7 @@ "install": "^0.13.0", "langchain": "^0.0.64", "lodash": "^4.17.21", - "next": "13.2.4", + "next": "^13.4.16", "next-i18next": "^13.2.2", "next-themes": "^0.2.1", "npm": "^9.6.6", diff --git a/public/locales/ar/agent.json b/public/locales/ar/agent.json index b82ba3a9..d47874d8 100644 --- a/public/locales/ar/agent.json +++ b/public/locales/ar/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "يرجى إعداد مفتاح OpenAI API الخاص بك من قائمة الإعدادات.", "ALL_TASKS_COMPLETED_TOAST": "اكتملت جميع المهام!", "COPIED_TO_CLIPBOARD": "نسخ إلى الحافظة", - "TASK_COMPLETED_TOAST": "تمت المهمة!" + "TASK_COMPLETED_TOAST": "تمت المهمة!", + "EXECUTION_STOPPED": "تم إيقاف التنفيذ" } \ No newline at end of file diff --git a/public/locales/au/agent.json b/public/locales/au/agent.json index 61377a03..e5973862 100644 --- a/public/locales/au/agent.json +++ b/public/locales/au/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Please set up your OpenAI API key from the settings menu.", "ALL_TASKS_COMPLETED_TOAST": "All Tasks Completed!", "COPIED_TO_CLIPBOARD": "Copied to clipboard", - "TASK_COMPLETED_TOAST": "Task Completed!" + "TASK_COMPLETED_TOAST": "Task Completed!", + "EXECUTION_STOPPED": "Execution stopped." } \ No newline at end of file diff --git a/public/locales/bg/agent.json b/public/locales/bg/agent.json index f89d3a34..58fb3ce3 100644 --- a/public/locales/bg/agent.json +++ b/public/locales/bg/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Моля, настройте вашия OpenAI API ключ от менюто с настройки.", "ALL_TASKS_COMPLETED_TOAST": "Всички задачи изпълнени!", "COPIED_TO_CLIPBOARD": "Копирано в клипборда", - "TASK_COMPLETED_TOAST": "Задачата е изпълнена!" + "TASK_COMPLETED_TOAST": "Задачата е изпълнена!", + "EXECUTION_STOPPED": "Изпълнението е спряно" } \ No newline at end of file diff --git a/public/locales/bn/agent.json b/public/locales/bn/agent.json index a1375f6a..b6376ce5 100644 --- a/public/locales/bn/agent.json +++ b/public/locales/bn/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "সেটিংস মেনু থেকে আপনার OpenAI API কী সেট আপ করুন।", "ALL_TASKS_COMPLETED_TOAST": "সমস্ত কাজ সম্পন্ন!", "COPIED_TO_CLIPBOARD": "ক্লিপবোর্ডে কপি করা হয়েছে", - "TASK_COMPLETED_TOAST": "কাজ সম্পূর্ণ!" + "TASK_COMPLETED_TOAST": "কাজ সম্পূর্ণ!", + "EXECUTION_STOPPED": "কার্যপরিচালনা থামে গেল" } \ No newline at end of file diff --git a/public/locales/br/agent.json b/public/locales/br/agent.json index 7426dc93..0e541e7a 100644 --- a/public/locales/br/agent.json +++ b/public/locales/br/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Por favor, configure sua chave de API do OpenAI no menu de configurações.", "ALL_TASKS_COMPLETED_TOAST": "Todas as tarefas concluídas!", "COPIED_TO_CLIPBOARD": "Copiado para a área de transferência", - "TASK_COMPLETED_TOAST": "Tarefa concluída!" + "TASK_COMPLETED_TOAST": "Tarefa concluída!", + "EXECUTION_STOPPED": "A execução foi interrompida." } \ No newline at end of file diff --git a/public/locales/cs/agent.json b/public/locales/cs/agent.json index 80a8bc2f..51cac022 100644 --- a/public/locales/cs/agent.json +++ b/public/locales/cs/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Nastavte svůj OpenAI API klíč z nabídky nastavení.", "ALL_TASKS_COMPLETED_TOAST": "Všechny úkoly dokončeny!", "COPIED_TO_CLIPBOARD": "Zkopírováno do schránky", - "TASK_COMPLETED_TOAST": "Úkol splněn!" + "TASK_COMPLETED_TOAST": "Úkol splněn!", + "EXECUTION_STOPPED": "Provedení zastaveno" } \ No newline at end of file diff --git a/public/locales/da/agent.json b/public/locales/da/agent.json index 3060b528..afe86e94 100644 --- a/public/locales/da/agent.json +++ b/public/locales/da/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Indstil venligst din OpenAI API-nøgle fra indstillingsmenuen.", "ALL_TASKS_COMPLETED_TOAST": "Alle opgaver udført!", "COPIED_TO_CLIPBOARD": "Kopieret til udklipsholder", - "TASK_COMPLETED_TOAST": "Opgave afsluttet!" + "TASK_COMPLETED_TOAST": "Opgave afsluttet!", + "EXECUTION_STOPPED": "Udførelse stoppede" } \ No newline at end of file diff --git a/public/locales/de/agent.json b/public/locales/de/agent.json index 6956f8d5..7d651997 100644 --- a/public/locales/de/agent.json +++ b/public/locales/de/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Bitte richten Sie Ihren OpenAI-API-Schlüssel über das Einstellungsmenü ein.", "ALL_TASKS_COMPLETED_TOAST": "Alle Aufgaben erledigt!", "COPIED_TO_CLIPBOARD": "In die Zwischenablage kopiert", - "TASK_COMPLETED_TOAST": "Aufgabe erledigt!" + "TASK_COMPLETED_TOAST": "Aufgabe erledigt!", + "EXECUTION_STOPPED": "Die Ausführung wurde gestoppt." } \ No newline at end of file diff --git a/public/locales/el/agent.json b/public/locales/el/agent.json index 0d2f506c..5dc4a090 100644 --- a/public/locales/el/agent.json +++ b/public/locales/el/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Ρυθμίστε το κλειδί OpenAI API από το μενού ρυθμίσεων.", "ALL_TASKS_COMPLETED_TOAST": "Ολοκληρώθηκαν όλες οι εργασίες!", "COPIED_TO_CLIPBOARD": "Αντιγράφηκε στο πρόχειρο", - "TASK_COMPLETED_TOAST": "Εργασία Ολοκληρώθηκε!" + "TASK_COMPLETED_TOAST": "Εργασία Ολοκληρώθηκε!", + "EXECUTION_STOPPED": "Η εκτέλεση διακόπηκε" } \ No newline at end of file diff --git a/public/locales/en/agent.json b/public/locales/en/agent.json index 61377a03..760408c0 100644 --- a/public/locales/en/agent.json +++ b/public/locales/en/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Please set up your OpenAI API key from the settings menu.", "ALL_TASKS_COMPLETED_TOAST": "All Tasks Completed!", "COPIED_TO_CLIPBOARD": "Copied to clipboard", - "TASK_COMPLETED_TOAST": "Task Completed!" -} \ No newline at end of file + "TASK_COMPLETED_TOAST": "Task Completed!", + "EXECUTION_STOPPED": "Execution stopped" +} diff --git a/public/locales/es/agent.json b/public/locales/es/agent.json index a458a2e7..4ac39559 100644 --- a/public/locales/es/agent.json +++ b/public/locales/es/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Configure su clave API de OpenAI desde el menú de configuración.", "ALL_TASKS_COMPLETED_TOAST": "¡Todas las tareas completadas!", "COPIED_TO_CLIPBOARD": "Copiado al portapapeles", - "TASK_COMPLETED_TOAST": "¡Tarea terminada!" + "TASK_COMPLETED_TOAST": "¡Tarea terminada!", + "EXECUTION_STOPPED": "La ejecución se detuvo" } \ No newline at end of file diff --git a/public/locales/et/agent.json b/public/locales/et/agent.json index 2fa25262..b485f58f 100644 --- a/public/locales/et/agent.json +++ b/public/locales/et/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Seadistage seadete menüüst oma OpenAI API võti.", "ALL_TASKS_COMPLETED_TOAST": "Kõik ülesanded täidetud!", "COPIED_TO_CLIPBOARD": "Kopeeriti lõikelauale", - "TASK_COMPLETED_TOAST": "Ülesanne täidetud!" + "TASK_COMPLETED_TOAST": "Ülesanne täidetud!", + "EXECUTION_STOPPED": "Täitmine peatus" } \ No newline at end of file diff --git a/public/locales/fa/agent.json b/public/locales/fa/agent.json index 0546ae75..e1dbad94 100644 --- a/public/locales/fa/agent.json +++ b/public/locales/fa/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "لطفاً کلید OpenAI API خود را از منوی تنظیمات تنظیم کنید.", "ALL_TASKS_COMPLETED_TOAST": "تمام وظایف تکمیل شد!", "COPIED_TO_CLIPBOARD": "در کلیپ بورد کپی شد", - "TASK_COMPLETED_TOAST": "کار تکمیل شد!" + "TASK_COMPLETED_TOAST": "کار تکمیل شد!", + "EXECUTION_STOPPED": "تنفیذ متوقف شد" } \ No newline at end of file diff --git a/public/locales/fi/agent.json b/public/locales/fi/agent.json index e14e53f0..54ea333b 100644 --- a/public/locales/fi/agent.json +++ b/public/locales/fi/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Määritä OpenAI API -avain asetusvalikosta.", "ALL_TASKS_COMPLETED_TOAST": "Kaikki tehtävät suoritettu!", "COPIED_TO_CLIPBOARD": "Kopioitu leikepöydälle", - "TASK_COMPLETED_TOAST": "Tehtävä suoritettu!" + "TASK_COMPLETED_TOAST": "Tehtävä suoritettu!", + "EXECUTION_STOPPED": "Suoritus keskeytetty" } \ No newline at end of file diff --git a/public/locales/fr/agent.json b/public/locales/fr/agent.json index ef426257..20278340 100644 --- a/public/locales/fr/agent.json +++ b/public/locales/fr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Veuillez configurer votre clé API OpenAI à partir du menu des paramètres.", "ALL_TASKS_COMPLETED_TOAST": "Toutes les tâches terminées !", "COPIED_TO_CLIPBOARD": "Copié dans le presse-papier", - "TASK_COMPLETED_TOAST": "Tâche terminée!" + "TASK_COMPLETED_TOAST": "Tâche terminée!", + "EXECUTION_STOPPED": "L'exécution s'est arrêtée." } \ No newline at end of file diff --git a/public/locales/gb/agent.json b/public/locales/gb/agent.json index 61377a03..795077b8 100644 --- a/public/locales/gb/agent.json +++ b/public/locales/gb/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Please set up your OpenAI API key from the settings menu.", "ALL_TASKS_COMPLETED_TOAST": "All Tasks Completed!", "COPIED_TO_CLIPBOARD": "Copied to clipboard", - "TASK_COMPLETED_TOAST": "Task Completed!" + "TASK_COMPLETED_TOAST": "Task Completed!", + "EXECUTION_STOPPED": "Execution stopped" } \ No newline at end of file diff --git a/public/locales/gu/agent.json b/public/locales/gu/agent.json index 327866fb..d62dfef3 100644 --- a/public/locales/gu/agent.json +++ b/public/locales/gu/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "કૃપા કરીને સેટિંગ્સ મેનૂમાંથી તમારી OpenAI API કી સેટ કરો.", "ALL_TASKS_COMPLETED_TOAST": "બધા કાર્યો પૂર્ણ!", "COPIED_TO_CLIPBOARD": "ક્લિપબોર્ડ પર કૉપિ કરી", - "TASK_COMPLETED_TOAST": "કાર્ય પૂર્ણ થયું!" + "TASK_COMPLETED_TOAST": "કાર્ય પૂર્ણ થયું!", + "EXECUTION_STOPPED": "પરિનિર્માણ બંધ થઇ ગયેલું છે" } \ No newline at end of file diff --git a/public/locales/he/agent.json b/public/locales/he/agent.json index 9aaa0f36..5c0f26d9 100644 --- a/public/locales/he/agent.json +++ b/public/locales/he/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "הגדר את מפתח ה-API של OpenAI מתפריט ההגדרות.", "ALL_TASKS_COMPLETED_TOAST": "כל המשימות הושלמו!", "COPIED_TO_CLIPBOARD": "הועתק ללוח", - "TASK_COMPLETED_TOAST": "משימה הושלמה!" + "TASK_COMPLETED_TOAST": "משימה הושלמה!", + "EXECUTION_STOPPED": "הביצוע נעצר" } \ No newline at end of file diff --git a/public/locales/hi/agent.json b/public/locales/hi/agent.json index 1fa131a7..6a8d64d2 100644 --- a/public/locales/hi/agent.json +++ b/public/locales/hi/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "कृपया सेटिंग मेनू से अपनी OpenAI API कुंजी सेट करें।", "ALL_TASKS_COMPLETED_TOAST": "सभी कार्य पूर्ण!", "COPIED_TO_CLIPBOARD": "क्लिपबोर्ड पर नकल", - "TASK_COMPLETED_TOAST": "कार्य पूरा हो गया!" + "TASK_COMPLETED_TOAST": "कार्य पूरा हो गया!", + "EXECUTION_STOPPED": "अंदाज़ा रोक दिया गया" } \ No newline at end of file diff --git a/public/locales/hr/agent.json b/public/locales/hr/agent.json index 7ea5d006..e1595242 100644 --- a/public/locales/hr/agent.json +++ b/public/locales/hr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Postavite svoj OpenAI API ključ iz izbornika postavki.", "ALL_TASKS_COMPLETED_TOAST": "Svi zadaci obavljeni!", "COPIED_TO_CLIPBOARD": "Kopirano u međuspremnik", - "TASK_COMPLETED_TOAST": "Zadatak obavljen!" + "TASK_COMPLETED_TOAST": "Zadatak obavljen!", + "EXECUTION_STOPPED": "Izvršenje je zaustavljeno" } \ No newline at end of file diff --git a/public/locales/hu/agent.json b/public/locales/hu/agent.json index 528007f5..339c2020 100644 --- a/public/locales/hu/agent.json +++ b/public/locales/hu/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Kérjük, állítsa be OpenAI API-kulcsát a beállítások menüből.", "ALL_TASKS_COMPLETED_TOAST": "Minden feladat teljesítve!", "COPIED_TO_CLIPBOARD": "Vágólapra másolva", - "TASK_COMPLETED_TOAST": "Feladat elvégezve!" + "TASK_COMPLETED_TOAST": "Feladat elvégezve!", + "EXECUTION_STOPPED": "Végrehajtás leállt" } \ No newline at end of file diff --git a/public/locales/id/agent.json b/public/locales/id/agent.json index 61f1a522..ad73ba01 100644 --- a/public/locales/id/agent.json +++ b/public/locales/id/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Silakan atur kunci API OpenAI Anda dari menu pengaturan.", "ALL_TASKS_COMPLETED_TOAST": "Semua Tugas Selesai!", "COPIED_TO_CLIPBOARD": "Disalin ke papan klip", - "TASK_COMPLETED_TOAST": "Tugas selesai!" + "TASK_COMPLETED_TOAST": "Tugas selesai!", + "EXECUTION_STOPPED": "Pembatasan eksekusi" } \ No newline at end of file diff --git a/public/locales/it/agent.json b/public/locales/it/agent.json index 1baa8c0c..4b8d4f12 100644 --- a/public/locales/it/agent.json +++ b/public/locales/it/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Configura la tua chiave API OpenAI dal menu delle impostazioni.", "ALL_TASKS_COMPLETED_TOAST": "Tutte le attività completate!", "COPIED_TO_CLIPBOARD": "Copiato negli appunti", - "TASK_COMPLETED_TOAST": "Compito completato!" + "TASK_COMPLETED_TOAST": "Compito completato!", + "EXECUTION_STOPPED": "Esecuzione interrotta" } \ No newline at end of file diff --git a/public/locales/ja/agent.json b/public/locales/ja/agent.json index 9f72333d..6b1c2cfb 100644 --- a/public/locales/ja/agent.json +++ b/public/locales/ja/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "設定メニューから OpenAI API キーを設定してください", "ALL_TASKS_COMPLETED_TOAST": "すべてのタスクが完了しました", "COPIED_TO_CLIPBOARD": "クリップボードにコピーされました", - "TASK_COMPLETED_TOAST": "タスクが完了しました" -} + "TASK_COMPLETED_TOAST": "タスクが完了しました", + "EXECUTION_STOPPED": "実行が停止しました" +} \ No newline at end of file diff --git a/public/locales/kn/agent.json b/public/locales/kn/agent.json index 962fcde4..8ac6c400 100644 --- a/public/locales/kn/agent.json +++ b/public/locales/kn/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "ದಯವಿಟ್ಟು ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೆನುವಿನಿಂದ ನಿಮ್ಮ OpenAI API ಕೀಯನ್ನು ಹೊಂದಿಸಿ.", "ALL_TASKS_COMPLETED_TOAST": "ಎಲ್ಲಾ ಕಾರ್ಯಗಳು ಪೂರ್ಣಗೊಂಡಿವೆ!", "COPIED_TO_CLIPBOARD": "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಲಾಗಿದೆ", - "TASK_COMPLETED_TOAST": "ಕಾರ್ಯ ಪೂರ್ಣಗೊಂಡಿದೆ!" + "TASK_COMPLETED_TOAST": "ಕಾರ್ಯ ಪೂರ್ಣಗೊಂಡಿದೆ!", + "EXECUTION_STOPPED": "ನೆಲಸಲು ನಿಲ್ಲಿಸಲಾಗಿದೆ" } \ No newline at end of file diff --git a/public/locales/ko/agent.json b/public/locales/ko/agent.json index 20e585c8..bd5a373e 100644 --- a/public/locales/ko/agent.json +++ b/public/locales/ko/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "설정 메뉴에서 OpenAI API 키를 설정하세요.", "ALL_TASKS_COMPLETED_TOAST": "모든 작업 완료!", "COPIED_TO_CLIPBOARD": "클립보드에 복사됨", - "TASK_COMPLETED_TOAST": "작업 완료!" + "TASK_COMPLETED_TOAST": "작업 완료!", + "EXECUTION_STOPPED": "실행 중지" } \ No newline at end of file diff --git a/public/locales/lt/agent.json b/public/locales/lt/agent.json index 2c7244fb..61dd3e3b 100644 --- a/public/locales/lt/agent.json +++ b/public/locales/lt/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Nustatymų meniu nustatykite OpenAI API raktą.", "ALL_TASKS_COMPLETED_TOAST": "Visos užduotys baigtos!", "COPIED_TO_CLIPBOARD": "Nukopijuota į mainų sritį", - "TASK_COMPLETED_TOAST": "Užduotis atlikta!" + "TASK_COMPLETED_TOAST": "Užduotis atlikta!", + "EXECUTION_STOPPED": "Vykdomasis sustabdytas" } \ No newline at end of file diff --git a/public/locales/lv/agent.json b/public/locales/lv/agent.json index a8790d20..79eb4e74 100644 --- a/public/locales/lv/agent.json +++ b/public/locales/lv/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Lūdzu, iestatiet savu OpenAI API atslēgu iestatījumu izvēlnē.", "ALL_TASKS_COMPLETED_TOAST": "Visi uzdevumi izpildīti!", "COPIED_TO_CLIPBOARD": "Kopēts starpliktuvē", - "TASK_COMPLETED_TOAST": "Uzdevums izpildīts!" + "TASK_COMPLETED_TOAST": "Uzdevums izpildīts!", + "EXECUTION_STOPPED": "Izp" } \ No newline at end of file diff --git a/public/locales/ml/agent.json b/public/locales/ml/agent.json index 1d3b9851..8c4a554c 100644 --- a/public/locales/ml/agent.json +++ b/public/locales/ml/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "ക്രമീകരണ മെനുവിൽ നിന്ന് നിങ്ങളുടെ OpenAI API കീ സജ്ജീകരിക്കുക.", "ALL_TASKS_COMPLETED_TOAST": "എല്ലാ ജോലികളും പൂർത്തിയായി!", "COPIED_TO_CLIPBOARD": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി", - "TASK_COMPLETED_TOAST": "ടാസ്ക് പൂർത്തിയായി!" + "TASK_COMPLETED_TOAST": "ടാസ്ക് പൂർത്തിയായി!", + "EXECUTION_STOPPED": "നിഷ്പത്തി നിർത്തി." } \ No newline at end of file diff --git a/public/locales/nl/agent.json b/public/locales/nl/agent.json index 00e2c615..02a72418 100644 --- a/public/locales/nl/agent.json +++ b/public/locales/nl/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Stel uw OpenAI API-sleutel in via het instellingenmenu.", "ALL_TASKS_COMPLETED_TOAST": "Alle taken voltooid!", "COPIED_TO_CLIPBOARD": "Gekopieerd naar het klembord", - "TASK_COMPLETED_TOAST": "Taak volbracht!" + "TASK_COMPLETED_TOAST": "Taak volbracht!", + "EXECUTION_STOPPED": "Uitvoering gestopt" } \ No newline at end of file diff --git a/public/locales/no/agent.json b/public/locales/no/agent.json index bdbbc0ba..4d40b874 100644 --- a/public/locales/no/agent.json +++ b/public/locales/no/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Sett opp OpenAI API-nøkkelen fra innstillingsmenyen.", "ALL_TASKS_COMPLETED_TOAST": "Alle oppgaver fullført!", "COPIED_TO_CLIPBOARD": "Kopiert til utklippstavlen", - "TASK_COMPLETED_TOAST": "Oppgave fullført!" + "TASK_COMPLETED_TOAST": "Oppgave fullført!", + "EXECUTION_STOPPED": "Utførelsen stoppet" } \ No newline at end of file diff --git a/public/locales/pl/agent.json b/public/locales/pl/agent.json index d4e16cc8..b453a97f 100644 --- a/public/locales/pl/agent.json +++ b/public/locales/pl/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Skonfiguruj swój klucz API OpenAI w menu ustawień.", "ALL_TASKS_COMPLETED_TOAST": "Wszystkie zadania wykonane!", "COPIED_TO_CLIPBOARD": "Skopiowane do schowka", - "TASK_COMPLETED_TOAST": "Zadanie ukończone!" + "TASK_COMPLETED_TOAST": "Zadanie ukończone!", + "EXECUTION_STOPPED": "Wykonanie zatrzymane" } \ No newline at end of file diff --git a/public/locales/pt/agent.json b/public/locales/pt/agent.json index 68d9f580..589f5808 100644 --- a/public/locales/pt/agent.json +++ b/public/locales/pt/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Configure sua chave de API OpenAI no menu de configurações.", "ALL_TASKS_COMPLETED_TOAST": "Todas as tarefas concluídas!", "COPIED_TO_CLIPBOARD": "Copiado para a área de transferência", - "TASK_COMPLETED_TOAST": "Tarefa completa!" + "TASK_COMPLETED_TOAST": "Tarefa completa!", + "EXECUTION_STOPPED": "Execução interrompida" } \ No newline at end of file diff --git a/public/locales/ro/agent.json b/public/locales/ro/agent.json index 52114471..e11f5a42 100644 --- a/public/locales/ro/agent.json +++ b/public/locales/ro/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Vă rugăm să vă configurați cheia API OpenAI din meniul de setări.", "ALL_TASKS_COMPLETED_TOAST": "Toate sarcinile finalizate!", "COPIED_TO_CLIPBOARD": "Copiat în clipboard", - "TASK_COMPLETED_TOAST": "Sarcina finalizată!" + "TASK_COMPLETED_TOAST": "Sarcina finalizată!", + "EXECUTION_STOPPED": "Execuția s-a oprit" } \ No newline at end of file diff --git a/public/locales/ru/agent.json b/public/locales/ru/agent.json index 37aff535..e6b9097e 100644 --- a/public/locales/ru/agent.json +++ b/public/locales/ru/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Настройте свой ключ API OpenAI в меню настроек.", "ALL_TASKS_COMPLETED_TOAST": "Все задания выполнены!", "COPIED_TO_CLIPBOARD": "Скопировано в буфер обмена", - "TASK_COMPLETED_TOAST": "Задача выполнена!" + "TASK_COMPLETED_TOAST": "Задача выполнена!", + "EXECUTION_STOPPED": "Исполнение остановлено" } \ No newline at end of file diff --git a/public/locales/sk/agent.json b/public/locales/sk/agent.json index acc7d36a..ca5597c0 100644 --- a/public/locales/sk/agent.json +++ b/public/locales/sk/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Nastavte svoj kľúč OpenAI API z ponuky nastavení.", "ALL_TASKS_COMPLETED_TOAST": "Všetky úlohy dokončené!", "COPIED_TO_CLIPBOARD": "Skopírované do schránky", - "TASK_COMPLETED_TOAST": "Úloha dokončená!" + "TASK_COMPLETED_TOAST": "Úloha dokončená!", + "EXECUTION_STOPPED": "Vykonanie zastavené" } \ No newline at end of file diff --git a/public/locales/sl/agent.json b/public/locales/sl/agent.json index 865b2d91..c99a158f 100644 --- a/public/locales/sl/agent.json +++ b/public/locales/sl/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "V meniju z nastavitvami nastavite ključ OpenAI API.", "ALL_TASKS_COMPLETED_TOAST": "Vse naloge opravljene!", "COPIED_TO_CLIPBOARD": "Kopirano v odložišče", - "TASK_COMPLETED_TOAST": "Naloga opravljena!" + "TASK_COMPLETED_TOAST": "Naloga opravljena!", + "EXECUTION_STOPPED": "Izvedba je bila ustavljena." } \ No newline at end of file diff --git a/public/locales/sr/agent.json b/public/locales/sr/agent.json index 3b835439..0cfe3174 100644 --- a/public/locales/sr/agent.json +++ b/public/locales/sr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Подесите свој ОпенАИ АПИ кључ из менија подешавања.", "ALL_TASKS_COMPLETED_TOAST": "Сви задаци завршени!", "COPIED_TO_CLIPBOARD": "Копирано у међуспремник", - "TASK_COMPLETED_TOAST": "Задатак завршен!" + "TASK_COMPLETED_TOAST": "Задатак завршен!", + "EXECUTION_STOPPED": "Izvršenje je zaustavljeno" } \ No newline at end of file diff --git a/public/locales/sv/agent.json b/public/locales/sv/agent.json index b1e6cab2..b93bdfbc 100644 --- a/public/locales/sv/agent.json +++ b/public/locales/sv/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Vänligen ställ in din OpenAI API-nyckel från inställningsmenyn.", "ALL_TASKS_COMPLETED_TOAST": "Alla uppgifter slutförda!", "COPIED_TO_CLIPBOARD": "Kopierade till urklipp", - "TASK_COMPLETED_TOAST": "Uppgift slutförd!" + "TASK_COMPLETED_TOAST": "Uppgift slutförd!", + "EXECUTION_STOPPED": "Utförande avbrutet." } \ No newline at end of file diff --git a/public/locales/ta/agent.json b/public/locales/ta/agent.json index 120f18e5..a0222567 100644 --- a/public/locales/ta/agent.json +++ b/public/locales/ta/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "அமைப்புகள் மெனுவிலிருந்து உங்கள் OpenAI API விசையை அமைக்கவும்.", "ALL_TASKS_COMPLETED_TOAST": "அனைத்து பணிகளும் முடிந்தது!", "COPIED_TO_CLIPBOARD": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது", - "TASK_COMPLETED_TOAST": "பணி முடிந்தது!" + "TASK_COMPLETED_TOAST": "பணி முடிந்தது!", + "EXECUTION_STOPPED": "மேற்படுக்கை நிறுத்தப்பட்டது" } \ No newline at end of file diff --git a/public/locales/te/agent.json b/public/locales/te/agent.json index f7575491..72c86f33 100644 --- a/public/locales/te/agent.json +++ b/public/locales/te/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "దయచేసి సెట్టింగ్‌ల మెను నుండి మీ OpenAI API కీని సెటప్ చేయండి.", "ALL_TASKS_COMPLETED_TOAST": "అన్ని పనులు పూర్తయ్యాయి!", "COPIED_TO_CLIPBOARD": "క్లిప్‌బోర్డ్‌కి కాపీ చేయబడింది", - "TASK_COMPLETED_TOAST": "పని పూర్తయింది!" + "TASK_COMPLETED_TOAST": "పని పూర్తయింది!", + "EXECUTION_STOPPED": "నిష్పత్తి నిలిపివేయబడింది" } \ No newline at end of file diff --git a/public/locales/th/agent.json b/public/locales/th/agent.json index 839c954a..b3598766 100644 --- a/public/locales/th/agent.json +++ b/public/locales/th/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "โปรดตั้งค่าคีย์ OpenAI API ของคุณจากเมนูการตั้งค่า", "ALL_TASKS_COMPLETED_TOAST": "เสร็จสิ้นภารกิจทั้งหมด!", "COPIED_TO_CLIPBOARD": "คัดลอกไปที่คลิปบอร์ดแล้ว", - "TASK_COMPLETED_TOAST": "ภารกิจเสร็จสิ้น!" + "TASK_COMPLETED_TOAST": "ภารกิจเสร็จสิ้น!", + "EXECUTION_STOPPED": "การดำเนินการถูกระงับ" } \ No newline at end of file diff --git a/public/locales/tr/agent.json b/public/locales/tr/agent.json index d23a5953..ec8d1b25 100644 --- a/public/locales/tr/agent.json +++ b/public/locales/tr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Lütfen ayarlar menüsünden OpenAI API anahtarınızı ayarlayın.", "ALL_TASKS_COMPLETED_TOAST": "Tüm Görevler Tamamlandı!", "COPIED_TO_CLIPBOARD": "Panoya kopyalandı", - "TASK_COMPLETED_TOAST": "Görev tamamlandı!" + "TASK_COMPLETED_TOAST": "Görev tamamlandı!", + "EXECUTION_STOPPED": "İşlem durdu" } \ No newline at end of file diff --git a/public/locales/uk/agent.json b/public/locales/uk/agent.json index 2dd4a827..9f7909a0 100644 --- a/public/locales/uk/agent.json +++ b/public/locales/uk/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Налаштуйте ключ OpenAI API у меню налаштувань.", "ALL_TASKS_COMPLETED_TOAST": "Усі завдання виконано!", "COPIED_TO_CLIPBOARD": "Скопійовано в буфер обміну", - "TASK_COMPLETED_TOAST": "Завдання виконано!" + "TASK_COMPLETED_TOAST": "Завдання виконано!", + "EXECUTION_STOPPED": "Виконання припинено" } \ No newline at end of file diff --git a/public/locales/ur/agent.json b/public/locales/ur/agent.json index 20d1b90c..c95db984 100644 --- a/public/locales/ur/agent.json +++ b/public/locales/ur/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "براہ کرم ترتیبات کے مینو سے اپنی OpenAI API کلید ترتیب دیں۔", "ALL_TASKS_COMPLETED_TOAST": "تمام کام مکمل!", "COPIED_TO_CLIPBOARD": "کلپ بورڈ پر کاپی ہو گیا۔", - "TASK_COMPLETED_TOAST": "کام مکمل ہو گیا!" + "TASK_COMPLETED_TOAST": "کام مکمل ہو گیا!", + "EXECUTION_STOPPED": "تنفیذ روک دیا گیا" } \ No newline at end of file diff --git a/public/locales/vi/agent.json b/public/locales/vi/agent.json index e9b92806..b8874573 100644 --- a/public/locales/vi/agent.json +++ b/public/locales/vi/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Vui lòng thiết lập khóa API OpenAI của bạn từ menu cài đặt.", "ALL_TASKS_COMPLETED_TOAST": "Tất cả các nhiệm vụ đã hoàn thành!", "COPIED_TO_CLIPBOARD": "Sao chép vào clipboard", - "TASK_COMPLETED_TOAST": "Nhiệm vụ hoàn thành!" + "TASK_COMPLETED_TOAST": "Nhiệm vụ hoàn thành!", + "EXECUTION_STOPPED": "Thực hiện đã dừng lại" } \ No newline at end of file diff --git a/public/locales/zh/agent.json b/public/locales/zh/agent.json index 34900be5..8e2925c9 100644 --- a/public/locales/zh/agent.json +++ b/public/locales/zh/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "请从设置菜单中设置您的 OpenAI API 密钥。", "ALL_TASKS_COMPLETED_TOAST": "所有任务完成!", "COPIED_TO_CLIPBOARD": "复制到剪贴板", - "TASK_COMPLETED_TOAST": "任务完成!" + "TASK_COMPLETED_TOAST": "任务完成!", + "EXECUTION_STOPPED": "执行已停止" } \ No newline at end of file diff --git a/public/locales/zhtw/agent.json b/public/locales/zhtw/agent.json index 5e0b1bb0..4b11a236 100644 --- a/public/locales/zhtw/agent.json +++ b/public/locales/zhtw/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "請從設置菜單中設置您的OpenAI API密鑰。", "ALL_TASKS_COMPLETED_TOAST": "所有任務已完成!", "COPIED_TO_CLIPBOARD": "已複製到剪貼簿。", - "TASK_COMPLETED_TOAST": "任務已完成!" + "TASK_COMPLETED_TOAST": "任務已完成!", + "EXECUTION_STOPPED": "執行停止了" } \ No newline at end of file diff --git a/src/agents/babybeeagi/agent.ts b/src/agents/babybeeagi/agent.ts deleted file mode 100644 index a99739a4..00000000 --- a/src/agents/babybeeagi/agent.ts +++ /dev/null @@ -1,567 +0,0 @@ -import { AgentStatus, Message, TaskStatus, ToolType } from '@/types'; -import { textCompletion } from './tools/textCompletion'; -import { overviewAgent, summarizerAgent, taskManagementAgent } from './service'; -import { getToolIcon, setupMessage } from '@/utils/message'; -import axios from 'axios'; -import { parseTasks } from '@/utils/task'; -import { getUserApiKey } from '@/utils/settings'; -import { t } from 'i18next'; -import { translate } from '@/utils/translate'; - -export interface AgentTask { - id: number; - task: string; - tool: ToolType; - dependentTaskId?: number; - status: TaskStatus; - result?: string; - resultSummary?: string; -} - -export class BabyBeeAGI { - objective: string; - modelName: string; - firstTask: string; - taskList: AgentTask[] = []; - sessionSummary: string = ''; - taskIdCounter: number = 1; - isRunning: boolean; - verbose: boolean; - language: string = 'en'; - messageCallback: (message: Message) => void; - statusCallback: (status: AgentStatus) => void; - cancelCallback: () => void; - abortController?: AbortController; - - constructor( - objective: string, - modelName: string, - firstTask: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancel: () => void, - language: string = 'en', - verbose: boolean = false, - ) { - this.objective = objective; - this.taskList = []; - this.verbose = verbose; - this.language = language; - this.modelName = modelName; - this.firstTask = firstTask; - this.cancelCallback = cancel; - this.messageCallback = messageCallback; - this.statusCallback = statusCallback; - this.isRunning = false; - } - - // print logs - printBabyBee() { - if (!this.verbose) return; - console.log( - '%c*****BABY BEE AGI*****\n\n%c%s', - 'color:orange', - '', - 'Baby Bee AGI is running...', - ); - } - - printObjective() { - this.messageCallback(setupMessage('objective', this.objective)); - if (!this.verbose) return; - console.log( - '%c*****OBJECTIVE*****\n\n%c%s', - 'color:blue', - '', - this.objective, - ); - } - - printTaskList() { - if (!this.isRunning) return; - - let message = - '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - this.taskList.forEach((task) => { - const dependentTask = task.dependentTaskId - ? `${task.dependentTaskId}` - : '-'; - const status = task.status === 'complete' ? '✅' : '⬜️'; - message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( - task.tool, - )} | ${dependentTask} |\n`; - }); - - this.messageCallback(setupMessage('task-list', message)); - - if (!this.verbose) return; - console.log('%c*****TASK LIST*****\n\n%c', 'color:fuchsia', ''); - console.log(message); - } - - printSessionSummary() { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('session-summary', this.sessionSummary)); - - if (!this.verbose) return; - console.log('%c*****SESSION SUMMARY*****\n\n%c', 'color:orange', ''); - console.log(this.sessionSummary); - } - - printNextTask(task: AgentTask) { - if (!this.isRunning) return; - - const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ - task.tool - }]**`; - this.messageCallback(setupMessage('next-task', nextTask)); - - if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n\n%s', 'color:green', '', nextTask); - } - - printResult(result: string, task: AgentTask) { - if (!this.isRunning) return; - - let output = result; - if (task.tool !== 'text-completion') { - // code block for non-text-completion tools - output = '```\n' + output + '\n```'; - } - this.messageCallback(setupMessage('task-result', output, task?.tool)); - - if (!this.verbose) return; - output = result.length > 2000 ? result.slice(0, 2000) + '...' : result; - console.log('%c*****TASK RESULT*****\n%c%s', 'color:purple', '', output); - } - - printResultSummary(summary: string) { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('task-result-summary', summary)); - - if (!this.verbose) return; - console.log( - '%c*****TASK RESULT SUMMARY*****\n%c%s', - 'color:purple', - '', - summary, - ); - } - - printDone() { - if (!this.isRunning) return; - - this.messageCallback( - setupMessage( - 'done', - `Number of tasks completed: ${this.taskIdCounter.toString()}`, - ), - ); - - if (!this.verbose) return; - console.log('%c*****DONE*****%c', 'color:blue', ''); - } - - printAllTaskCompleted() { - if (!this.isRunning) return; - - this.messageCallback( - setupMessage('complete', translate('ALL_TASK_COMPLETED_TOAST', 'agent')), - ); - if (!this.verbose) return; - console.log('%c*****ALL TASK COMPLETED*****%c', 'color:blue', ''); - } - - // Tools functions - async webSearchTool(query: string) { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data.response; - } - - async webScrapeTool(url: string) { - const response = await axios - .post( - '/api/tools/scrape', - { - url, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async textCompletionTool(prompt: string) { - if (getUserApiKey()) { - return await textCompletion( - prompt, - 'gpt-3.5-turbo-0613', - getUserApiKey(), - ); - } - - const response = await axios - .post( - '/api/tools/completion', - { - prompt, - apiKey: getUserApiKey(), - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - // Task list functions - async addTask(task: AgentTask) { - this.taskList.push(task); - } - - async getTask(id: number) { - return this.taskList.find((task) => task.id === id); - } - - async getCompletedTasks() { - return this.taskList.filter((task) => task.status === 'complete'); - } - - async summarizeTask(value: string) { - const text = value.length > 4000 ? value.slice(0, 4000) + '...' : value; - - if (getUserApiKey()) { - return await summarizerAgent(text, this.language, getUserApiKey()); - } - - const response = await axios - .post( - '/api/agents/summarize', - { - text, - language: this.language, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async overviewTask(lastTaskId: number) { - const completedTasks = await this.getCompletedTasks(); - let completedTasksText = ''; - completedTasks.forEach((task) => { - completedTasksText += `${task.id}. ${task.task} - ${task.resultSummary}\n`; - }); - - if (getUserApiKey()) { - return await overviewAgent( - this.objective, - this.sessionSummary, - lastTaskId, - completedTasksText, - getUserApiKey(), - ); - } - - const response = await axios - .post( - '/api/agents/overview', - { - objective: this.objective, - session_summary: this.sessionSummary, - last_task_id: lastTaskId, - completed_tasks_text: completedTasksText, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async managementTask( - result: string, - taskDescription: string, - incompleteTasks: string[], - currntTaskId: number, - ) { - let taskList = this.taskList; - // copy task list - const originalTaskList = taskList.slice(); - // minified task list - const minifiedTaskList = taskList.map((task) => { - const { result, ...rest } = task; - return rest; - }); - const websearchVar = process.env.SERP_API_KEY ? '[web-search] ' : ''; // if search api key is not set, don't add [web-search] to the task description - const res = result.slice(0, 4000); // come up with a better solution lator - - let managedResult = ''; - if (getUserApiKey()) { - managedResult = await taskManagementAgent( - minifiedTaskList, - this.objective, - res, - websearchVar, - this.modelName, - this.language, - getUserApiKey(), - ); - } else { - const response = await axios - .post( - '/api/agents/management', - { - task_list: minifiedTaskList, - objective: this.objective, - result: res, - websearch_var: websearchVar, - model_name: this.modelName, - language: this.language, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - managedResult = response?.data?.response; - } - - // update task list - try { - taskList = parseTasks(managedResult); - } catch (error) { - console.error(error); - - // TODO: handle error - return taskList; - } - - // Add the 'result' field back in - for (let i = 0; i < taskList.length; i++) { - const originalTask = originalTaskList[i]; - if (originalTask?.result) { - taskList[i].result = originalTask.result; - } - } - - const currentTask = taskList[currntTaskId - 1]; - if (currentTask) { - taskList[currntTaskId - 1].result = managedResult; - } - - return taskList; - } - - // Agent functions - async executeTask(task: AgentTask, taskList: AgentTask[], objective: string) { - // Check if task is already completed - let dependentTask; - if (task.dependentTaskId) { - dependentTask = await this.getTask(task.dependentTaskId); - if (!dependentTask || dependentTask.status !== 'complete') { - return; - } - } - - // Execute task - this.statusCallback({ type: 'executing' }); - this.printNextTask(task); - let taskPrompt = `Complete your assign task based on the objective:\n\n${objective}, Your task: ${task.task}`; - if (task.dependentTaskId) { - if (dependentTask) { - const dependentTaskResult = dependentTask.resultSummary; // Use summary instead of result to avoid long text (original code use result) - // console.log('dependentTaskResult: ', dependentTaskResult); - taskPrompt += `\nThe previous task ${dependentTask.id}. ${dependentTask.task} result: ${dependentTaskResult}`; - } - } - - // taskPrompt += '\nResponses should be no more than 1000 characters.'; // Added message (Not in original code) - taskPrompt += '\nResponse:'; - let result = ''; - - switch (task.tool) { - case 'text-completion': - result = - (await this.textCompletionTool(taskPrompt)) ?? - 'Failed to complete text'; - break; - case 'web-search': - const search = (await this.webSearchTool(task.task)) ?? ''; - result = JSON.stringify(search); - break; - case 'web-scrape': - result = - (await this.webScrapeTool(task.task)) ?? 'Failed to scrape web page'; - break; - default: - result = 'Unknown tool'; - break; - } - - this.printResult(result, task); - - this.statusCallback({ type: 'updating' }); - // Update task status and result - task.status = 'complete'; - task.result = result; - task.resultSummary = await this.summarizeTask(result); - - this.printResultSummary(task.resultSummary ?? ''); - - this.statusCallback({ type: 'summarizing' }); - // Update session summary - this.sessionSummary = await this.overviewTask(task.id); - - this.printSessionSummary(); - - // Increment task id counter - this.taskIdCounter += 1; - - const incompleteTasks = taskList - .filter((task) => task.status === 'incomplete') - .map((task) => task.task); - - this.statusCallback({ type: 'managing' }); - // Update task manager agent of tasks - this.taskList = await this.managementTask( - result, - task.task, - incompleteTasks, - task.id, - ); - } - - async stop() { - this.isRunning = false; - this.cancelCallback(); - this.abortController?.abort(); - } - - async start() { - // Add the first task - const task: AgentTask = { - id: this.taskIdCounter, // 1 - task: this.firstTask, - tool: 'text-completion', - status: 'incomplete', - }; - - this.addTask(task); - this.taskIdCounter = 0; - this.printBabyBee(); - this.printObjective(); - - // Start the loop - this.isRunning = true; - await this.loop(); - - if (!this.isRunning) { - this.statusCallback({ type: 'finished' }); - return; - } - - // Objective completed - this.printAllTaskCompleted(); - this.statusCallback({ type: 'finished' }); - this.cancelCallback(); - this.isRunning = false; - } - - async loop() { - // Continue the loop while there are incomplete tasks - while ( - this.taskList.some((task) => task.status === 'incomplete') && - this.isRunning - ) { - this.statusCallback({ type: 'preparing' }); - // Filter out incomplete tasks - const incompleteTasks = this.taskList.filter( - (task) => task.status === 'incomplete', - ); - - if (incompleteTasks.length === 0) { - break; - } - - // sort tasks by id - incompleteTasks.sort((a, b) => a.id - b.id); - - // Pull the first task - const task = incompleteTasks[0]; - - if (!this.isRunning) break; - - // Execute the task & call task manager from function - await this.executeTask(task, incompleteTasks, this.objective); - - this.statusCallback({ type: 'closing' }); - // Print task list - this.printTaskList(); - this.printDone(); - - await new Promise((resolve) => setTimeout(resolve, 1000)); // Sleep before checking the task list again - } - } -} diff --git a/src/agents/babybeeagi/chains/taskManagement.ts b/src/agents/babybeeagi/chains/taskManagement.ts deleted file mode 100644 index d5263164..00000000 --- a/src/agents/babybeeagi/chains/taskManagement.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskManagementChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskManagementTemplate = `You are a task management AI tasked with cleaning the formatting of and reprioritizing the following tasks: {minified_task_list}. - Consider the ultimate objective of your team: {objective}. - Do not remove any tasks. Task description must be answered in {language}. Return the result as a JSON-formatted list of dictionaries.\n - Create new tasks based on the result of last task if necessary for the objective. Limit tasks types to those that can be completed with the available tools listed below. Task description should be detailed. - The maximum task list length is 7. Do not add an 8th task. - The last completed task has the following result: {result}. - Current tool option is [text-completion] {websearch_var} and [web-scrape] only. - For tasks using [web-scrape], provide only the URL to scrape as the task description. Do not provide placeholder URLs, but use ones provided by a search step or the initial objective. - For tasks using [web-search], provide the search query, and only the search query to use (eg. not 'research waterproof shoes, but 'waterproof shoes') - dependent_task_id should always be null or a number. - Do not reorder completed tasks. Only reorder and dedupe incomplete tasks.\n - Make sure all task IDs are in chronological order.\n - Do not provide example URLs for [web-scrape].\n - Do not include the result from the last task in the JSON, that will be added after..\n - The last step is always to provide a final summary report of all tasks.\n - An example of the desired output format is: `; - - // json format is invalid in prompt template. so escape { to {{ and } to }} - const jsonExamples = - '[' + - '{{"id": 1, "task": "https://untapped.vc", "tool": "web-scrape", "dependent_task_id": null, "status": "incomplete", "result": null, "result_summary": null}},' + - '{{"id": 2, "task": "Analyze the contents of...", "tool": "text-completion", "dependent_task_id": 1, "status": "incomplete", "result": null, "result_summary": null}},' + - '{{"id": 3, "task": "Untapped Capital", "tool": "web-search", "dependent_task_id": null, "status": "incomplete", "result": null, "result_summary": null}}' + - '].'; - - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate( - 'You are a task management AI tasked with cleaning the formatting of and reprioritizing. The response will return only JSON.', - ), - HumanMessagePromptTemplate.fromTemplate( - taskManagementTemplate + jsonExamples, - ), - ]); - - prompt.inputVariables = [ - 'minified_task_list', - 'objective', - 'result', - 'websearch_var', - 'language', - ]; - - return new TaskManagementChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babybeeagi/chains/taskOverview.ts b/src/agents/babybeeagi/chains/taskOverview.ts deleted file mode 100644 index f94993ea..00000000 --- a/src/agents/babybeeagi/chains/taskOverview.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { PromptTemplate } from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskOverviewChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskOverviewTemplate = - `Here is the current session summary:\n{session_summary}` + - ` to create new tasks with the following objective: {objective},` + - ` The last completed task is task {last_task_id}.` + - ` Please update the session summary with the information of the last task:` + - ` {completed_tasks_text}` + - ` Updated session summary, which should describe all tasks in chronological order:`; - const prompt = new PromptTemplate({ - template: taskOverviewTemplate, - inputVariables: [ - 'objective', - 'session_summary', - 'last_task_id', - 'completed_tasks_text', - ], - }); - return new TaskOverviewChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babybeeagi/chains/taskSummarize.ts b/src/agents/babybeeagi/chains/taskSummarize.ts deleted file mode 100644 index 4e761451..00000000 --- a/src/agents/babybeeagi/chains/taskSummarize.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PromptTemplate } from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskSummarizeChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskSummarizeTemplate = - 'Please summarize the following text. The Summary must be answered in {language}. If specific figures are available, they must be included.:\n{text}\nSummary:'; - const prompt = new PromptTemplate({ - template: taskSummarizeTemplate, - inputVariables: ['text', 'language'], - }); - return new TaskSummarizeChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babybeeagi/service.ts b/src/agents/babybeeagi/service.ts deleted file mode 100644 index a8117973..00000000 --- a/src/agents/babybeeagi/service.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { TaskSummarizeChain } from './chains/taskSummarize'; -import { TaskOverviewChain } from './chains/taskOverview'; -import { TaskManagementChain } from './chains/taskManagement'; -import { AgentTask } from './agent'; -import { OpenAI, OpenAIChat } from 'langchain/llms/openai'; -import { stringifyTasks } from '@/utils/task'; - -export const summarizerAgent = async ( - text: string, - language: string, - openAIApiKey?: string, -) => { - const model = new OpenAI({ - openAIApiKey, - modelName: 'gpt-3.5-turbo-0613', - temperature: 0.5, - maxTokens: 100, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const chain = TaskSummarizeChain.fromLLM({ llm: model }); - const response = await chain.call({ - text, - language, - }); - return response.text; -}; - -export const overviewAgent = async ( - objective: string, - sessionSummary: string, - lastTaskId: number, - completedTasksText: string, - openAIApiKey?: string, -) => { - const model = new OpenAI({ - openAIApiKey, - modelName: 'gpt-3.5-turbo-0613', - temperature: 0.5, - maxTokens: 200, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const chain = TaskOverviewChain.fromLLM({ llm: model }); - const response = await chain.call({ - objective, - session_summary: sessionSummary, - last_task_id: lastTaskId, - completed_tasks_text: completedTasksText, - }); - return response.text; -}; - -export const taskManagementAgent = async ( - minifiedTaskList: AgentTask[], - objective: string, - result: string, - websearchVar: string, - modelName: string, - language: string, - openAIApiKey?: string, -) => { - const model = new OpenAIChat({ - openAIApiKey, - modelName, - temperature: 0.2, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const chain = TaskManagementChain.fromLLM({ llm: model }); - - const response = await chain.call({ - // minified_task_list: stringifyTasks(minifiedTaskList), - minified_task_list: stringifyTasks([]), - objective, - result, - websearch_var: websearchVar, - language, - }); - return response.text; -}; diff --git a/src/agents/babybeeagi/tools/textCompletion.ts b/src/agents/babybeeagi/tools/textCompletion.ts deleted file mode 100644 index 9f7e6295..00000000 --- a/src/agents/babybeeagi/tools/textCompletion.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { OpenAIChat } from 'langchain/llms/openai'; - -export const textCompletion = async ( - text: string, - modelName: string, - openAIApiKey?: string, -) => { - const tool = new OpenAIChat({ - openAIApiKey: openAIApiKey, - modelName: modelName, - temperature: 0.5, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const result = await tool.call(text); - return result; -}; diff --git a/src/agents/babycatagi/agent.ts b/src/agents/babycatagi/agent.ts deleted file mode 100644 index 28c46733..00000000 --- a/src/agents/babycatagi/agent.ts +++ /dev/null @@ -1,556 +0,0 @@ -import { AgentStatus, AgentTask, Message } from '@/types'; -import { textCompletion } from './tools/textCompletion'; -import { getToolIcon, setupMessage } from '@/utils/message'; -import axios from 'axios'; -import { parseTasks } from '@/utils/task'; -import { getUserApiKey } from '@/utils/settings'; -import { extractRelevantInfoAgent, taskCreationAgent } from './service'; -import { simplifySearchResults } from '../common/tools/webSearch'; - -export class BabyCatAGI { - objective: string; - modelName: string; - taskList: AgentTask[] = []; - sessionSummary: string = ''; - taskIdCounter: number = 1; - isRunning: boolean; - verbose: boolean; - language: string = 'en'; - messageCallback: (message: Message) => void; - statusCallback: (status: AgentStatus) => void; - cancelCallback: () => void; - abortController?: AbortController; - chunk: string = ''; - - constructor( - objective: string, - modelName: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancel: () => void, - language: string = 'en', - verbose: boolean = false, - ) { - this.objective = objective; - this.taskList = []; - this.verbose = verbose; - this.modelName = modelName; - this.language = language; - this.cancelCallback = cancel; - this.messageCallback = messageCallback; - this.statusCallback = statusCallback; - this.isRunning = false; - } - - // print logs - printBabyCat() { - if (!this.verbose) return; - console.log( - '%c*****BABY CAT AGI*****\n\n%c%s', - 'color:orange', - '', - 'Baby Cat AGI is running...', - ); - } - - printObjective() { - this.messageCallback(setupMessage('objective', this.objective)); - if (!this.verbose) return; - console.log( - '%c*****OBJECTIVE*****\n\n%c%s', - 'color:blue', - '', - this.objective, - ); - } - - printTaskList() { - if (!this.isRunning) return; - - let message = - '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - this.taskList.forEach((task) => { - const dependentTask = task.dependentTaskIds - ? `${task.dependentTaskIds.join(', ')}` - : '-'; - const status = task.status === 'complete' ? '✅' : '⬜️'; - message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( - task.tool, - )} | ${dependentTask} |\n`; - }); - - this.messageCallback(setupMessage('task-list', message)); - - if (!this.verbose) return; - console.log('%c*****TASK LIST*****\n\n%c', 'color:fuchsia', ''); - console.log(message); - } - - printSessionSummary() { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('session-summary', this.sessionSummary)); - - if (!this.verbose) return; - console.log('%c*****SESSION SUMMARY*****\n\n%c', 'color:orange', ''); - console.log(this.sessionSummary); - } - - printNextTask(task: AgentTask) { - if (!this.isRunning) return; - - const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ - task.tool - }]**`; - this.messageCallback(setupMessage('next-task', nextTask)); - - if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n\n%s', 'color:green', '', nextTask); - } - - printTaskOutput(output: string, task: AgentTask) { - if (!this.isRunning) return; - - if (task.tool !== 'text-completion') { - // code block for non-text-completion tools - output = '```\n' + output + '\n```'; - } - this.messageCallback(setupMessage('task-output', output, task?.tool)); - - if (!this.verbose) return; - console.log('%c*****TASK RESULT*****\n%c%s', 'color:purple', '', output); - } - - printDone() { - if (!this.isRunning) return; - - this.messageCallback( - setupMessage( - 'done', - `Number of tasks completed: ${this.taskIdCounter.toString()}`, - ), - ); - - if (!this.verbose) return; - console.log('%c*****DONE*****%c', 'color:blue', ''); - } - - printAllTaskCompleted() { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('complete', 'All Tasks Completed')); - if (!this.verbose) return; - console.log('%c*****ALL TASK COMPLETED*****%c', 'color:blue', ''); - } - - // Tools functions - async textCompletionTool(prompt: string) { - this.abortController = new AbortController(); - this.statusCallback({ type: 'executing' }); - - this.chunk = '```markdown\n'; - const callback = (token: string) => { - this.chunk += token; - this.statusCallback({ type: 'executing-stream', message: this.chunk }); - }; - - if (getUserApiKey()) { - this.statusCallback({ type: 'executing-stream' }); - - return await textCompletion( - prompt, - 'gpt-3.5-turbo-0613', - this.abortController?.signal, - getUserApiKey(), - callback, - ); - } - - const response = await axios - .post( - '/api/tools/completion', - { - prompt, - apiKey: getUserApiKey(), - callback, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async webSearchTool(query: string) { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data.response; - } - - async webScrapeTool(url: string) { - const response = await axios - .post( - '/api/tools/scrape', - { - url, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async callbackSearchStatus(message: string) { - this.statusCallback({ - type: 'executing-stream', - message: '```markdown\n' + message + '\n```', - }); - } - - async webSearchToolWithAgent(task: AgentTask) { - // get search results - const searchResults = await this.webSearchTool(task.task); - - if (!this.isRunning) return; - - // simplify search results - const sinmplifiedSearchResults = simplifySearchResults(searchResults); - if (this.verbose) { - console.log('Completed search. Now scraping results.\n'); - } - let statusMessage = 'Completed search. Now scraping results.\n'; - this.callbackSearchStatus(statusMessage); - - if (!this.isRunning) return; - - let result = ''; - let index = 1; - // Loop through search results - for (const searchResult of sinmplifiedSearchResults) { - if (!this.isRunning) break; - if (index >= 5) break; - - // Extract the URL from the search result - const url = searchResult.link; - if (this.verbose) { - console.log('Scraping: %s ...', url); - } - statusMessage += `${index}. Scraping: ${url} ...\n`; - this.callbackSearchStatus(statusMessage); - - const content = await this.webScrapeTool(url); - if (!content) continue; - - if (this.verbose) { - console.log( - 'Scrape completed. Length:%s. Now extracting relevant info... \n', - content.length, - ); - } - statusMessage += ` - Scrape completed. Length:${content.length}. Now extracting relevant info... \n`; - this.callbackSearchStatus(statusMessage); - - if (!this.isRunning) break; - - // extract relevant text from the scraped text - const info = await this.extractRelevantInfo(content.slice(0, 5000), task); - // combine search result and scraped text - result += `${info}. `; - - if (this.verbose) { - console.log('Content: %s ...\n', result.slice(0, 100)); - } - statusMessage += ` - Content: ${result.slice(0, 100)} ...\n`; - this.callbackSearchStatus(statusMessage); - - index++; - } - - if (!this.isRunning) return; - - // callback to search logs - this.messageCallback( - setupMessage('search-logs', '```markdown\n' + statusMessage + '\n```'), - ); - - return result; - } - - async extractRelevantInfo(largeString: string, task: AgentTask) { - const chunkSize = 2800; //3000; - const overlap = 500; - let notes = ''; - - for (let i = 0; i < largeString.length; i += chunkSize - overlap) { - if (!this.isRunning) break; - - const chunk = largeString.slice(i, i + chunkSize); - if (getUserApiKey()) { - const response = await extractRelevantInfoAgent( - this.objective, - task.task, - chunk, - notes, - getUserApiKey(), - ); - notes += response; - } else { - // Server side call - const response = await axios - .post( - '/api/tools/extract', - { - objective: this.objective, - task: task.task, - chunk, - notes, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - notes += response?.data?.response; - } - } - return notes; - } - - // Task list functions - async addTask(task: AgentTask) { - this.taskList.push(task); - } - - async getTaskById(id: number) { - return this.taskList.find((task) => task.id === id); - } - - async getCompletedTasks() { - return this.taskList.filter((task) => task.status === 'complete'); - } - - // Agent functions - async taskCreationAgent() { - this.abortController = new AbortController(); - const websearchVar = process.env.SERP_API_KEY ? '[web-search] ' : ''; // if search api key is not set, don't add [web-search] to the task description - - this.chunk = '```json\n'; - const callback = (token: string) => { - this.chunk += token; - this.statusCallback({ type: 'creating-stream', message: this.chunk }); - }; - - let result = ''; - if (getUserApiKey()) { - this.statusCallback({ type: 'creating-stream' }); - - result = await taskCreationAgent( - this.objective, - websearchVar, - this.modelName, - this.language, - this.abortController?.signal, - getUserApiKey(), - callback, - ); - } else { - // Server side call - const response = await axios - .post( - '/api/agents/create', - { - objective: this.objective, - websearch_var: websearchVar, - model_name: this.modelName, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - result = response?.data?.response; - } - - if (!result) { - return []; - } - - let taskList = this.taskList; - // update task list - try { - taskList = parseTasks(result); - } catch (error) { - console.error(error); - // TODO: handle error - } - - return taskList; - } - - async executeTask(task: AgentTask, taskList: AgentTask[], objective: string) { - // Check if dependent task id is not empty - if (task.dependentTaskIds) { - let allDependentTasksCompleted = true; - for (const id of task.dependentTaskIds) { - const dependentTask = await this.getTaskById(id); - if (dependentTask?.status !== 'complete') { - allDependentTasksCompleted = false; - break; - } - } - } - - if (!this.isRunning) return; - - // Execute the task - this.statusCallback({ type: 'executing' }); - this.printNextTask(task); - - let taskPrompt = `Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. Your objective: ${objective}. Your task: ${task.task}`; - if (task.dependentTaskIds) { - let dependentTasksOutput = ''; - for (const id of task.dependentTaskIds) { - const dependentTasks = await this.getTaskById(id); - const dependentTaskOutput = dependentTasks?.output?.slice(0, 2000); - dependentTasksOutput += dependentTaskOutput; - } - taskPrompt += `Your dependent task output: ${dependentTasksOutput}\n OUTPUT:`; - } - - if (!this.isRunning) return; - - // Use the tool to complete the task - let taskOutput = ''; - switch (task.tool) { - case 'text-completion': - taskOutput = await this.textCompletionTool(taskPrompt); - break; - case 'web-search': - taskOutput = (await this.webSearchToolWithAgent(task)) ?? ''; - break; - default: - break; - } - - // Find the task index in the task list - const taskIndex = taskList.findIndex((t) => t.id === task.id); - - // Matk task as complete and update the output - taskList[taskIndex].status = 'complete'; - taskList[taskIndex].output = taskOutput; - - // print the task output - this.printTaskOutput(taskOutput, task); - - this.sessionSummary += `\n\nTask: ${task.id} - ${task.task}\n${taskOutput}`; - } - - async stop() { - this.isRunning = false; - this.cancelCallback(); - this.abortController?.abort(); - } - - async start() { - this.isRunning = true; - this.printBabyCat(); - this.printObjective(); - - // Initialize the task id counter - this.taskIdCounter = 0; - - // Run the task creation agent to create the initial tasks - this.taskList = (await this.taskCreationAgent()) ?? []; - - this.printTaskList(); - - if (!this.isRunning) return; - - // Start the loop - await this.loop(); - - if (!this.isRunning) { - this.statusCallback({ type: 'finished' }); - return; - } - - // Objective completed - this.printSessionSummary(); - this.printAllTaskCompleted(); - this.statusCallback({ type: 'finished' }); - this.cancelCallback(); - this.isRunning = false; - } - - async loop() { - // Continue the loop while there are incomplete tasks - while ( - this.taskList.some((task) => task.status === 'incomplete') && - this.isRunning - ) { - this.statusCallback({ type: 'preparing' }); - // Filter out incomplete tasks - const incompleteTasks = this.taskList.filter( - (task) => task.status === 'incomplete', - ); - - // Pull the first task - const task = incompleteTasks[0]; - - if (!this.isRunning) break; - - await this.executeTask(task, this.taskList, this.objective); - - this.taskIdCounter += 1; - this.statusCallback({ type: 'closing' }); - // Print task list - this.printTaskList(); - this.printDone(); - } - } -} diff --git a/src/agents/babycatagi/chains/relevantInfoExtraction.ts b/src/agents/babycatagi/chains/relevantInfoExtraction.ts deleted file mode 100644 index 9c06fd7c..00000000 --- a/src/agents/babycatagi/chains/relevantInfoExtraction.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class relevantInfoExtractionChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; - const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes: - `; - - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate(systemTemplate), - HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), - ]); - - prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; - - return new relevantInfoExtractionChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babycatagi/chains/taskCreation.ts b/src/agents/babycatagi/chains/taskCreation.ts deleted file mode 100644 index 6df76f63..00000000 --- a/src/agents/babycatagi/chains/taskCreation.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskCreationChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskCreationTemplate = ` - You are a task creation AI tasked with creating a list of tasks as a JSON array, considering the ultimate objective of your team: {objective}. - Create new tasks based on the objective. Limit tasks types to those that can be completed with the available tools listed below. Task description should be detailed. - Task description must be answered in {language}. - Current tool option is [text-completion] {websearch_var} and only." # web-search is added automatically if SERPAPI exists - For tasks using [web-search], provide the search query, and only the search query to use (eg. not 'research waterproof shoes, but 'waterproof shoes') - dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. - Make sure all task IDs are in chronological order.\n - The last step is always to provide a final summary report including tasks executed and summary of knowledge acquired.\n - Do not create any summarizing steps outside of the last step..\n - An example of the desired output format is: - [{{\"id\": 1, \"task\": \"https://untapped.vc\", \"tool\": \"web-scrape\", \"dependent_task_ids\": [], \"status\": \"incomplete\", \"output\": null}}, - {{\"id\": 2, \"task\": \"Consider additional insights that can be reasoned from the results of...\", \"tool\": \"text-completion\", \"dependent_task_ids\": [1], \"status\": \"incomplete\", \"output\": null}}, - {{\"id\": 3, \"task\": \"Untapped Capital\", \"tool\": \"web-search\", \"dependent_task_ids\": [], \"status\": \"incomplete\", \"output\": null}}].\n - JSON TASK LIST= - `; - - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate('You are a task creation AI.'), - HumanMessagePromptTemplate.fromTemplate(taskCreationTemplate), - ]); - - prompt.inputVariables = ['objective', 'websearch_var', 'language']; - - return new TaskCreationChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babycatagi/service.ts b/src/agents/babycatagi/service.ts deleted file mode 100644 index d035f22a..00000000 --- a/src/agents/babycatagi/service.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { OpenAI, OpenAIChat } from 'langchain/llms/openai'; -import { relevantInfoExtractionChain } from './chains/relevantInfoExtraction'; -import { TaskCreationChain } from './chains/taskCreation'; - -export const taskCreationAgent = async ( - objective: string, - websearchVar: string, - modelName: string, - language: string, - signal?: AbortSignal, - openAIApiKey?: string, - callback?: (token: string) => void, -) => { - const model = new OpenAIChat( - { - openAIApiKey, - modelName, - temperature: 0, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - if (callback) { - callback(token); - } - }, - }, - ], - }, - { baseOptions: { signal: signal } }, - ); - const chain = TaskCreationChain.fromLLM({ llm: model }); - - try { - const response = await chain.call({ - objective, - websearch_var: websearchVar, - language, - }); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return error; - } -}; - -export const extractRelevantInfoAgent = async ( - objective: string, - task: string, - notes: string, - chunk: string, - openAIApiKey?: string, -) => { - const model = new OpenAI({ - openAIApiKey, - modelName: 'gpt-3.5-turbo-0613', - temperature: 0.7, - maxTokens: 800, - n: 1, - stop: ['###'], - }); - const chain = relevantInfoExtractionChain.fromLLM({ llm: model }); - const response = await chain.call({ - objective, - task: task, - notes, - chunk, - }); - return response.text; -}; diff --git a/src/agents/babycatagi/tools/textCompletion.ts b/src/agents/babycatagi/tools/textCompletion.ts deleted file mode 100644 index ad2dff0a..00000000 --- a/src/agents/babycatagi/tools/textCompletion.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { OpenAI } from 'langchain/llms/openai'; - -export const textCompletion = async ( - text: string, - modelName: string, - abortSignal?: AbortSignal, - openAIApiKey?: string, - callback?: (token: string) => void, -) => { - const tool = new OpenAI( - { - openAIApiKey: openAIApiKey, - modelName: modelName, - temperature: 0.2, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - if (callback) { - callback(token); - } - }, - }, - ], - }, - { baseOptions: { signal: abortSignal } }, - ); - - try { - const response = await tool.call(text); - return response; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return error; - } -}; diff --git a/src/agents/babydeeragi/agents/relevantInfoExtraction/agent.ts b/src/agents/babydeeragi/agents/relevantInfoExtraction/agent.ts deleted file mode 100644 index 8945119e..00000000 --- a/src/agents/babydeeragi/agents/relevantInfoExtraction/agent.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { getUserApiKey } from '@/utils/settings'; -import { OpenAIChat } from 'langchain/llms/openai'; -import { relevantInfoExtractionPrompt } from './prompt'; -import { LLMChain } from 'langchain/chains'; -import axios from 'axios'; - -// TODO: Only client-side requests are allowed. -// To use the environment variable API key, the request must be implemented from the server side. - -export const relevantInfoExtractionAgent = async ( - objective: string, - task: string, - notes: string, - chunk: string, - signal?: AbortSignal, -) => { - const openAIApiKey = getUserApiKey(); - const modelName = 'gpt-3.5-turbo-16k-0613'; // use a fixed model - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - if (!openAIApiKey) { - // server side request - const response = await axios - .post( - '/api/deer/extract', - { - objective, - task, - notes, - chunk, - model_name: modelName, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - return response?.data?.response; - } - - const prompt = relevantInfoExtractionPrompt(); - const llm = new OpenAIChat( - { - openAIApiKey, - modelName, - temperature: 0.7, - maxTokens: 800, - topP: 1, - stop: ['###'], - }, - { baseOptions: { signal: signal } }, - ); - const chain = new LLMChain({ llm: llm, prompt }); - try { - const response = await chain.call({ - objective, - task, - notes, - chunk, - }); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return 'Failed to extract relevant information.'; - } -}; diff --git a/src/agents/babydeeragi/agents/relevantInfoExtraction/prompt.ts b/src/agents/babydeeragi/agents/relevantInfoExtraction/prompt.ts deleted file mode 100644 index 59e8dade..00000000 --- a/src/agents/babydeeragi/agents/relevantInfoExtraction/prompt.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; - -export const relevantInfoExtractionPrompt = () => { - const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; - const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes:`; - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate(systemTemplate), - HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), - ]); - - prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; - - return prompt; -}; diff --git a/src/agents/babydeeragi/agents/taskCreation/agent.ts b/src/agents/babydeeragi/agents/taskCreation/agent.ts deleted file mode 100644 index 9c829b12..00000000 --- a/src/agents/babydeeragi/agents/taskCreation/agent.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { OpenAIChat } from 'langchain/llms/openai'; -import { taskCreationPrompt } from './prompt'; -import { LLMChain } from 'langchain/chains'; -import { AgentTask, Message } from '@/types'; -import { getUserApiKey } from '@/utils/settings'; -import { parseTasks } from '@/utils/task'; -import { translate } from '@/utils/translate'; -import axios from 'axios'; - -// TODO: Only client-side requests are allowed. -// To use the environment variable API key, the request must be implemented from the server side. - -export const taskCreationAgent = async ( - objective: string, - modelName: string, - language: string, - signal?: AbortSignal, - messageCallback?: (message: Message) => void, -) => { - let chunk = '```json\n'; - const websearchVar = - process.env.SERP_API_KEY || process.env.GOOGLE_SEARCH_API_KEY - ? '[web-search] ' - : ''; // if search api key is not set, don't add [web-search] to the task description - - const userinputVar = '[user-input] '; - const prompt = taskCreationPrompt(); - const openAIApiKey = getUserApiKey(); - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - let result = ''; - if (getUserApiKey()) { - // client side request - const model = new OpenAIChat( - { - openAIApiKey, - modelName, - temperature: 0, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - maxRetries: 3, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - const message: Message = { - type: 'task-execute', - title: translate('CREATING', 'message'), - text: chunk, - icon: '📝', - id: 0, - }; - messageCallback?.(message); - }, - }, - ], - }, - { baseOptions: { signal: signal } }, - ); - - const chain = new LLMChain({ llm: model, prompt }); - try { - const response = await chain.call({ - objective, - websearch_var: websearchVar, - user_input_var: userinputVar, - language, - }); - result = response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log(error); - return null; - } - } else { - // server side request - const response = await axios - .post( - '/api/deer/create', - { - objective: objective, - websearch_var: websearchVar, - user_input_var: userinputVar, - model_name: modelName, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - result = response?.data?.response; - } - - if (!result) { - return null; - } - - let taskList: AgentTask[] = []; - // update task list - try { - taskList = parseTasks(result); - } catch (error) { - console.log(error); - // TODO: handle error - return null; - } - - return taskList; -}; diff --git a/src/agents/babydeeragi/agents/taskCreation/prompt.ts b/src/agents/babydeeragi/agents/taskCreation/prompt.ts deleted file mode 100644 index 437630ed..00000000 --- a/src/agents/babydeeragi/agents/taskCreation/prompt.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { PromptTemplate } from 'langchain/prompts'; - -export const taskCreationPrompt = () => { - const prompt = new PromptTemplate({ - inputVariables: [ - 'objective', - 'websearch_var', - 'user_input_var', - 'language', - ], - template: ` - You are an expert task creation AI tasked with creating a list of tasks as a JSON array, considering the ultimate objective of your team: {objective}. - Create new tasks based on the objective. Limit tasks types to those that can be completed with the available tools listed below. Task description should be detailed. - Task description must be answered in {language}. - Current tool options are [text-completion] {websearch_var} {user_input_var}. - For tasks using [web-search], provide the search query, and only the search query to use (eg. not 'research waterproof shoes, but 'waterproof shoes'). Result will be a summary of relevant information from the first few articles. - When requiring multiple searches, use the [web-search] multiple times. This tool will use the dependent task result to generate the search query if necessary. - Use [user-input] sparingly and only if you need to ask a question to the user who set up the objective. The task description should be the question you want to ask the user.') - dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. - Make sure all task IDs are in chronological order.\n - EXAMPLE OBJECTIVE=Look up AI news from today (May 27, 2023) and write a poem. - TASK LIST=[ - {{\"id\":1,\"task\":\"AI news today\",\"tool\":\"web-search\",\"dependent_task_ids\":[],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}}, - {{\"id\":2,\"task\":\"Summarize a news article\",\"tool\":\"text-completion\",\"dependent_task_ids\":[1],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}}, - {{\"id\":3,\"task\":\"Pick up important news\",\"tool\":\"text-completion\",\"dependent_task_ids\":[2],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}}, - {{\"id\":4,\"task\":\"Final summary report\",\"tool\":\"text-completion\",\"dependent_task_ids\":[1,2,3],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}} - ] - OBJECTIVE={objective} - TASK LIST= - `, - }); - - return prompt; -}; diff --git a/src/agents/babydeeragi/executer.ts b/src/agents/babydeeragi/executer.ts deleted file mode 100644 index 85757ca6..00000000 --- a/src/agents/babydeeragi/executer.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { AgentExecuter } from '../base/AgentExecuter'; -import { taskCreationAgent } from './agents/taskCreation/agent'; -import { AgentTask } from '@/types'; -import { getTaskById } from '@/utils/task'; -import { webBrowsing } from './tools/webBrowsing'; -import { textCompletionToolPrompt } from './prompt'; -import { textCompletionTool } from '../common/tools/textCompletionTool'; -import { setupMessage } from '@/utils/message'; -import { toast } from 'sonner'; -import { translate } from '@/utils/translate'; - -export class BabyDeerAGI extends AgentExecuter { - sessionSummary = `OBJECTIVE: ${this.objective}\n\n`; - userInputResolvers: { [id: number]: (message: string) => void } = {}; - userInputPromises: { [id: number]: Promise } = {}; - - // Create task list by agent - async taskCreation() { - this.statusCallback({ type: 'creating' }); - this.abortController = new AbortController(); - const taskList = await taskCreationAgent( - this.objective, - this.modelName, - this.language, - this.abortController?.signal, - this.messageCallback, - ); - - if (!taskList) { - toast.error(translate('ERROR_CREATING_TASKS', 'message')); - this.stop(); - return; - } - - this.taskList = taskList; - this.printer.printTaskList(this.taskList, 0); - } - - async taskOutputWithTool(task: AgentTask) { - let taskOutput = ''; - switch (task.tool) { - case 'text-completion': - this.abortController = new AbortController(); - let dependentTasksOutput = ''; - if (task.dependentTaskIds) { - for (const id of task.dependentTaskIds) { - const dependentTask = getTaskById(this.taskList, id); - const dependentTaskOutput = dependentTask?.output; - dependentTasksOutput += `${dependentTask?.task}: ${dependentTaskOutput}\n`; - } - } - const prompt = textCompletionToolPrompt( - this.objective, - this.language, - task.task, - dependentTasksOutput.slice(0, 14000), - ); - - taskOutput = await textCompletionTool( - prompt, - this.modelName, - this.abortController?.signal, - task.id, - this.messageCallback, - ); - break; - case 'web-search': - let dependentOutput = ''; - if (task.dependentTaskIds) { - for (const dependentTaskId of task.dependentTaskIds) { - const dependentTask = getTaskById(this.taskList, dependentTaskId); - if (!dependentTask) continue; - const dependentTaskOutput = dependentTask.output; - dependentOutput += `${dependentTask.task}: ${dependentTaskOutput}\n`; - } - } - taskOutput = - (await webBrowsing( - this.objective, - task, - dependentOutput, - this.messageCallback, - this.statusCallback, - this.isRunningRef, - this.verbose, - this.modelName, - this.language, - this.abortController?.signal, - )) ?? ''; - break; - case 'user-input': - taskOutput = await this.getUserInput(task); - break; - default: - break; - } - return taskOutput; - } - - async executeTask(task: AgentTask) { - if (!this.isRunningRef.current) return; - - // Find the task index in the task list - const taskIndex = this.taskList.findIndex((t) => t.id === task.id); - - // Execute the task - this.taskList[taskIndex].status = 'running'; - this.currentStatusCallback(); - // this.printer.printNextTask(task); - this.printer.printTaskExecute(task); - - let taskOutput = await this.taskOutputWithTool(task); - - if (!this.isRunningRef.current) return; - - // print the task output - this.printer.printTaskOutput(taskOutput, task); - - if (!this.isRunningRef.current) return; - - // Update the task status - this.taskList[taskIndex].output = taskOutput; - this.taskList[taskIndex].status = 'complete'; - - this.currentStatusCallback(); - } - - // Override AgentExecuter - async prepare() { - super.prepare(); - this.userInputPromises = {}; - this.userInputResolvers = {}; - // 1. Create task list - await this.taskCreation(); - } - - async loop() { - // Continue the loop while there are incomplete tasks - while ( - this.isRunningRef.current && - this.taskList.some((task) => task.status === 'incomplete') - ) { - if (!this.isRunningRef.current) { - break; - } - - this.statusCallback({ type: 'preparing' }); - - const incompleteTasks = this.taskList.filter( - (task) => task.status === 'incomplete', - ); - // Filter tasks that have all their dependencies completed - const MaxExecutableTasks = 5; - const executableTasks = incompleteTasks - .filter((task) => { - if (!task.dependentTaskIds) return true; - return task.dependentTaskIds.every((id) => { - const dependentTask = getTaskById(this.taskList, id); - return dependentTask?.status === 'complete'; - }); - }) - .slice(0, MaxExecutableTasks); - - // Execute all executable tasks in parallel - await Promise.all(executableTasks.map((task) => this.executeTask(task))); - } - } - - async finishup() { - if (!this.isRunningRef.current) { - this.statusCallback({ type: 'finished' }); - return; - } - const id = this.taskList.length + 1; - this.printer.printTaskList(this.taskList, id); - - super.finishup(); - } - - async userInput(taskId: number, message: string): Promise { - if (this.userInputResolvers[taskId]) { - this.userInputResolvers[taskId](message); - delete this.userInputResolvers[taskId]; - delete this.userInputPromises[taskId]; - } - } - - getUserInput(task: AgentTask) { - this.messageCallback( - setupMessage('user-input', task.task, task.tool, undefined, task.id), - ); - toast.message(translate('USER_INPUT_WAITING', 'message')); - this.statusCallback({ type: 'user-input' }); - this.userInputPromises[task.id] = new Promise((resolve) => { - this.userInputResolvers[task.id] = resolve; - }); - return this.userInputPromises[task.id]; - } - - currentStatusCallback = () => { - const ids = this.taskList - .filter((t) => t.status === 'running') - .map((t) => t.id); - this.statusCallback({ - type: 'executing', - message: `(👉 ${ids.join(', ')} / ${this.taskList.length})`, - }); - }; -} diff --git a/src/agents/babydeeragi/tools/largeTextExtract.ts b/src/agents/babydeeragi/tools/largeTextExtract.ts deleted file mode 100644 index fe59e7e9..00000000 --- a/src/agents/babydeeragi/tools/largeTextExtract.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AgentStatus, AgentTask } from '@/types'; -import { getUserApiKey } from '@/utils/settings'; -import { relevantInfoExtractionAgent } from '../agents/relevantInfoExtraction/agent'; -import axios from 'axios'; -import React from 'react'; - -export const largeTextExtract = async ( - objective: string, - largeString: string, - task: AgentTask, - isRunningRef?: React.MutableRefObject, - callback?: (message: string) => void, - signal?: AbortSignal, -) => { - const chunkSize = 15000; - const overlap = 500; - let notes = ''; - - // for status message - const total = Math.ceil(largeString.length / (chunkSize - overlap)); - - for (let i = 0; i < largeString.length; i += chunkSize - overlap) { - if (!isRunningRef?.current) break; - - callback?.(` - chunk ${i / (chunkSize - overlap) + 1} of ${total}\n`); - - const chunk = largeString.slice(i, i + chunkSize); - // Client side call - if (getUserApiKey()) { - const response = await relevantInfoExtractionAgent( - objective, - task.task, - notes, - chunk, - ); - notes += response; - } else { - // Server side call - const response = await axios - .post( - '/api/tools/extract', - { - objective: objective, - task: task.task, - chunk, - notes, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - notes += response?.data?.response; - } - } - return notes; -}; diff --git a/src/agents/babydeeragi/tools/webBrowsing.ts b/src/agents/babydeeragi/tools/webBrowsing.ts deleted file mode 100644 index eff879cd..00000000 --- a/src/agents/babydeeragi/tools/webBrowsing.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { simplifySearchResults } from '@/agents/common/tools/webSearch'; -import { AgentStatus, AgentTask, Message } from '@/types'; -import axios from 'axios'; -import { getTaskById } from '@/utils/task'; -import { analystPrompt, searchQueryPrompt } from '../prompt'; -import { textCompletionTool } from '../../common/tools/textCompletionTool'; -import { largeTextExtract } from './largeTextExtract'; -import { translate } from '@/utils/translate'; - -export const webBrowsing = async ( - objective: string, - task: AgentTask, - dependentTasksOutput: string, - messageCallback: (message: Message) => void, - statusCallback?: (status: AgentStatus) => void, - isRunningRef?: React.MutableRefObject, - verbose: boolean = false, - modelName: string = 'gpt-3.5-turbo', - language: string = 'en', - signal?: AbortSignal, -) => { - const prompt = searchQueryPrompt( - task.task, - dependentTasksOutput.slice(0, 3500), - ); - const searchQuery = await textCompletionTool(prompt, modelName, signal); - - const trimmedQuery = searchQuery.replace(/^"|"$/g, ''); // remove quotes from the search query - - let title = `🔎 Searching: ${trimmedQuery}`; - let message = `Search query: ${trimmedQuery}\n`; - callbackSearchStatus(title, message, task, messageCallback); - const searchResults = await webSearchTool(trimmedQuery, signal); - if (!searchResults) { - return 'Failed to search.'; - } - let statusMessage = message; - - if (!isRunningRef?.current) return; - - const simplifiedSearchResults = simplifySearchResults(searchResults); - title = `📖 Reading content...`; - message = `✅ Completed search. \nNow reading content.\n`; - if (verbose) { - console.log(message); - } - - statusMessage += message; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - if (!isRunningRef.current) return; - - let results = ''; - let index = 1; - let completedCount = 0; - const MaxCompletedCount = 3; - // Loop through search results - for (const searchResult of simplifiedSearchResults) { - if (!isRunningRef.current) return; - if (completedCount >= MaxCompletedCount) break; - - // Extract the URL from the search result - const url = searchResult.link; - let title = `${index}. Reading: ${url} ...`; - - if (verbose) { - console.log(message); - } - statusMessage += `${title}\n`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - const content = (await webScrapeTool(url, signal)) ?? ''; - - title = `${index}. Extracting relevant info...`; - message = ` - Content reading completed. Length:${content.length}. Now extracting relevant info...\n`; - if (verbose) { - console.log(message); - } - - statusMessage += message; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - if (content.length === 0) { - let message = ` - Content too short. Skipped. \n`; - if (verbose) console.log(message); - statusMessage += message; - callbackSearchStatus(undefined, statusMessage, task, messageCallback); - index += 1; - continue; - } - - if (!isRunningRef.current) return; - - // extract relevant text from the scraped text - const callback = (message: string) => { - if (verbose) { - console.log(message); - } - statusMessage = `${statusMessage}${message}`; - title = `${index}. Extracting relevant info... ${message}`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - }; - - statusMessage += ` - Extracting relevant information\n`; - title = `${index}. Extracting relevant info...`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - const info = await largeTextExtract( - objective, - content.slice(0, 20000), - task, - isRunningRef, - callback, - signal, - ); - - message = ` - Relevant info: ${info - .slice(0, 100) - .replace(/\r?\n/g, '')} ...\n`; - if (verbose) { - console.log(message); - } - statusMessage += message; - title = `${index}. Relevant info...`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - results += `${info}. `; - index += 1; - completedCount += 1; - } - - if (!isRunningRef.current) return; - - callbackSearchStatus( - 'Analyzing results...', - `${statusMessage}Analyze results...`, - task, - messageCallback, - ); - - const ap = analystPrompt(results, language); - const analyzedResults = await textCompletionTool( - ap, - modelName, - signal, - task.id, - messageCallback, - ); - - // callback to search logs - const msg: Message = { - type: 'search-logs', - text: '```markdown\n' + statusMessage + '\n```', - title: `🔎 ${translate('SEARCH_LOGS', 'message')}`, - id: task.id, - icon: '🌐', - open: false, - }; - messageCallback(msg); - - return analyzedResults; -}; - -const callbackSearchStatus = ( - title: string | undefined, - message: string, - task: AgentTask, - messageCallback: (message: Message) => void, -) => { - messageCallback({ - type: 'search-logs', - title: title ?? translate('SEARCH_LOGS', 'message'), - text: '```markdown\n' + message + '\n```', - id: task.id, - icon: '🌐', - open: false, - }); -}; - -const webSearchTool = async (query: string, signal?: AbortSignal) => { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data.response; -}; - -const webScrapeTool = async (url: string, signal?: AbortSignal) => { - const response = await axios - .post( - '/api/tools/scrape', - { - url, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; -}; diff --git a/src/agents/babyelfagi/example_objectives/example1.json b/src/agents/babyelfagi/example_objectives/example1.json deleted file mode 100644 index 6bcd9b73..00000000 --- a/src/agents/babyelfagi/example_objectives/example1.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "objective": "Create a new skill that writes a poem based on an input.", - "examples": [ - { - "id": 1, - "task": "Read the code in textCompletion.ts using the code_reader skill to understand its structure and concepts.", - "skill": "code_reader", - "icon": "📖", - "dependent_task_ids": [], - "status": "incomplete" - }, - { - "id": 2, - "task": "Write a new skill that uses the concepts from textCompletion.ts to generate a poem based on user input.", - "skill": "text_completion", - "icon": "🤖", - "dependent_task_ids": [1], - "status": "incomplete" - }, - { - "id": 3, - "task": "Save the newly created skill using the skill_saver skill for future use.", - "skill": "skill_saver", - "icon": "💾", - "dependent_task_ids": [2], - "status": "incomplete" - } - ] -} diff --git a/src/agents/babyelfagi/executer.ts b/src/agents/babyelfagi/executer.ts index f1aa069d..cb2482cc 100644 --- a/src/agents/babyelfagi/executer.ts +++ b/src/agents/babyelfagi/executer.ts @@ -1,61 +1,68 @@ -import { AgentStatus, Message, TaskOutputs } from '@/types'; // You need to define these types +import { AgentMessage, TaskOutputs } from '@/types'; // You need to define these types import { AgentExecuter } from '../base/AgentExecuter'; import { SkillRegistry, TaskRegistry } from './registory'; -import { translate } from '@/utils/translate'; +import { v4 as uuidv4 } from 'uuid'; const REFLECTION = false; // If you want to use reflection, set this to true. now support only client side reflection. export class BabyElfAGI extends AgentExecuter { skillRegistry: SkillRegistry; taskRegistry: TaskRegistry; - sessionSummary: string; + sessionSummary: string = ''; constructor( objective: string, modelName: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancelCallback: () => void, + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }, language: string = 'en', verbose: boolean = false, + specifiedSkills: string[] = [], + userApiKey?: string, + signal?: AbortSignal, ) { - super( - objective, - modelName, - messageCallback, - statusCallback, - cancelCallback, - language, - verbose, - ); + super(objective, modelName, handlers, language, verbose, signal); + + signal?.addEventListener('abort', () => { + this.verbose && + console.log('Abort signal received. Stopping execution...'); + }); - this.abortController = new AbortController(); this.skillRegistry = new SkillRegistry( - this.messageCallback, - this.abortController, - this.isRunningRef, - verbose, + this.handlers.handleMessage, + this.verbose, this.language, + specifiedSkills, + userApiKey, + this.signal, + ); + + const useSpecifiedSkills = specifiedSkills.length > 0; + this.taskRegistry = new TaskRegistry( + this.language, + this.verbose, + useSpecifiedSkills, + userApiKey, + this.signal, ); - this.taskRegistry = new TaskRegistry(this.verbose); - this.sessionSummary = ''; } async prepare() { await super.prepare(); const skillDescriptions = this.skillRegistry.getSkillDescriptions(); - this.abortController = new AbortController(); - this.statusCallback({ type: 'creating' }); + const id = uuidv4(); + // Create task list await this.taskRegistry.createTaskList( + id, this.objective, skillDescriptions, - 'gpt-4', // Culletly using GPT-4 - this.messageCallback, - this.abortController, - this.language, + this.modelName, + this.handlers.handleMessage, ); - this.printer.printTaskList(this.taskRegistry.tasks, 0); + this.printer.printTaskList(this.taskRegistry.tasks, id); } async loop() { @@ -64,14 +71,11 @@ export class BabyElfAGI extends AgentExecuter { for (let task of this.taskRegistry.tasks) { taskOutputs[task.id] = { completed: false, output: undefined }; } - // Loop until all tasks are completed - while (!Object.values(taskOutputs).every((task) => task.completed)) { - if (!this.isRunningRef.current) { - break; - } - this.statusCallback({ type: 'preparing' }); - + while ( + !this.signal?.aborted && + !Object.values(taskOutputs).every((task) => task.completed) + ) { // Get the tasks that are ready to be executed const tasks = this.taskRegistry.getTasks(); @@ -106,14 +110,20 @@ export class BabyElfAGI extends AgentExecuter { updates: { status: 'running' }, }); this.printer.printTaskExecute(task); - this.currentStatusCallback(); - const output = await this.taskRegistry.executeTask( - i, - task, - taskOutputs, - this.objective, - this.skillRegistry, - ); + + let output = ''; + try { + output = await this.taskRegistry.executeTask( + i, + task, + taskOutputs, + this.objective, + this.skillRegistry, + ); + } catch (error) { + console.error(error); + return; + } taskOutputs[task.id] = { completed: true, output: output }; this.taskRegistry.updateTasks({ @@ -156,31 +166,24 @@ export class BabyElfAGI extends AgentExecuter { } async finishup() { + if (this.signal?.aborted) return; + const tasks = this.taskRegistry.getTasks(); const lastTask = tasks[tasks.length - 1]; - this.messageCallback({ - type: 'final-result', - text: lastTask.result ?? '', - title: translate('FINAL_TASK_RESULT', 'message'), - icon: '✍️', - id: 9999, + this.handlers.handleMessage({ + type: 'result', + content: lastTask.result ?? '', + id: uuidv4(), }); - this.messageCallback({ + this.handlers.handleMessage({ type: 'session-summary', - text: this.sessionSummary, - id: 9999, + content: this.sessionSummary, + id: uuidv4(), }); super.finishup(); } - currentStatusCallback = () => { - const tasks = this.taskRegistry.getTasks(); - const ids = tasks.filter((t) => t.status === 'running').map((t) => t.id); - this.statusCallback({ - type: 'executing', - message: `(👉 ${ids.join(', ')} / ${tasks.length})`, - }); - }; + async close() {} } diff --git a/src/agents/babyelfagi/registory/skillRegistry.ts b/src/agents/babyelfagi/registory/skillRegistry.ts index 2813a8f6..a3396ce6 100644 --- a/src/agents/babyelfagi/registory/skillRegistry.ts +++ b/src/agents/babyelfagi/registory/skillRegistry.ts @@ -1,56 +1,67 @@ -import { Message } from '@/types'; +import { AgentMessage } from '@/types'; import { - AirtableSaver, + TextCompletion, + WebLoader, + WebSearch, + YoutubeSearch, CodeReader, CodeReviewer, CodeWriter, DirectoryStructure, SkillSaver, - TextCompletion, - WebLoader, - WebSearch, - YoutubeSearch, + AirtableSaver, } from '../skills'; import { Skill } from '../skills/skill'; -import { getUserApiKey } from '@/utils/settings'; export class SkillRegistry { skillClasses: (typeof Skill)[]; skills: Skill[] = []; apiKeys: { [key: string]: string }; + userApiKey?: string; + signal?: AbortSignal; // for UI - messageCallback: (message: Message) => void; - abortController: AbortController; - isRunningRef?: React.MutableRefObject; + handleMessage: (message: AgentMessage) => Promise; verbose: boolean; language: string = 'en'; constructor( - messageCallback?: (message: Message) => void, - abortController?: AbortController, - isRunningRef?: React.MutableRefObject, + handleMessage: (message: AgentMessage) => Promise, verbose: boolean = false, language: string = 'en', + specifiedSkills: string[] = [], + userApiKey?: string, + signal?: AbortSignal, ) { this.skillClasses = SkillRegistry.getSkillClasses(); this.apiKeys = SkillRegistry.apiKeys; - // - this.messageCallback = messageCallback || (() => {}); - this.abortController = abortController || new AbortController(); - this.isRunningRef = isRunningRef; + this.userApiKey = userApiKey; + this.signal = signal; + this.handleMessage = handleMessage; this.verbose = verbose; this.language = language; + if (this.userApiKey) { + this.apiKeys['openai'] = this.userApiKey; + } + // Load all skills for (let SkillClass of this.skillClasses) { let skill = new SkillClass( this.apiKeys, - this.messageCallback, - this.abortController, - this.isRunningRef, + this.handleMessage, this.verbose, this.language, + this.signal, ); + + // If the skill is specified, load it. + if (specifiedSkills.length > 0) { + if (specifiedSkills.includes(skill.name)) { + this.skills.push(skill); + } + continue; + } + if ( skill.type === 'dev' ? process.env.NODE_ENV === 'development' : true ) { @@ -75,21 +86,21 @@ export class SkillRegistry { const skills: (typeof Skill)[] = [ TextCompletion, WebSearch, + WebLoader, + YoutubeSearch, AirtableSaver, CodeReader, CodeWriter, SkillSaver, DirectoryStructure, - YoutubeSearch, CodeReviewer, - WebLoader, ]; return skills; } static apiKeys = { - openai: getUserApiKey() || process.env.OPENAI_API_KEY || '', - airtable: 'keyXXXXXXXXXXXXXX', // Your Airtable API key here + openai: process.env.OPENAI_API_KEY || '', + airtable: process.env.AIRTABLE_API_KEY || '', }; getSkill(name: string): Skill { diff --git a/src/agents/babyelfagi/registory/taskRegistry.ts b/src/agents/babyelfagi/registory/taskRegistry.ts index 12306e9f..26f9912a 100644 --- a/src/agents/babyelfagi/registory/taskRegistry.ts +++ b/src/agents/babyelfagi/registory/taskRegistry.ts @@ -1,32 +1,44 @@ -import _ from 'lodash'; -import { AgentTask, Message, TaskOutputs } from '@/types'; +import { AgentTask, AgentMessage, TaskOutputs } from '@/types'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { parseTasks } from '@/utils/task'; import { HumanChatMessage, SystemChatMessage } from 'langchain/schema'; -import { getUserApiKey } from '@/utils/settings'; -import { translate } from '@/utils/translate'; import { SkillRegistry } from './skillRegistry'; import { findMostRelevantObjective } from '@/utils/objective'; -import axios from 'axios'; - export class TaskRegistry { tasks: AgentTask[]; verbose: boolean = false; - - constructor(verbose = false) { + language: string = 'en'; + useSpecifiedSkills: boolean = false; + userApiKey?: string; + signal?: AbortSignal; + + constructor( + language = 'en', + verbose = false, + useSpecifiedSkills = false, + userApiKey?: string, + signal?: AbortSignal, + ) { this.tasks = []; this.verbose = verbose; + this.language = language; + this.userApiKey = userApiKey; + this.useSpecifiedSkills = useSpecifiedSkills; + this.signal = signal; } async createTaskList( + id: string, objective: string, skillDescriptions: string, modelName: string = 'gpt-3.5-turbo', - messageCallback?: (message: Message) => void, - abortController?: AbortController, - language: string = 'en', + handleMessage: (message: AgentMessage) => Promise, ): Promise { - const relevantObjective = await findMostRelevantObjective(objective); + const relevantObjective = await findMostRelevantObjective( + objective, + this.userApiKey, + ); + const exapmleObjective = relevantObjective.objective; const exampleTaskList = relevantObjective.examples; const prompt = ` @@ -36,10 +48,10 @@ export class TaskRegistry { RULES: Do not use skills that are not listed. Always include one skill. - Do not create files unless specified in the objective. + Do not create files unless specified in the objective. dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. Make sure all task IDs are in chronological order.### - Output must be answered in ${language}. + Output must be answered in ${this.language}. EXAMPLE OBJECTIVE=${exapmleObjective} TASK LIST=${JSON.stringify(exampleTaskList)} OBJECTIVE=${objective} @@ -47,75 +59,43 @@ export class TaskRegistry { const systemPrompt = 'You are a task creation AI.'; const systemMessage = new SystemChatMessage(systemPrompt); const messages = new HumanChatMessage(prompt); - const openAIApiKey = getUserApiKey(); - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } let result = ''; - if (openAIApiKey) { - let chunk = '```json\n'; - const model = new ChatOpenAI( - { - openAIApiKey, - modelName, - temperature: 0, - maxTokens: 1500, - topP: 1, - verbose: this.verbose, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - const message: Message = { - type: 'task-execute', - title: translate('CREATING', 'message'), - text: chunk, - icon: '📝', - id: 0, - }; - messageCallback?.(message); - }, + const model = new ChatOpenAI( + { + openAIApiKey: this.userApiKey, + modelName: this.useSpecifiedSkills ? modelName : 'gpt-4', + temperature: 0, + maxTokens: 1500, + topP: 1, + verbose: false, // You can set this to true to see the lanchain logs + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + const message: AgentMessage = { + id, + content: token, + type: 'task-list', + style: 'log', + status: 'running', + }; + handleMessage(message); }, - ], - }, - { baseOptions: { signal: abortController?.signal } }, - ); + }, + ], + }, + { baseOptions: { signal: this.signal } }, + ); - try { - const response = await model.call([systemMessage, messages]); - result = response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - console.log('Task creation aborted'); - } - console.log(error); + try { + const response = await model.call([systemMessage, messages]); + result = response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + console.log('Task creation aborted'); } - } else { - // server side request - const response = await axios - .post( - '/api/elf/completion', - { - prompt: prompt, - model_name: modelName, - temperature: 0, - max_tokens: 1500, - }, - { - signal: abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - result = response?.data?.response; + console.log(error); } if (result === undefined) { @@ -137,23 +117,7 @@ export class TaskRegistry { ? task.dependentTaskIds.map((id) => taskOutputs[id].output).join('\n') : ''; - if (skill.executionLocation === 'server') { - // Call the API endpoint if the skill needs to be executed on the server side - const response = await axios.post('/api/execute-skill', { - task: JSON.stringify(task), - dependent_task_outputs: dependentTaskOutputs, - objective, - }); - return response.data.taskOutput; - } else { - // Execute the skill on the client side - let taskOutput = await skill.execute( - task, - dependentTaskOutputs, - objective, - ); - return taskOutput; - } + return await skill.execute(task, dependentTaskOutputs, objective); } getTasks(): AgentTask[] { @@ -181,7 +145,7 @@ export class TaskRegistry { } reorderTasks(): void { - this.tasks = _.sortBy(this.tasks, ['priority', 'task_id']); + this.tasks.sort((a, b) => a.id - b.id); } async reflectOnOutput( @@ -250,7 +214,7 @@ export class TaskRegistry { ); const model = new ChatOpenAI({ - openAIApiKey: getUserApiKey(), + openAIApiKey: this.userApiKey, modelName, temperature: 0.7, maxTokens: 1500, diff --git a/src/agents/babyelfagi/skills/addons/airtableSaver.ts b/src/agents/babyelfagi/skills/addons/airtableSaver.ts index 5a352ed2..da35e550 100644 --- a/src/agents/babyelfagi/skills/addons/airtableSaver.ts +++ b/src/agents/babyelfagi/skills/addons/airtableSaver.ts @@ -1,4 +1,3 @@ -import Airtable from 'airtable'; import { Skill, SkillType } from '../skill'; import { AgentTask } from '@/types'; @@ -17,19 +16,29 @@ export class AirtableSaver extends Skill { async execute( task: AgentTask, - dependentTaskOutputs: any, + dependentTaskOutputs: string, objective: string, ): Promise { if (!this.valid) { return ''; } - const airtable = new Airtable({ apiKey: this.apiKeys['airtable'] }); - const base = airtable.base(this.baseId); const fields = { Notes: dependentTaskOutputs }; // Your fields here + const url = `https://api.airtable.com/v0/${this.baseId}/${this.tableName}`; + const options = { + method: 'POST', + headers: { + Authorization: `Bearer ${this.apiKeys['airtable']}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ fields }), + }; try { - await base(this.tableName).create([{ fields }]); + const response = await fetch(url, options); + if (!response.ok) { + throw new Error('Record creation failed'); + } return 'Record creation successful'; } catch (error: any) { return `Record creation failed: ${error.message}`; diff --git a/src/agents/babyelfagi/skills/addons/youtubeSearch.ts b/src/agents/babyelfagi/skills/addons/youtubeSearch.ts index ceb0a196..ff8a0ecb 100644 --- a/src/agents/babyelfagi/skills/addons/youtubeSearch.ts +++ b/src/agents/babyelfagi/skills/addons/youtubeSearch.ts @@ -1,6 +1,6 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import axios from 'axios'; +import { webSearch } from '../../tools/webSearch'; export class YoutubeSearch extends Skill { name = 'youtube_search'; @@ -15,37 +15,25 @@ export class YoutubeSearch extends Skill { objective: string, ): Promise { const prompt = `Generate query for YouTube search based on the dependent task outputs and the objective. - Dependent tasks output: ${dependentTaskOutputs} - Objective: ${objective} + Dont include "Youtube video". Only include the query. + Dependent tasks output: ${dependentTaskOutputs} + Objective: ${objective} `; const query = await this.generateText(prompt, task); - const searchResults = await this.webSearchTool(`site:youtube.com ${query}`); + const searchResults = await webSearch(`site:youtube.com ${query}`); const youtubeLinks = this.extractYoutubeLinks(searchResults); + const result = JSON.stringify(youtubeLinks, null, 2); - return '```json\n' + JSON.stringify(youtubeLinks, null, 2) + '\n```'; - } - - webSearchTool = async (query: string) => { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: this.abortController.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); + this.callbackMessage({ + taskId: task.id.toString(), + content: '```json\n\n' + result + '\n\n```', + type: task.skill, + style: 'text', + status: 'complete', + }); - return response?.data.response; - }; + return result; + } extractYoutubeLinks = (searchResults: any[]) => { const youtubeLinks = searchResults diff --git a/src/agents/babyelfagi/skills/index.ts b/src/agents/babyelfagi/skills/index.ts index b6154137..07f7b5de 100644 --- a/src/agents/babyelfagi/skills/index.ts +++ b/src/agents/babyelfagi/skills/index.ts @@ -8,5 +8,5 @@ export * from './presets/objectiveSaver'; export * from './presets/codeWriter'; export * from './presets/codeReviewer'; export * from './presets/webLoader'; -export * from './addons/airtableSaver'; export * from './addons/youtubeSearch'; +export * from './addons/airtableSaver'; diff --git a/src/agents/babyelfagi/skills/presets/codeReader.ts b/src/agents/babyelfagi/skills/presets/codeReader.ts index 78de89a3..a88d5399 100644 --- a/src/agents/babyelfagi/skills/presets/codeReader.ts +++ b/src/agents/babyelfagi/skills/presets/codeReader.ts @@ -37,7 +37,9 @@ export class CodeReader extends Skill { try { const response = await fetch( - `/api/local/read-file?filename=${encodeURIComponent(filePath)}`, + `${this.BASE_URL}/api/local/read-file?filename=${encodeURIComponent( + filePath, + )}`, { method: 'GET', }, @@ -47,6 +49,10 @@ export class CodeReader extends Skill { } const fileContent = await response.json(); console.log(`File content:\n${JSON.stringify(fileContent)}`); + this.callbackMessage({ + content: 'Read file successfully.', + status: 'complete', + }); return JSON.stringify(fileContent); } catch (error) { console.error( diff --git a/src/agents/babyelfagi/skills/presets/codeReviewer.ts b/src/agents/babyelfagi/skills/presets/codeReviewer.ts index aae59138..e1ad0c6a 100644 --- a/src/agents/babyelfagi/skills/presets/codeReviewer.ts +++ b/src/agents/babyelfagi/skills/presets/codeReviewer.ts @@ -31,7 +31,9 @@ export class CodeReviewer extends Skill { const prompt = this.generatePrompt(dependentTaskOutputs); try { - return this.generateText(prompt, task, { modelName: MODEL_NAME }); + const result = this.generateText(prompt, task, { modelName: MODEL_NAME }); + this.sendCompletionMessage(); + return result; } catch (error) { console.error('Failed to generate text:', error); throw new Error( diff --git a/src/agents/babyelfagi/skills/presets/codeWriter.ts b/src/agents/babyelfagi/skills/presets/codeWriter.ts index f6ea5e62..4aafec87 100644 --- a/src/agents/babyelfagi/skills/presets/codeWriter.ts +++ b/src/agents/babyelfagi/skills/presets/codeWriter.ts @@ -32,7 +32,7 @@ export class CodeWriter extends Skill { } const prompt = ` - You are a genius AI programmer. + You are a genius AI programmer. Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. Dependent tasks output include reference code. Your objective: ${objective}. @@ -42,11 +42,12 @@ export class CodeWriter extends Skill { `; try { - return await this.generateText(prompt, task, { + const result = await this.generateText(prompt, task, { modelName: MODEL_NAME, temperature: TEMPERATURE, maxTokens: MAX_TOKENS, }); + return result; } catch (error) { console.error('Error generating text:', error); throw error; diff --git a/src/agents/babyelfagi/skills/presets/directoryStructure.ts b/src/agents/babyelfagi/skills/presets/directoryStructure.ts index 229a1fa0..db6acabc 100644 --- a/src/agents/babyelfagi/skills/presets/directoryStructure.ts +++ b/src/agents/babyelfagi/skills/presets/directoryStructure.ts @@ -15,9 +15,12 @@ export class DirectoryStructure extends Skill { dependentTaskOutputs: string, objective: string, ): Promise { - const response = await fetch('/api/local/directory-structure', { - method: 'GET', - }); + const response = await fetch( + `${this.BASE_URL}/api/local/directory-structure`, + { + method: 'GET', + }, + ); if (!response.ok) { throw new Error('Failed to get directory structure'); } diff --git a/src/agents/babyelfagi/skills/presets/objectiveSaver.ts b/src/agents/babyelfagi/skills/presets/objectiveSaver.ts index b75ba66a..126c09f7 100644 --- a/src/agents/babyelfagi/skills/presets/objectiveSaver.ts +++ b/src/agents/babyelfagi/skills/presets/objectiveSaver.ts @@ -26,13 +26,13 @@ export class ObjectiveSaver extends Skill { const examplesPath = `data/example_objectives/`; try { - const response = await fetch('/api/local/write-file', { + const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - filename: `${examplesPath}/${filename}`, + filename: `${this.BASE_URL}/${examplesPath}/${filename}`, content: `${code}`, }), }); diff --git a/src/agents/babyelfagi/skills/presets/skillSaver.ts b/src/agents/babyelfagi/skills/presets/skillSaver.ts index 21d39c4d..e55678e8 100644 --- a/src/agents/babyelfagi/skills/presets/skillSaver.ts +++ b/src/agents/babyelfagi/skills/presets/skillSaver.ts @@ -33,8 +33,8 @@ export class SkillSaver extends Skill { TASK: ${task.task} CODE: ${code} FILE_NAME:`; - const filename = await this.generateText(filePrompt, task, params); - let skillsPath = `src/agents/babyelfagi/skills/addons`; + const filename = await this.generateText(filePrompt, task, params, true); + let skillsPath = `src/agents/elf/skills/addons`; const dirStructure: string[] = await this.getDirectoryStructure(); const skillPaths = dirStructure.filter((path) => path.includes(filename)); @@ -45,7 +45,7 @@ export class SkillSaver extends Skill { } try { - const response = await fetch('/api/local/write-file', { + const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -58,9 +58,18 @@ export class SkillSaver extends Skill { if (!response.ok) { throw new Error('Failed to save file'); } - return `Code saved successfully: ${filename}`; + const message = `Code saved successfully: ${filename}`; + this.callbackMessage({ + content: message, + status: 'complete', + }); + return message; } catch (error) { console.error('Error saving code.', error); + this.callbackMessage({ + content: 'Error saving code.', + status: 'complete', + }); return 'Error saving code.'; } } diff --git a/src/agents/babyelfagi/skills/presets/webLoader.ts b/src/agents/babyelfagi/skills/presets/webLoader.ts index 7913025b..76eed946 100644 --- a/src/agents/babyelfagi/skills/presets/webLoader.ts +++ b/src/agents/babyelfagi/skills/presets/webLoader.ts @@ -1,8 +1,8 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import axios from 'axios'; -import { largeTextExtract } from '@/agents/babydeeragi/tools/largeTextExtract'; -import { translate } from '@/utils/translate'; +import { largeTextExtract } from '@/agents/babyelfagi/tools/utils/largeTextExtract'; +import { webScrape } from '../../tools/webScrape'; +import { v4 as uuidv4 } from 'uuid'; export class WebLoader extends Skill { name = 'web_loader'; @@ -17,28 +17,34 @@ export class WebLoader extends Skill { throw new Error('Invalid inputs'); } - let statusMessage = '- Extracting URLs from the task.\n'; - const callback = (message: string) => { - statusMessage += message; - this.messageCallback({ - type: 'search-logs', - text: '```markdown\n' + statusMessage + '\n```', - title: `🔎 ${translate('SEARCH_LOGS', 'message')}`, - id: task.id, - icon: '🌐', - open: false, + let message = '- Extracting URLs from the task.\n'; + const callback = ( + message: string, + status: 'running' | 'complete' | 'incomplete' = 'running', + ) => { + this.handleMessage({ + id: this.id, + taskId: task.id.toString(), + content: message, + type: this.name, + style: 'log', + status, }); }; const urlString = await this.extractUrlsFromTask(task, callback); const urls = urlString.split(',').map((url) => url.trim()); const contents = await this.fetchContentsFromUrls(urls, callback); - const info = await this.extractInfoFromContents( - contents, - objective, - task, - callback, - ); + const info = await this.extractInfoFromContents(contents, objective, task); + this.handleMessage({ + id: uuidv4(), + taskId: task.id.toString(), + content: info.join('\n\n'), + type: this.name, + style: 'text', + status: 'complete', + }); + callback('- Completed: Extract info from contents', 'complete'); return info.join('\n\n'); } @@ -47,7 +53,7 @@ export class WebLoader extends Skill { task: AgentTask, callback: (message: string) => void, ): Promise { - const prompt = `- Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; + const prompt = `Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; const urlString = await this.generateText(prompt, task, undefined, true); callback(` - URLs: ${urlString}\n`); return urlString; @@ -61,8 +67,8 @@ export class WebLoader extends Skill { return await Promise.all( urls.slice(0, MAX_URLS).map(async (url) => { callback(`- Reading: ${url} ...\n`); - const content = await this.webScrapeTool(url); - if (content.length === 0) { + const content = await webScrape(url); + if (!content || content.length === 0) { callback(` - Content: No content found.\n`); return { url, content: '' }; } @@ -80,41 +86,22 @@ export class WebLoader extends Skill { contents: { url: string; content: string }[], objective: string, task: AgentTask, - callback: (message: string) => void, ): Promise { - callback(`- Extracting relevant information from the web pages.\n`); return await Promise.all( contents.map(async (item) => { return ( `URL: ${item.url}\n\n` + (await largeTextExtract( + this.id, objective, item.content, task, - this.isRunningRef, - callback, - this.abortController.signal, + this.apiKeys.openai, + this.handleMessage, + this.abortSignal, )) ); }), ); } - - private async webScrapeTool(url: string): Promise { - try { - const response = await axios.post( - '/api/tools/scrape', - { url }, - { signal: this.abortController.signal }, - ); - return response?.data?.response; - } catch (error: any) { - if (error.name === 'AbortError') { - console.error('Request aborted', error.message); - } else { - console.error(error.message); - } - return ''; - } - } } diff --git a/src/agents/babyelfagi/skills/presets/webSearch.ts b/src/agents/babyelfagi/skills/presets/webSearch.ts index f0ec2d77..b5a79d32 100644 --- a/src/agents/babyelfagi/skills/presets/webSearch.ts +++ b/src/agents/babyelfagi/skills/presets/webSearch.ts @@ -1,5 +1,5 @@ import { AgentTask } from '@/types'; -import { webBrowsing } from '@/agents/babydeeragi/tools/webBrowsing'; +import { webBrowsing } from '@/agents/babyelfagi/tools/webBrowsing'; import { Skill } from '../skill'; // This skill is Specialized for web browsing @@ -9,27 +9,26 @@ export class WebSearch extends Skill { descriptionForHuman = 'A tool that performs web searches.'; descriptionForModel = 'A tool that performs web searches.'; icon = '🔎'; - apiKeysRequired = []; + apiKeysRequired = ['openai']; async execute( task: AgentTask, dependentTaskOutputs: string, objective: string, ): Promise { - if (!this.valid) return ''; + if (!this.valid || this.signal?.aborted) return ''; const taskOutput = (await webBrowsing( objective, task, dependentTaskOutputs, - this.messageCallback, - undefined, - this.isRunningRef, + this.handleMessage, this.verbose, undefined, this.language, - this.abortController.signal, + this.apiKeys.openai, + this.signal, )) ?? ''; return taskOutput; diff --git a/src/agents/babyelfagi/skills/skill.ts b/src/agents/babyelfagi/skills/skill.ts index 54cff634..9d48cee1 100644 --- a/src/agents/babyelfagi/skills/skill.ts +++ b/src/agents/babyelfagi/skills/skill.ts @@ -1,14 +1,12 @@ -import { AgentTask, LLMParams, Message } from '@/types'; -import { setupMessage } from '@/utils/message'; -import { getUserApiKey } from '@/utils/settings'; -import axios from 'axios'; +import { AgentTask, LLMParams, AgentMessage } from '@/types'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { HumanChatMessage } from 'langchain/schema'; +import { v4 as uuidv4 } from 'uuid'; export type SkillType = 'normal' | 'dev'; -export type SkillExecutionLocation = 'client' | 'server'; export class Skill { + id: string; name: string = 'base_kill'; descriptionForHuman: string = 'This is the base skill.'; descriptionForModel: string = 'This is the base skill.'; @@ -17,31 +15,30 @@ export class Skill { apiKeysRequired: Array> = []; valid: boolean; apiKeys: { [key: string]: string }; - executionLocation: SkillExecutionLocation = 'client'; // 'client' or 'server' // for UI - messageCallback: (message: Message) => void; - abortController: AbortController; - isRunningRef?: React.MutableRefObject; + handleMessage: (message: AgentMessage) => void; verbose: boolean; language: string = 'en'; + signal?: AbortSignal; + + BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; // This index signature allows dynamic assignment of properties [key: string]: any; constructor( apiKeys: { [key: string]: string }, - messageCallback: (message: Message) => void, - abortController: AbortController, - isRunningRef?: React.MutableRefObject, + handleMessage: (message: AgentMessage) => Promise, verbose: boolean = false, language: string = 'en', + abortSignal?: AbortSignal, ) { this.apiKeys = apiKeys; - this.messageCallback = messageCallback; - this.abortController = abortController; - this.isRunningRef = isRunningRef; + this.handleMessage = handleMessage; this.verbose = verbose; this.language = language; + this.signal = abortSignal; + this.id = uuidv4(); const missingKeys = this.checkRequiredKeys(apiKeys); if (missingKeys.length > 0) { @@ -91,86 +88,90 @@ export class Skill { throw new Error("Method 'execute' must be implemented"); } + async sendCompletionMessage() { + this.handleMessage({ + content: '', + status: 'complete', + }); + } + async generateText( prompt: string, task: AgentTask, - params?: LLMParams, + params: LLMParams = {}, ignoreCallback: boolean = false, ): Promise { - if (getUserApiKey()) { - let chunk = ''; - const messageCallback = ignoreCallback ? () => {} : this.messageCallback; - const llm = new ChatOpenAI( - { - openAIApiKey: this.apiKeys.openai, - modelName: params?.modelName ?? 'gpt-3.5-turbo', - temperature: params?.temperature ?? 0.7, - maxTokens: params?.maxTokens ?? 1500, - topP: params?.topP ?? 1, - frequencyPenalty: params?.frequencyPenalty ?? 0, - presencePenalty: params?.presencePenalty ?? 0, - streaming: params?.streaming === undefined ? true : params.streaming, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - messageCallback?.( - setupMessage('task-execute', chunk, undefined, '🤖', task.id), - ); - }, - }, - ], - }, - { baseOptions: { signal: this.abortController.signal } }, - ); + const callback = ignoreCallback ? () => {} : this.callbackMessage; + const id = uuidv4(); + const defaultParams = { + apiKey: this.apiKeys.openai, + modelName: 'gpt-3.5-turbo', + temperature: 0.7, + maxTokens: 1500, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + streaming: true, + }; + const llmParams = { ...defaultParams, ...params }; - try { - const response = await llm.call([new HumanChatMessage(prompt)]); - messageCallback?.( - setupMessage('task-output', response.text, undefined, '✅', task.id), - ); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return `Task aborted.`; - } - console.log('error: ', error); - return 'Failed to generate text.'; - } - } else { - // server side request - const response = await axios - .post( - '/api/elf/completion', + const message: AgentMessage = { + id, + content: '', + type: task.skill, + taskId: task.id.toString(), + status: 'complete', + }; + const llm = new ChatOpenAI( + { + openAIApiKey: this.apiKeys.openai, + ...llmParams, + callbacks: [ { - prompt: prompt, - model_name: params?.modelName ?? 'gpt-3.5-turbo', - temperature: params?.temperature ?? 0.7, - max_tokens: params?.maxTokens ?? 1500, - top_p: params?.topP ?? 1, - frequency_penalty: params?.frequencyPenalty ?? 0, - presence_penalty: params?.presencePenalty ?? 0, - }, - { - signal: this.abortController.signal, + handleLLMNewToken(token: string) { + callback?.({ + ...message, + ...{ content: token, status: 'running' }, + }); + }, }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - return undefined; - } - console.log('error: ', error); - return undefined; - }); + ], + }, + { baseOptions: { signal: this.abortSignal } }, + ); - return response?.data.response; + try { + const response = await llm.call([new HumanChatMessage(prompt)]); + this.callbackMessage(message); + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return `Task aborted.`; + } + console.log('error: ', error); + return 'Failed to generate text.'; } } + callbackMessage = (message: AgentMessage) => { + const baseMessage: AgentMessage = { + id: this.id, + content: '', + type: this.name, + style: 'text', + status: 'running', + }; + const mergedMessage = { ...baseMessage, ...message }; + this.handleMessage(mergedMessage); + }; + async getDirectoryStructure(): Promise { - const response = await fetch('/api/local/directory-structure', { - method: 'GET', - }); + const response = await fetch( + `${this.BASE_URL}/api/local/directory-structure`, + { + method: 'GET', + }, + ); if (!response.ok) { throw new Error('Failed to get directory structure'); } diff --git a/src/agents/babyelfagi/tools/utils/largeTextExtract.ts b/src/agents/babyelfagi/tools/utils/largeTextExtract.ts new file mode 100644 index 00000000..44695252 --- /dev/null +++ b/src/agents/babyelfagi/tools/utils/largeTextExtract.ts @@ -0,0 +1,43 @@ +import { AgentMessage, AgentTask } from '@/types'; +import { relevantInfoExtraction } from './relevantInfoExtraction'; + +export const largeTextExtract = async ( + id: string, + objective: string, + largeString: string, + task: AgentTask, + userApiKey?: string, + callback?: (message: AgentMessage) => void, + signal?: AbortSignal, +) => { + const chunkSize = 15000; + const overlap = 500; + let notes = ''; + + // for status message + const total = Math.ceil(largeString.length / (chunkSize - overlap)); + + for (let i = 0; i < largeString.length; i += chunkSize - overlap) { + callback?.({ + content: ` - chunk ${i / (chunkSize - overlap) + 1} of ${total}\n`, + style: 'log', + type: task.skill, + id: id, + taskId: task.id.toString(), + status: 'running', + }); + + const chunk = largeString.slice(i, i + chunkSize); + + const response = await relevantInfoExtraction( + objective, + task.task, + notes, + chunk, + userApiKey, + signal, + ); + notes += response; + } + return notes; +}; diff --git a/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts b/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts new file mode 100644 index 00000000..339bd4da --- /dev/null +++ b/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts @@ -0,0 +1,60 @@ +import { OpenAIChat } from 'langchain/llms/openai'; +import { LLMChain } from 'langchain/chains'; +import { + ChatPromptTemplate, + HumanMessagePromptTemplate, + SystemMessagePromptTemplate, +} from 'langchain/prompts'; + +export const relevantInfoExtraction = async ( + objective: string, + task: string, + notes: string, + chunk: string, + userApiKey?: string, + signal?: AbortSignal, +) => { + const modelName = 'gpt-3.5-turbo-16k-0613'; // use a fixed model + const prompt = relevantInfoExtractionPrompt(); + const llm = new OpenAIChat( + { + modelName, + openAIApiKey: userApiKey, + temperature: 0.7, + maxTokens: 800, + topP: 1, + stop: ['###'], + }, + { baseOptions: { signal: signal } }, + ); + const chain = new LLMChain({ llm: llm, prompt }); + try { + const response = await chain.call({ + openAIApiKey: userApiKey, + objective, + task, + notes, + chunk, + }); + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return null; + } + console.log('error: ', error); + return 'Failed to extract relevant information.'; + } +}; + +const relevantInfoExtractionPrompt = () => { + const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; + const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes:`; + const prompt = ChatPromptTemplate.fromPromptMessages([ + SystemMessagePromptTemplate.fromTemplate(systemTemplate), + HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), + ]); + + prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; + + return prompt; +}; diff --git a/src/agents/babyelfagi/tools/utils/textCompletion.ts b/src/agents/babyelfagi/tools/utils/textCompletion.ts new file mode 100644 index 00000000..8f8e3192 --- /dev/null +++ b/src/agents/babyelfagi/tools/utils/textCompletion.ts @@ -0,0 +1,67 @@ +import { AgentMessage, AgentTask } from '@/types'; +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import { HumanChatMessage } from 'langchain/schema'; + +export const textCompletion = async ( + prompt: string, + id: string, + task: AgentTask, + modelName: string, + userApiKey?: string, + signal?: AbortSignal, + messageCallnback?: (message: AgentMessage) => void, +) => { + if (prompt.length > 3200) { + modelName = 'gpt-3.5-turbo-16k-0613'; + } + + const llm = new ChatOpenAI( + { + openAIApiKey: userApiKey, + modelName, + temperature: 0.2, + maxTokens: 800, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + messageCallnback?.({ + content: token, + type: task.skill, + id: id, + taskId: task.id.toString(), + status: 'running', + }); + }, + }, + ], + }, + { baseOptions: { AbortSignal: signal } }, + ); + + try { + const response = await llm.call([new HumanChatMessage(prompt)]); + messageCallnback?.({ + content: response.text, + type: task.skill, + id: id, + taskId: task.id.toString(), + status: 'complete', + }); + + if (messageCallnback instanceof AbortSignal) { + throw new Error('messageCallnback cannot be of type AbortSignal'); + } + + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return null; + } + console.log('error: ', error); + return 'Failed to generate text.'; + } +}; diff --git a/src/agents/babyelfagi/tools/webBrowsing.ts b/src/agents/babyelfagi/tools/webBrowsing.ts new file mode 100644 index 00000000..dbd73e82 --- /dev/null +++ b/src/agents/babyelfagi/tools/webBrowsing.ts @@ -0,0 +1,163 @@ +import { simplifySearchResults } from '@/agents/babyelfagi/tools/webSearch'; +import { AgentTask, AgentMessage } from '@/types'; +import { analystPrompt, searchQueryPrompt } from '../../../utils/prompt'; +import { textCompletion } from './utils/textCompletion'; +import { largeTextExtract } from './utils/largeTextExtract'; +import { v4 as uuidv4 } from 'uuid'; +import { webSearch } from './webSearch'; +import { webScrape } from './webScrape'; + +export const webBrowsing = async ( + objective: string, + task: AgentTask, + dependentTasksOutput: string, + messageCallback: (message: AgentMessage) => void, + verbose: boolean = false, + modelName: string = 'gpt-3.5-turbo', + language: string = 'en', + userApiKey?: string, + signal?: AbortSignal, +) => { + let id = uuidv4(); + const prompt = searchQueryPrompt( + task.task, + dependentTasksOutput.slice(0, 3500), + ); + const searchQuery = await textCompletion( + prompt, + id, + task, + modelName, + userApiKey, + signal, + ); + const trimmedQuery = searchQuery?.replace(/^"|"$/g, ''); // remove quotes from the search query + + let message = `Search query: ${trimmedQuery}\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + const searchResults = await webSearch(trimmedQuery || ''); + if (!searchResults) { + return 'Failed to search.'; + } + + const simplifiedSearchResults = simplifySearchResults(searchResults); + message = `✅ Completed search. \nNow reading content.\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + let results = ''; + let index = 1; + let completedCount = 0; + const MaxCompletedCount = 2; + // Loop through search results + for (const searchResult of simplifiedSearchResults) { + if (signal?.aborted) return ''; + if (completedCount >= MaxCompletedCount) break; + + // Extract the URL from the search result + const url = searchResult.link; + let title = `${index}. Reading: ${url} ...`; + + message = `${title}\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + const content = await webScrape(url); + if (!content) { + let message = ` - Failed to read content. Skipped. \n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + continue; + } + + title = `${index}. Extracting relevant info...`; + message = ` - Content reading completed. Length:${content?.length}. Now extracting relevant info...\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + if (content?.length === 0) { + let message = ` - Content too short. Skipped. \n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + index += 1; + continue; + } + + message = ` - Extracting relevant information\n`; + title = `${index}. Extracting relevant info...`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + // Set an interval to ping callbackSearchStatus every 10 seconds + const intervalId = setInterval(() => { + message = ' - Still extracting relevant information...\n'; + callbackSearchStatus(id, message, task, messageCallback, verbose); + }, 10000); + + const info = await largeTextExtract( + id, + objective, + content.slice(0, 20000), + task, + userApiKey, + messageCallback, + signal, + ); + clearInterval(intervalId); + + message = ` - Relevant info: ${info + .slice(0, 100) + .replace(/\r?\n/g, '')} ...\n`; + + title = `${index}. Relevant info...`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + results += `${info}. `; + index += 1; + completedCount += 1; + } + + message = 'Analyzing results...\n'; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + const outputId = uuidv4(); + const ap = analystPrompt(results, language); + const analyzedResults = await textCompletion( + ap, + outputId, + task, + modelName, + userApiKey, + signal, + messageCallback, + ); + + // callback to search logs + message = 'Completed analyzing results.'; + const msg: AgentMessage = { + id, + taskId: task.id.toString(), + type: task.skill, + content: message, + style: 'log', + status: 'complete', + }; + messageCallback(msg); + + return analyzedResults; +}; + +const callbackSearchStatus = ( + id: string, + message: string, + task: AgentTask, + messageCallback: (message: AgentMessage) => void, + verbose: boolean = false, +) => { + if (verbose) { + console.log(message); + } + + messageCallback({ + id, + taskId: task.id.toString(), + type: task.skill, + style: 'log', + content: message, + status: 'running', + }); +}; diff --git a/src/agents/common/tools/webScrape.ts b/src/agents/babyelfagi/tools/webScrape.ts similarity index 91% rename from src/agents/common/tools/webScrape.ts rename to src/agents/babyelfagi/tools/webScrape.ts index 3b30f5dc..ab8dc11b 100644 --- a/src/agents/common/tools/webScrape.ts +++ b/src/agents/babyelfagi/tools/webScrape.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; import { load } from 'cheerio'; export const webScrape = async (url: string) => { @@ -15,8 +14,9 @@ export const webScrape = async (url: string) => { const fetchUrlContent = async (url: string) => { try { - const response = await axios.get(url); - return response.data; + const response = await fetch(url); + const data = await response.text(); + return data; } catch (error) { console.error(`Error while fetching the URL: ${error}`); return ''; diff --git a/src/agents/common/tools/webSearch.ts b/src/agents/babyelfagi/tools/webSearch.ts similarity index 69% rename from src/agents/common/tools/webSearch.ts rename to src/agents/babyelfagi/tools/webSearch.ts index c5d3b3c7..8b583dbb 100644 --- a/src/agents/common/tools/webSearch.ts +++ b/src/agents/babyelfagi/tools/webSearch.ts @@ -1,5 +1,3 @@ -import axios from 'axios'; - export const webSearch = async (query: string) => { try { // Change environment variable name for typo @@ -10,29 +8,37 @@ export const webSearch = async (query: string) => { } if (process.env.SERP_API_KEY) { - const response = await axios.get('https://serpapi.com/search', { - params: { - api_key: process.env.SERP_API_KEY, - engine: 'google', - q: query, + const response = await fetch( + `https://serpapi.com/search?api_key=${process.env.SERP_API_KEY}&engine=google&q=${query}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, }, - }); - return response.data.organic_results; + ); + const data = await response.json(); + return data.organic_results; } else if ( process.env.GOOGLE_SEARCH_API_KEY && process.env.GOOGLE_CUSTOM_INDEX_ID ) { - const response = await axios.get( + const response = await fetch( 'https://www.googleapis.com/customsearch/v1', { - params: { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ key: process.env.GOOGLE_SEARCH_API_KEY, cx: process.env.GOOGLE_CUSTOM_INDEX_ID, q: query, - }, + }), }, ); - return response.data.items; + const data = await response.json(); + return data.items; } } catch (error) { console.log('error: ', error); diff --git a/src/agents/base/AgentExecuter.ts b/src/agents/base/AgentExecuter.ts index 57fff585..f8c2d541 100644 --- a/src/agents/base/AgentExecuter.ts +++ b/src/agents/base/AgentExecuter.ts @@ -1,76 +1,58 @@ -import { AgentStatus, AgentTask, Message } from '@/types'; +import { AgentMessage, AgentTask } from '@/types'; import { Printer } from '@/utils/print'; export class AgentExecuter { objective: string; modelName: string; - messageCallback: (message: Message) => void; - statusCallback: (status: AgentStatus) => void; - cancelCallback: () => void; - language: string = 'en'; - verbose: boolean = false; + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }; + language: string; + verbose: boolean; + signal?: AbortSignal; - taskIdCounter: number = 0; - retryCounter: number = 0; - taskList: AgentTask[] = []; - isRunningRef = { current: false }; - - abortController?: AbortController; printer: Printer; + taskList: AgentTask[] = []; constructor( objective: string, modelName: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancelCallback: () => void, + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }, language: string = 'en', - verbose: boolean = false, + varbose: boolean = false, + signal?: AbortSignal, ) { this.objective = objective; this.modelName = modelName; - this.messageCallback = messageCallback; - this.statusCallback = statusCallback; - this.cancelCallback = cancelCallback; + this.handlers = handlers; this.language = language; - this.verbose = verbose; - this.printer = new Printer(messageCallback, verbose); + this.verbose = varbose; + this.signal = signal; + this.printer = new Printer(this.handlers.handleMessage, this.verbose); } - async start() { - this.isRunningRef.current = true; - this.taskIdCounter = 0; - this.retryCounter = 0; + async run() { this.taskList = []; await this.prepare(); await this.loop(); await this.finishup(); } - async stop() { - this.isRunningRef.current = false; - this.cancelCallback(); - this.abortController?.abort(); - } - - async finishup() { - if (!this.isRunningRef.current) { - this.statusCallback({ type: 'finished' }); - return; - } - - // Objective completed - this.printer.printAllTaskCompleted(); - this.statusCallback({ type: 'finished' }); - this.cancelCallback(); - this.isRunningRef.current = false; - } - // prepare() is called before loop() async prepare() { this.printer.printObjective(this.objective); } + async loop() {} - async userInput(taskId: number, message: string) {} + async finishup() { + if (this.signal?.aborted) return; + // Objective completed + this.printer.printAllTaskCompleted(); + this.handlers.handleEnd(); + } } diff --git a/src/agents/base/AgentStream.ts b/src/agents/base/AgentStream.ts new file mode 100644 index 00000000..331c31a3 --- /dev/null +++ b/src/agents/base/AgentStream.ts @@ -0,0 +1,49 @@ +import { AIStreamCallbacks, createCallbacksTransformer } from 'ai'; +import { AgentMessage } from '@/types'; + +export function AgentStream(callbacks?: AIStreamCallbacks) { + const stream = new TransformStream(); + const writer = stream.writable.getWriter(); + let observers: ((isActive: boolean) => void)[] = []; + + let wasActive: boolean | null = null; + const isWriterActive = () => writer.desiredSize !== null; + const notifyObservers = () => { + const isActive = isWriterActive(); + if (wasActive !== isActive) { + observers.forEach((observer) => observer(isActive)); + wasActive = isActive; + } + }; + + return { + stream: stream.readable.pipeThrough(createCallbacksTransformer(callbacks)), + handlers: { + handleMessage: async (message: AgentMessage) => { + notifyObservers(); + if (!isWriterActive()) return; + await writer.ready; + await writer.write( + `${JSON.stringify({ + message, + })}\n\n`, + ); + }, + handleEnd: async () => { + notifyObservers(); + if (!isWriterActive()) return; + await writer.ready; + await writer.close(); + }, + handleError: async (e: Error) => { + notifyObservers(); + if (!isWriterActive()) return; + await writer.ready; + await writer.abort(e); + }, + }, + addObserver: (observer: (isActive: boolean) => void) => { + observers.push(observer); + }, + }; +} diff --git a/src/agents/common/tools/textCompletionTool.ts b/src/agents/common/tools/textCompletionTool.ts deleted file mode 100644 index f86e3e24..00000000 --- a/src/agents/common/tools/textCompletionTool.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { getUserApiKey } from '@/utils/settings'; -import { Message } from '@/types'; -import { setupMessage } from '@/utils/message'; -import axios from 'axios'; -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { HumanChatMessage } from 'langchain/schema'; - -export const textCompletionTool = async ( - prompt: string, - modelName: string, - signal?: AbortSignal, - id?: number, - messageCallnback?: (message: Message) => void, -) => { - if (prompt.length > 3200) { - modelName = 'gpt-3.5-turbo-16k-0613'; - } - let chunk = ''; - const openAIApiKey = getUserApiKey(); - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - if (!openAIApiKey) { - // server side request - const response = await axios - .post( - '/api/deer/completion', - { - prompt, - model_name: modelName, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - return response?.data?.response.text; - } - - const llm = new ChatOpenAI( - { - openAIApiKey: openAIApiKey, - modelName: modelName, - temperature: 0.2, - maxTokens: 800, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - messageCallnback?.( - setupMessage('task-execute', chunk, undefined, '🤖', id), - ); - }, - }, - ], - }, - { baseOptions: { signal: signal } }, - ); - - try { - const response = await llm.call([new HumanChatMessage(prompt)]); - // - messageCallnback?.( - setupMessage('task-output', response.text, undefined, '✅', id), - ); - - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return 'Failed to generate text.'; - } -}; diff --git a/src/components/Agent/Agent.tsx b/src/components/Agent/Agent.tsx deleted file mode 100644 index 2438614b..00000000 --- a/src/components/Agent/Agent.tsx +++ /dev/null @@ -1,512 +0,0 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import va from '@vercel/analytics'; -import { - AgentStatus, - AgentType, - Execution, - Message, - MessageBlock, - SelectItem, - UserSettings, -} from '@/types'; -import { Input } from './Input'; -import AgentMessage from './AgentMessage'; -import { AgentParameter } from './AgentParameter'; -import { ProjectTile } from './ProjectTile'; -import { AgentMessageHeader } from './AgentMessageHeader'; -import { - getExportText, - getMessageBlocks, - loadingAgentMessage, -} from '../../utils/message'; -import { BabyAGI } from '@/agents/babyagi'; -import { BabyDeerAGI } from '@/agents/babydeeragi/executer'; -import { AGENT, ITERATIONS, MODELS, SETTINGS_KEY } from '@/utils/constants'; -import { toast } from 'sonner'; -import { v4 as uuidv4 } from 'uuid'; -import { useExecution } from '@/hooks/useExecution'; -import { useExecutionStatus } from '@/hooks/useExecutionStatus'; -import { translate } from '../../utils/translate'; -import axios from 'axios'; -import { taskCompletedNotification } from '@/utils/notification'; -import { useTranslation } from 'next-i18next'; -import { AgentMessageBlock } from './AgentMessageBlock'; -import { AgentTask } from './AgentTask'; -import { IntroGuide } from './IntroGuide'; -import { BabyElfAGI } from '@/agents/babyelfagi/executer'; -import { SkillsList } from './SkillList'; - -export const Agent: FC = () => { - const [model, setModel] = useState(MODELS[1]); - const [iterations, setIterations] = useState(ITERATIONS[0]); - const [objective, setObjective] = useState(''); - const [firstTask, setFirstTask] = useState( - translate('FIRST_TASK_PLACEHOLDER', 'constants'), - ); - const [messages, setMessages] = useState([]); - const [messageBlocks, setMessageBlocks] = useState([]); - const [agentStatus, setAgentStatus] = useState({ - type: 'ready', - }); - const [agent, setAgent] = useState( - null, - ); - const [selectedAgent, setSelectedAgent] = useState(AGENT[0]); - const { i18n } = useTranslation(); - const [language, setLanguage] = useState(i18n.language); - - const messagesEndRef = useRef(null); - const { - addExecution, - updateExec, - executions, - selectedExecutionId, - selectExecution, - } = useExecution(); - const { isExecuting, setExecuting } = useExecutionStatus(); - - const scrollToBottom = useCallback(() => { - const behavior = isExecuting ? 'smooth' : 'auto'; - messagesEndRef.current?.scrollIntoView({ behavior: behavior }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [messageBlocks]); - - useEffect(() => { - scrollToBottom(); - }, [scrollToBottom]); - - useEffect(() => { - if (selectedExecutionId) { - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - setMessages(selectedExecution.messages); - } - } else { - setMessages([]); - setObjective(''); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedExecutionId]); - - useEffect(() => { - const execution = executions.find((exe) => exe.id === selectedExecutionId); - if (execution) { - const updatedExecution: Execution = { - ...execution, - messages: messages, - }; - updateExec(updatedExecution); - } - - const blocks = getMessageBlocks(messages, isExecuting); - setMessageBlocks(blocks); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [messages]); - - useEffect(() => { - setLanguage(i18n.language); - }, [i18n]); - - // manage data - const saveNewData = async () => { - const execution: Execution = { - id: uuidv4(), - name: objective, - date: new Date().toISOString(), - params: { - objective: objective, - model: model, - iterations: iterations, - firstTask: firstTask, - agent: selectedAgent.id as AgentType, - }, - messages: messages, - }; - - selectExecution(execution.id); - await new Promise((resolve) => { - addExecution(execution); - resolve(null); - }); - - return execution; - }; - - // handler functions - const messageHandler = (message: Message) => { - setMessages((currentMessages) => { - if (selectedAgent.id !== 'babyagi') { - // if the message.type and id are the same, overwrite the message - const index = currentMessages.findIndex( - (msg) => msg.type === message.type && msg.id === message.id, - ); - if (index !== -1) { - const newMessages = [...currentMessages]; - newMessages[index] = message; - return newMessages; - } - } - - const updatedMessages = [...currentMessages, message]; - - // show toast notification - if (message.type === 'complete' || message.type === 'end-of-iterations') { - toast.success(translate('ALL_TASKS_COMPLETED_TOAST', 'agent')); - taskCompletedNotification(objective); - } else if (message.type === 'done') { - toast.success(translate('TASK_COMPLETED_TOAST', 'agent')); - } - - return updatedMessages; - }); - }; - - const inputHandler = (value: string) => { - setObjective(value); - }; - - const cancelHandle = () => { - setAgent(null); - setExecuting(false); - }; - - const startHandler = async () => { - if (needSettingsAlert()) { - alert(translate('ALERT_SET_UP_API_KEY', 'agent')); - return; - } - if (model.id === 'gpt-4') { - const enabled = await enabledGPT4(); - if (!enabled) { - alert(translate('ALERT_GPT_4_DISABLED', 'constants')); - return; - } - } - - setMessages([]); - setExecuting(true); - const execution = await saveNewData(); - const verbose = false; // You can set this to true to see the agent's internal state - - // switch agent - let agent = null; - switch (selectedAgent.id) { - case 'babyagi': - agent = new BabyAGI( - objective, - model.id, - Number(iterations.id), - firstTask, - execution.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - case 'babydeeragi': - agent = new BabyDeerAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - case 'babyelfagi': - agent = new BabyElfAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - } - setAgent(agent); - agent?.start(); - - va.track('Start', { - model: model.id, - agent: selectedAgent.id, - iterations: iterations.id, - }); - }; - - const stopHandler = () => { - // refresh message blocks - const blocks = getMessageBlocks(messages, false); - setMessageBlocks(blocks); - - setExecuting(false); - agent?.stop(); - - va.track('Stop'); - }; - - const clearHandler = () => { - setMessages([]); - selectExecution(undefined); - setAgentStatus({ type: 'ready' }); - - va.track('New'); - }; - - const copyHandler = () => { - navigator.clipboard.writeText(getExportText(messages, selectedAgent.id)); - toast.success(translate('COPIED_TO_CLIPBOARD', 'agent')); - - va.track('CopyToClipboard'); - }; - - const downloadHandler = () => { - const element = document.createElement('a'); - const filename = - objective.length > 0 - ? `${objective.replace(/\s/g, '_')}.txt` - : 'download.txt'; - const file = new Blob( - ['\uFEFF' + getExportText(messages, selectedAgent.id)], - { - type: 'text/plain;charset=utf-8', - }, - ); - element.href = URL.createObjectURL(file); - element.download = filename; - document.body.appendChild(element); - element.click(); - - va.track('Download'); - }; - - const feedbackHandler = (isGood: boolean) => { - let selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - setMessages(selectedExecution.messages); - } - const feedbackObjective = selectedExecution?.params.objective; - const feedbackModel = selectedExecution?.params.model.id; - const feedbackAgent = selectedExecution?.params.agent; - const feedbackIterations = Number(selectedExecution?.params.iterations.id); - - let lastResult = messages - .filter( - (message) => - message.type === 'task-output' || message.type === 'task-result', - ) - .pop()?.text; - if (feedbackAgent === 'babybeeagi') { - lastResult = messages - .filter((message) => message.type === 'task-result-summary') - .pop()?.text; - } - const lastTaskList = messages - .filter((message) => message.type === 'task-list') - .pop()?.text; - const sessionSummary = messages - .filter((message) => message.type === 'session-summary') - .pop()?.text; - const iterationNumber = messages.filter( - (message) => message.type === 'done', - ).length; - const finished = - messages.filter( - (message) => - message.type === 'complete' || message.type === 'end-of-iterations', - ).length > 0; - const output = getExportText(messages); - - axios.post('/api/feedback', { - objective: feedbackObjective, - evaluation: isGood ? 'good' : 'bad', - model: feedbackModel, - agent: feedbackAgent, - iterations: feedbackIterations, - last_result: lastResult, - task_list: lastTaskList, - session_summary: sessionSummary, - iteration_number: iterationNumber, - finished: finished, - output: output, - }); - - toast.success(translate('FEEDBACK_SUBMITTED_TOAST', 'constants')); - - // update execution - if (selectedExecution) { - selectedExecution.evaluation = isGood ? 'good' : 'bad'; - updateExec(selectedExecution); - } - }; - - const userInputHandler = async (id: number, text: string) => { - if (agent instanceof BabyDeerAGI) { - agent.userInput(id, text); - } - }; - - const needSettingsAlert = () => { - const useUserApiKey = process.env.NEXT_PUBLIC_USE_USER_API_KEY; - if (useUserApiKey === 'false') { - return false; - } - - const userSettings = localStorage.getItem(SETTINGS_KEY); - if (userSettings) { - const { openAIApiKey } = JSON.parse(userSettings) as UserSettings; - if (openAIApiKey && openAIApiKey?.length > 0) { - return false; - } - } - return true; - }; - - const enabledGPT4 = async () => { - const userSettings = localStorage.getItem(SETTINGS_KEY); - if (!userSettings) { - return false; - } - - const { enabledGPT4 } = JSON.parse(userSettings) as UserSettings; - if (enabledGPT4 === undefined) { - return true; // If no value is given, its enabled by default - } - - return enabledGPT4; - }; - - const currentEvaluation = () => { - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - return selectedExecution.evaluation; - } - return undefined; - }; - - const currentAgentId = () => { - if (isExecuting) { - return selectedAgent.id; - } - - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - return selectedExecution.params.agent; - } - return undefined; - }; - - const skills = () => { - if (selectedAgent.id === 'babyelfagi') { - const elf = new BabyElfAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - false, - ); - const skills = elf.skillRegistry.getAllSkills(); - const skillInfos = skills.map((skill) => { - const skillInfo = { - name: skill.name, - description: skill.descriptionForHuman, - icon: skill.icon, - badge: skill.type, - }; - return skillInfo; - }); - return skillInfos; - } - return []; - }; - - return ( -
- {messageBlocks.length === 0 ? ( - <> - - {selectedAgent.id === 'babyelfagi' && ( - - )} -
-
- - {(selectedAgent.id === 'babydeeragi' || - selectedAgent.id === 'babyelfagi') && ( - setObjective(value)} - agent={selectedAgent.id} - /> - )} -
-
- - ) : ( -
- - {messageBlocks.map((block, index) => - currentAgentId() === 'babydeeragi' || - currentAgentId() === 'babyelfagi' ? ( - - ) : ( - - ), - )} - {isExecuting && ( - - )} -
-
- )} - 0} - agent={selectedAgent.id as AgentType} - evaluation={currentEvaluation()} - /> -
- ); -}; diff --git a/src/components/Agent/AgentBlock.tsx b/src/components/Agent/AgentBlock.tsx new file mode 100644 index 00000000..ec9a2df6 --- /dev/null +++ b/src/components/Agent/AgentBlock.tsx @@ -0,0 +1,20 @@ +import { Block } from '@/types'; +import { FC } from 'react'; +import { LabelBlock } from './LabelBlock'; +import { TaskBlock } from './TaskBlock'; + +export interface AgentBlockProps { + block: Block; +} + +export const AgentBlock: FC = ({ block }) => { + return ( +
+ {block.style === 'label' ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/src/components/Agent/AgentInput.tsx b/src/components/Agent/AgentInput.tsx new file mode 100644 index 00000000..5d8503bf --- /dev/null +++ b/src/components/Agent/AgentInput.tsx @@ -0,0 +1,182 @@ +import TextareaAutosize from 'react-textarea-autosize'; +import { translate } from '@/utils/translate'; +import { + ClipboardIcon, + DividerVerticalIcon, + DotFilledIcon, + DownloadIcon, + PlayIcon, + PlusIcon, + StopIcon, + UpdateIcon, +} from '@radix-ui/react-icons'; +import { FC, FormEvent, ChangeEvent, KeyboardEvent } from 'react'; +import { FeedbackButtons } from './FeedbackButtons'; + +type InputProps = { + value: string; + handleSubmit: ( + event: FormEvent | KeyboardEvent, + ) => void; + handleInputChange: (event: ChangeEvent) => void; + handleCancel: () => void; + handleClear: () => void; + handleCopy: () => void; + handleDownload: () => void; + handleFeedback: (isGood: boolean) => void; + isRunning: boolean; + hasMessages: boolean; + type?: string; + evaluation?: 'good' | 'bad'; +}; + +// Extracted the button components to make the main component cleaner +const StopButton = ({ onClick }: { onClick: () => void }) => ( + +); + +const NewButton = ({ onClick }: { onClick: () => void }) => ( + +); + +export const AgentInput: FC = ({ + value, + handleSubmit, + handleInputChange, + handleCancel, + handleClear, + handleCopy, + handleDownload, + handleFeedback, + isRunning, + hasMessages, + type, + evaluation, +}) => { + const handleKeyDown = (e: React.KeyboardEvent) => { + if (hasMessages) { + return; + } + + if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { + e.preventDefault(); + handleSubmit(e); + } + }; + + // Simplified the button rendering logic + const renderButton = () => { + if (isRunning) { + return ; + } + + if (hasMessages) { + return ; + } + + return null; + }; + + return ( +
+
+
+
+
{renderButton()}
+
+ {!isRunning && hasMessages && ( +
+ +
+ +
+
+ + +
+
+ )} +
+
+
+
+ + + + +
+ {isRunning ? : null} +
+
+
+
+ + BabyAGI UI + + {' is designed to make it easier to run and develop with '} + + babyagi + + {' in a web app, like a ChatGPT.'} +
+
+ ); +}; diff --git a/src/components/Agent/AgentLabelBlock.tsx b/src/components/Agent/AgentLabelBlock.tsx deleted file mode 100644 index fca626a2..00000000 --- a/src/components/Agent/AgentLabelBlock.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { MessageBlock } from '@/types'; -import { translate } from '@/utils/translate'; -import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; -import remarkGfm from 'remark-gfm'; -import AgentMessage from './AgentMessage'; -import { useEffect, useRef } from 'react'; - -export interface AgentLabelBlockProps { - block: MessageBlock; -} - -export const AgentLabelBlock: React.FC = ({ block }) => { - const message = block.messages[0]; - const nextMessage = block.messages?.[1]; - const linkRef = useRef(null); - - useEffect(() => { - if (message.type === 'final-result') { - if (!nextMessage) return; - - const file = new Blob(['\uFEFF' + nextMessage?.text], { - type: 'text/plain;charset=utf-8', - }); - const url = URL.createObjectURL(file); - if (linkRef.current) { - const link = linkRef.current; - link.href = url; - link.download = 'session_summary.txt'; - } - - // If it's a development environment, save the file automatically. - if (process.env.NODE_ENV === 'development') { - fetch('/api/local/save-session-summary', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ text: nextMessage.text }), - }) - .then((response) => response.json()) - .then((data) => console.log(data)); - } - } - }, [nextMessage]); - - if ( - message.type !== 'objective' && - message.type !== 'complete' && - message.type !== 'final-result' && - message.type !== 'task-list' && - message.type !== 'task-execute' - ) - return null; - - if (message.type === 'task-execute') { - return ; - } - - return ( -
-
-
-
- {message.icon} -
-
- - {`### ${message.title}\n${message.text}`} - - {message.type === 'final-result' && ( - - ⬇️ {translate('DOWNLOAD_SESSION_SUMMARY', 'message')} - - )} -
-
-
-
- ); -}; diff --git a/src/components/Agent/AgentLoading.tsx b/src/components/Agent/AgentLoading.tsx new file mode 100644 index 00000000..1fc5699d --- /dev/null +++ b/src/components/Agent/AgentLoading.tsx @@ -0,0 +1,25 @@ +import { UpdateIcon } from '@radix-ui/react-icons'; +import { FC } from 'react'; + +interface AgentLoadingProps { + message: string; +} + +const AgentLoading: FC = ({ message }) => { + return ( +
+
+
+ +
+
{message}
+
+
+ ); +}; + +export default AgentLoading; diff --git a/src/components/Agent/AgentMessage.tsx b/src/components/Agent/AgentMessage.tsx deleted file mode 100644 index e4167d4d..00000000 --- a/src/components/Agent/AgentMessage.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Message } from '@/types'; -import { getMessageText } from '@/utils/message'; -import { UpdateIcon } from '@radix-ui/react-icons'; -import { FC, useEffect, useState } from 'react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import { translate } from '../../utils/translate'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { - oneLight, - vscDarkPlus, -} from 'react-syntax-highlighter/dist/cjs/styles/prism'; -import { AgentCollapsible } from './AgentCollapsible'; -import { useTheme } from 'next-themes'; - -interface AgentMessageProps { - message: Message; - spacing?: 'normal' | 'tight'; -} - -const AgentMessage: FC = ({ - message, - spacing = 'normal', -}) => { - const simpleTitle = message.title?.split('(')[0] ?? ''; // ex: 'Creating tasks... (*This process takes time. Please wait...*)' - const { theme, systemTheme } = useTheme(); - const [highlightStyle, setHighlightStyle] = useState(oneLight); - const py = spacing === 'normal' ? 'py-4 md:py-6' : 'py-0'; - - useEffect(() => { - const isDark = - theme === 'system' ? systemTheme === 'dark' : theme === 'dark'; - const style = isDark ? vscDarkPlus : oneLight; - setHighlightStyle(style); - }, [theme, systemTheme]); - - const contents = ( -
- - {String(children).replace(/\n$/, '')} - - ) : ( - - {children} - - ); - }, - }} - > - {getMessageText(message)} - -
- ); - - return ( -
-
- {message.type === 'loading' ? ( -
- -
- ) : message.type === 'task-execute' ? ( -
- ) : ( -
{message.icon}
- )} - {message.type === 'session-summary' ? ( -
- - {translate('SUMMARY')} - - {contents} -
- ) : message.status?.type === 'creating-stream' || - message.status?.type === 'executing-stream' || - message.type === 'search-logs' || - message.type === 'task-execute' ? ( - - {contents} - - ) : ( - contents - )} -
-
- ); -}; - -export default AgentMessage; diff --git a/src/components/Agent/AgentMessageBlock.tsx b/src/components/Agent/AgentMessageBlock.tsx deleted file mode 100644 index be2ee1fa..00000000 --- a/src/components/Agent/AgentMessageBlock.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { MessageBlock } from '@/types'; -import { FC } from 'react'; -import AgentMessage from './AgentMessage'; -import { AgentMessageInput } from './AgentMessageInput'; - -export interface AgentMessageBlockProps { - block: MessageBlock; - userInputCallback: (id: number, input: string) => Promise; -} - -export const AgentMessageBlock: FC = ({ - block, - userInputCallback, -}) => { - return ( -
- {block.messages.map((message, index) => - message.type === `user-input` ? ( - - ) : ( - - ), - )} -
- ); -}; diff --git a/src/components/Agent/AgentMessageFooter.tsx b/src/components/Agent/AgentMessageFooter.tsx deleted file mode 100644 index 747b42e3..00000000 --- a/src/components/Agent/AgentMessageFooter.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { translate } from '@/utils/translate'; -import { FC } from 'react'; -import { ThumbsDown, ThumbsUp } from 'react-feather'; - -export const AgentMessageFooter: FC = () => { - return ( -
- - - {translate('FEEDBACK_MESSAGE', 'constants')} -
- ); -}; diff --git a/src/components/Agent/AgentMessageInput.tsx b/src/components/Agent/AgentMessageInput.tsx deleted file mode 100644 index 8a75edbd..00000000 --- a/src/components/Agent/AgentMessageInput.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Message } from '@/types'; -import { translate } from '@/utils/translate'; -import { FC, FormEvent, useState } from 'react'; -import { Check } from 'react-feather'; - -export interface AgentMessageInputProps { - id: number; - message: Message; - onSubmit: (id: number, message: string) => Promise; -} - -export const AgentMessageInput: FC = ({ - id, - message, - onSubmit, -}) => { - const [text, setText] = useState(''); - const [submitted, setSubmitted] = useState(false); - - const handleSubmit = async (e: FormEvent) => { - e.preventDefault(); - onSubmit(id, text); - setSubmitted(true); - setText(''); - }; - - return ( -
-
-
-
- {message.icon} -
-
-
- {`${message.id}. `} - {message.text} -
-
- setText(e.target.value)} - disabled={submitted} - className="datk:placeholder-neutral-500 w-full rounded-l-lg border border-neutral-200 bg-white py-2 pl-4 pr-12 text-base placeholder-neutral-300 focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-black dark:placeholder-neutral-600 focus:dark:border-neutral-500" - /> - -
-
-
-
-
- ); -}; diff --git a/src/components/Agent/AgentResult.tsx b/src/components/Agent/AgentResult.tsx index 3136e82d..281db65a 100644 --- a/src/components/Agent/AgentResult.tsx +++ b/src/components/Agent/AgentResult.tsx @@ -6,7 +6,7 @@ import React from 'react'; export interface AgentResultProps { children: React.ReactNode; title: string; - dependencies?: number[]; + dependencies?: number[] | string; isOpen?: boolean; } @@ -17,6 +17,8 @@ export const AgentResult: FC = ({ isOpen = true, }) => { const [open, setOpen] = React.useState(false); + const dependenciesString = + typeof dependencies === 'string' ? dependencies : dependencies?.join(', '); useEffect(() => { setOpen(isOpen); @@ -37,7 +39,7 @@ export const AgentResult: FC = ({ {dependencies && dependencies.length > 0 && (
-
{dependencies.join(', ')}
+
{dependenciesString}
)} diff --git a/src/components/Agent/AgentTask.tsx b/src/components/Agent/AgentTask.tsx deleted file mode 100644 index 5df41748..00000000 --- a/src/components/Agent/AgentTask.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { MessageBlock } from '@/types'; -import { FC } from 'react'; -import { AgentCollapsible } from './AgentCollapsible'; -import remarkGfm from 'remark-gfm'; -import { AgentResult } from './AgentResult'; -import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; -import { AgentLabelBlock } from './AgentLabelBlock'; -import { AgentTaskStatus } from './AgentTastStatus'; -import { AgentMessageInput } from './AgentMessageInput'; - -export interface AgentTaskProps { - block: MessageBlock; - userInputCallback: (id: number, input: string) => Promise; -} - -export const AgentTask: FC = ({ block, userInputCallback }) => { - const message = block.messages[0]; - const nextMessage = block.messages[1]; - const id = block.id ?? 0; - - if (message === undefined) return null; - - if (nextMessage?.type === 'user-input') { - return ( - - ); - } - - // task output - const outputMessages = block.messages.filter( - (message) => - message.type === 'task-output' || message.type === 'task-execute', - ); - const logs = block.messages.filter( - (message) => message.type === 'search-logs', - ); - - return message.type === 'next-task' ? ( -
-
-
-
- {message.icon} -
-
- {message.text} -
- -
- {block.messages.length > 1 && ( - -
-
-
- {outputMessages[0]?.icon} -
-
- {outputMessages[0]?.text && ( -
- - {outputMessages[0]?.text} - -
- )} - {logs.length > 0 && - (outputMessages.length === 0 || - block.status === 'complete') && ( - -
- - {logs[0].text} - -
-
- )} -
-
-
-
- )} -
-
- ) : ( - - ); -}; diff --git a/src/components/Agent/AgentTastStatus.tsx b/src/components/Agent/AgentTastStatus.tsx index 95ed7889..c3cc82cb 100644 --- a/src/components/Agent/AgentTastStatus.tsx +++ b/src/components/Agent/AgentTastStatus.tsx @@ -1,8 +1,8 @@ -import { MessageBlock } from '@/types'; +import { Block, MessageBlock } from '@/types'; import { CheckCircledIcon, CircleIcon } from '@radix-ui/react-icons'; export interface AgentTaskStatusProps { - block: MessageBlock; + block: MessageBlock | Block; } export const AgentTaskStatus: React.FC = ({ block }) => { diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx new file mode 100644 index 00000000..b4b3140a --- /dev/null +++ b/src/components/Agent/AgentView.tsx @@ -0,0 +1,251 @@ +import { FC, useEffect, useRef, useState } from 'react'; +import va from '@vercel/analytics'; +import { Execution, SelectItem, Block } from '@/types'; +import { AgentInput } from './AgentInput'; +import { AgentParameter } from './AgentParameter'; +import { ProjectTile } from './ProjectTile'; +import { AgentMessageHeader } from './AgentMessageHeader'; +import { + getExportAgentMessage, + getAgentLoadingMessage, + groupMessages, + convertToAgentMessages, +} from '../../utils/message'; +import { AGENT, ITERATIONS, MODELS } from '@/utils/constants'; +import { translate } from '@/utils/translate'; +import { useTranslation } from 'next-i18next'; +import { IntroGuide } from './IntroGuide'; +import { SkillsList } from './SkillList'; +import { AgentBlock } from './AgentBlock'; +import AgentLoading from './AgentLoading'; +import { + useAgent, + useSkills, + useExecutionManagement, + useApiKeyCheck, + useNotifications, + useErrorHandler, + useResetAndDeselect, + useClipboard, + useFileDownload, + useFeedback, + useScrollControl, + useCurrentEvaluation, +} from '@/hooks'; +import { toast } from 'sonner'; + +export const AgentView: FC = () => { + // Custom hooks + const { i18n } = useTranslation(); + + // useState hooks + const [model, setModel] = useState(MODELS[1]); + const [iterations, setIterations] = useState(ITERATIONS[0]); + const [objective, setObjective] = useState(''); + const [firstTask, setFirstTask] = useState( + translate('FIRST_TASK_PLACEHOLDER', 'constants'), + ); + const [selectedAgent, setSelectedAgent] = useState(AGENT[0]); + const [language, setLanguage] = useState(i18n.language); + const [agentBlocks, setAgentBlocks] = useState([]); + + // useRef hooks + const messagesEndRef = useRef(null); + + // Custom hooks + const skills = useSkills(selectedAgent.id); + const { + saveNewData, + updateExec, + executions, + selectedExecutionId, + selectExecution, + } = useExecutionManagement(); + const { checkAndAlertApiKeySetting } = useApiKeyCheck(); + const { notifyTaskCompletion } = useNotifications(); + const { errorHandler } = useErrorHandler(); + const { copyToClipboard } = useClipboard(); + const { downloadFile } = useFileDownload(); + const { currentEvaluation } = useCurrentEvaluation( + executions, + selectedExecutionId, + ); + + // Functions + const stopHandler = () => { + va.track('Stop'); + toast(translate('EXECUTION_STOPPED', 'agent')); + }; + + const startHandler = async () => { + if (checkAndAlertApiKeySetting()) { + return; + } + + saveNewData( + input, + model, + iterations, + firstTask, + selectedAgent, + agentMessages, + ); + va.track('Start', { + model: model.id, + agent: selectedAgent.id, + iterations: iterations.id, + }); + }; + + const finishHandler = async () => { + notifyTaskCompletion(input); + }; + + const copyHandler = () => { + copyToClipboard(getExportAgentMessage(agentBlocks)); + }; + + const downloadHandler = () => { + const filename = + objective.length > 0 + ? `${objective.replace(/\s/g, '_')}.txt` + : 'download.txt'; + downloadFile(filename, getExportAgentMessage(agentBlocks)); + }; + + const { + input, + setInput, + agentMessages, + setAgentMessages, + isRunning, + handleInputChange, + handleSubmit, + handleCancel, + reset, + } = useAgent({ + api: '/api/agent', + agentId: selectedAgent.id, + modelName: model.id, + verbose: false, + onSubmit: startHandler, + onCancel: stopHandler, + onFinish: finishHandler, + onError: errorHandler, + }); + const { clearHandler } = useResetAndDeselect(reset, selectExecution); + const { feedbackHandler } = useFeedback( + updateExec, + executions, + selectedExecutionId, + agentMessages, + setAgentMessages, + ); + const { scrollToBottom } = useScrollControl( + messagesEndRef, + agentBlocks, + isRunning, + ); + + useEffect(() => { + if (selectedExecutionId) { + const selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + if (selectedExecution.messages) { + const messages = convertToAgentMessages(selectedExecution.messages); + setAgentMessages(messages); + } else { + setAgentMessages(selectedExecution.agentMessages); + } + } + } else { + reset(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedExecutionId]); + + useEffect(() => { + setLanguage(i18n.language); + }, [i18n]); + + useEffect(() => { + const execution = executions.find((exe) => exe.id === selectedExecutionId); + if (execution) { + const updatedExecution: Execution = { + ...execution, + agentMessages, + }; + updateExec(updatedExecution); + } + + const newGroupedMessages = groupMessages(agentMessages, isRunning); + setAgentBlocks(newGroupedMessages); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [agentMessages, isRunning]); + + useEffect(() => { + scrollToBottom(); + }, [scrollToBottom]); + + return ( +
+ {agentMessages.length === 0 ? ( + <> + + {selectedAgent.id !== 'babyagi' && } +
+
+ + {(selectedAgent.id === 'babydeeragi' || + selectedAgent.id === 'babyelfagi') && ( + setInput(value)} + agent={selectedAgent.id} + /> + )} +
+
+ + ) : ( +
+ + {agentBlocks.map((block, index) => ( + + ))} + {isRunning && ( + + )} +
+
+ )} + 0} + type={selectedAgent.id} + evaluation={currentEvaluation()} + /> +
+ ); +}; diff --git a/src/components/Agent/FeedbackButtons.tsx b/src/components/Agent/FeedbackButtons.tsx new file mode 100644 index 00000000..b10b879b --- /dev/null +++ b/src/components/Agent/FeedbackButtons.tsx @@ -0,0 +1,37 @@ +import { FC } from 'react'; +import { ThumbsUp, ThumbsDown } from 'react-feather'; + +type FeedbackButtonsProps = { + handleFeedback: (value: boolean) => void; + evaluation?: 'good' | 'bad'; +}; + +export const FeedbackButtons: FC = ({ + handleFeedback, + evaluation, +}) => ( +
+ {!evaluation || evaluation === 'good' ? ( + + ) : null} + {!evaluation || evaluation === 'bad' ? ( + + ) : null} +
+); diff --git a/src/components/Agent/Input.tsx b/src/components/Agent/Input.tsx deleted file mode 100644 index f2700f45..00000000 --- a/src/components/Agent/Input.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import TextareaAutosize from 'react-textarea-autosize'; -import { AgentType } from '@/types'; -import { translate } from '@/utils/translate'; -import { - ClipboardIcon, - DividerVerticalIcon, - DotFilledIcon, - DownloadIcon, - PlayIcon, - PlusIcon, - StopIcon, - UpdateIcon, -} from '@radix-ui/react-icons'; -import { ThumbsUp, ThumbsDown } from 'react-feather'; -import { FC } from 'react'; - -type InputProps = { - value: string; - onChange: (value: string) => void; - onStart: (value: string) => void; - onStop: () => void; - onClear: () => void; - onCopy: () => void; - onDownload: () => void; - onFeedback: (value: boolean) => void; - isExecuting: boolean; - hasMessages: boolean; - agent: AgentType; - evaluation?: 'good' | 'bad'; -}; - -export const Input: FC = ({ - value, - onChange, - onStart, - onStop, - onClear, - onCopy, - onDownload, - onFeedback, - isExecuting, - hasMessages, - agent, - evaluation, -}) => { - const handleKeyDown = (e: React.KeyboardEvent) => { - if (hasMessages) { - return; - } - - if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { - e.preventDefault(); - onStart(value); - } - }; - - return ( -
-
-
-
-
- {isExecuting ? ( - - ) : hasMessages ? ( - - ) : null} -
-
- {!isExecuting && hasMessages && ( -
-
- {!evaluation || evaluation === 'good' ? ( - - ) : null} - {!evaluation || evaluation === 'bad' ? ( - - ) : null} -
-
- -
-
- - -
-
- )} -
-
-
- onChange(e.target.value)} - onKeyDown={handleKeyDown} - /> - -
- {isExecuting ? : null} -
-
-
-
- - BabyAGI UI - - {' is designed to make it easier to run and develop with '} - - babyagi - - {' in a web app, like a ChatGPT.'} -
-
- ); -}; diff --git a/src/components/Agent/IntroGuide.tsx b/src/components/Agent/IntroGuide.tsx index 6454189d..aafc9f6f 100644 --- a/src/components/Agent/IntroGuide.tsx +++ b/src/components/Agent/IntroGuide.tsx @@ -10,7 +10,6 @@ export const IntroGuide: FC = ({ onClick, agent }) => { const deerExamples = [ translate('EXAMPLE_OBJECTIVE_1', 'constants'), translate('EXAMPLE_OBJECTIVE_2', 'constants'), - translate('EXAMPLE_OBJECTIVE_3', 'constants'), ]; const elfExample = [ `${translate( diff --git a/src/components/Agent/LabelBlock.tsx b/src/components/Agent/LabelBlock.tsx new file mode 100644 index 00000000..549f7189 --- /dev/null +++ b/src/components/Agent/LabelBlock.tsx @@ -0,0 +1,73 @@ +import { useEffect, useRef } from 'react'; +import { Block } from '@/types'; +import { getEmoji, getTitle } from '@/utils/message'; +import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; +import remarkGfm from 'remark-gfm'; +import { translate } from '@/utils/translate'; + +export interface LabelBlockProps { + block: Block; +} + +export const LabelBlock: React.FC = ({ block }) => { + const { icon, type, style, title, content } = block.messages[0]; + const emoji = icon || getEmoji(type); + const blockTitle = title || getTitle(type); + const blockContent = style === 'log' ? '```\n' + content + '\n```' : content; + const sessionSummary = + block.messages[0].type === 'session-summary' + ? block.messages[0].content + : null; + + const linkRef = useRef(null); + + useEffect(() => { + if (sessionSummary) { + const file = new Blob(['\uFEFF' + sessionSummary], { + type: 'text/plain;charset=utf-8', + }); + const url = URL.createObjectURL(file); + if (linkRef.current) { + const link = linkRef.current; + link.href = url; + link.download = 'session_summary.txt'; + } + } + }, [sessionSummary]); + + const renderEmoji = () => ( +
+ {emoji} +
+ ); + + const renderContent = () => ( +
+ {sessionSummary ? ( + <> + + {`### ${blockTitle}\n`} + + + ⬇️ {translate('DOWNLOAD_SESSION_SUMMARY', 'message')} + + + ) : ( + + {`### ${blockTitle}\n${blockContent}`} + + )} +
+ ); + + return ( +
+
+
+ {renderEmoji()} + {renderContent()} +
+
+
+ ); +}; diff --git a/src/components/Agent/Markdown.tsx b/src/components/Agent/Markdown.tsx new file mode 100644 index 00000000..f7fe24d6 --- /dev/null +++ b/src/components/Agent/Markdown.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import remarkGfm from 'remark-gfm'; +import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; + +interface MarkdownProps { + content: string; +} + +const Markdown: React.FC = ({ content }) => { + return {content}; +}; + +export default Markdown; diff --git a/src/components/Agent/MessageSummary.tsx b/src/components/Agent/MessageSummary.tsx deleted file mode 100644 index 4afdcdb2..00000000 --- a/src/components/Agent/MessageSummary.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Message } from '@/types'; -import { getMessageSummaryTitle } from '@/utils/message'; -import { FC } from 'react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; - -export interface MessageSummaryProps { - message?: Message; -} - -export const MessageSummary: FC = ({ message }) => { - return ( -
- -
- - {message?.text ?? ''} - -
-
- ); -}; diff --git a/src/components/Agent/MessageSummaryCard.tsx b/src/components/Agent/MessageSummaryCard.tsx deleted file mode 100644 index 527933f4..00000000 --- a/src/components/Agent/MessageSummaryCard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as HoverCard from '@radix-ui/react-hover-card'; -import { FC } from 'react'; -import { Message } from '@/types'; -import { MessageSummary } from './MessageSummary'; -import { DrawingPinFilledIcon, InfoCircledIcon } from '@radix-ui/react-icons'; -import clsx from 'clsx'; - -export interface MessageSummaryCardProps { - messages: Message[]; -} - -export const MessageSummaryCard: FC = ({ - messages, -}) => { - const objective = messages.find((message) => message.type === 'objective'); - const task = messages - .slice() - .reverse() - .find((message) => message.type === 'next-task'); - const taskList = messages - .slice() - .reverse() - .find((message) => message.type === 'task-list'); - const list = [objective, task, taskList].filter( - (message) => message !== undefined, - ); - - const content = ( -
-
- {messages.length > 0 ? ( - list.map( - (message) => - message && ( - - ), - ) - ) : ( -
Nothing to show
- )} -
-
- ); - - return ( - - -
- -
-
- - - {content} - -
- ); -}; diff --git a/src/components/Agent/TaskBlock.tsx b/src/components/Agent/TaskBlock.tsx new file mode 100644 index 00000000..d463e837 --- /dev/null +++ b/src/components/Agent/TaskBlock.tsx @@ -0,0 +1,81 @@ +import { FC } from 'react'; +import { Block, AgentMessage } from '@/types'; +import { useSkills } from '@/hooks'; +import { AgentResult } from './AgentResult'; +import { AgentTaskStatus } from './AgentTastStatus'; +import { getEmoji } from '@/utils/message'; +import Markdown from './Markdown'; +import { AgentCollapsible } from './AgentCollapsible'; +export interface AgentTaskProps { + block: Block; +} + +const renderIcon = (message: AgentMessage, block: Block) => { + return message.style === 'log' || block.status === 'complete' + ? '' + : message.icon || getEmoji(message.type); +}; + +const renderContent = (message: AgentMessage) => { + const title = + message.status === 'complete' + ? message.content.split('\n')[0] + : message.content.trim().split('\n').slice(-1)[0]; + return message.style === 'log' ? ( + +
+ +
+
+ ) : ( +
+ +
+ ); +}; + +export const TaskBlock: FC = ({ block }) => { + const message = block.messages[0]; + const title = message.taskId + ? `${message.taskId}. ${message.title}` + : message.title; + const dependentTaskIds = message?.options?.dependentTaskIds ?? ''; + const icon = message.icon || getEmoji(message.type); + const renderMessages = block.messages.filter( + (message) => message.content !== '', + ); + + return ( +
+
+
+
+ {icon} +
+
+ {title} +
+ +
+ {renderMessages.length > 0 && ( + + {renderMessages.map((message, index) => ( +
+
+
+ {renderIcon(message, block)} +
+
{renderContent(message)}
+
+
+ ))} +
+ )} +
+
+ ); +}; diff --git a/src/components/Sidebar/ExecutionRow.tsx b/src/components/Sidebar/ExecutionRow.tsx index cfcaad02..30e79a96 100644 --- a/src/components/Sidebar/ExecutionRow.tsx +++ b/src/components/Sidebar/ExecutionRow.tsx @@ -3,7 +3,7 @@ import { useExecutionStatus } from '@/hooks/useExecutionStatus'; import { Execution } from '@/types'; import { FC } from 'react'; import { ExtraButton } from './ExtraButton'; -import { AGENT } from '@/utils/constants'; +import { ALL_AGENTS } from '@/utils/constants'; interface ExecutionRowProps { execution: Execution; @@ -23,7 +23,7 @@ export const ExecutionRow: FC = ({ execution }) => { selectExecution(undefined); }; - const agent = AGENT.find((agent) => agent.id === execution.params.agent); + const agent = ALL_AGENTS.find((agent) => agent.id === execution.params.agent); return (
)} - +
void; }; + +export type AgentMessage = { + id?: string; + content: string; + type?: string; + title?: string; + icon?: string; + taskId?: string; + style?: 'text' | 'log'; + status?: 'complete' | 'incomplete' | 'running'; + options?: { [key: string]: string }; +}; + +export type Block = { + id?: string; + messages: AgentMessage[]; + status?: 'complete' | 'incomplete' | 'running'; + style?: 'label' | 'task'; +}; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 09d54e44..334007b6 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -20,7 +20,7 @@ export const MODELS = [ ]; export const ITERATIONS = [ - { id: '0', name: 'Infinity' }, // translate('INFINITY', 'constants') this translation is not working + { id: '0', name: 'Infinity' }, { id: '1', name: '1' }, { id: '3', name: '3' }, { id: '5', name: '5' }, @@ -38,16 +38,42 @@ export const AGENT = [ id: 'babydeeragi', name: 'BabyDeerAGI', icon: '🦌', - message: '🤖/🔎+📄/🧑‍💻', badge: 'STABLE', }, { id: 'babyelfagi', name: 'BabyElfAGI', icon: '🧝', - message: '(Currently using GPT-4 for task creation)', badge: 'BETA', }, +]; + +export const ALL_AGENTS = [ + { + id: 'babyelfagi', + name: 'BabyElfAGI', + icon: '🧝', + badge: 'BETA', + }, + { + id: 'babydeeragi', + name: 'BabyDeerAGI', + icon: '🦌', + message: '🤖/🔎+📄/🧑‍💻', + badge: 'STABLE', + }, + { + id: 'babycatagi', + name: 'BabyCatAGI', + icon: '🐱', + message: '🤖/🔎+📄', + }, + { + id: 'babybeeagi', + name: 'BabyBeeAGI', + icon: '🐝', + message: '🤖/🔎/📄', + }, { id: 'babyagi', name: 'BabyAGI', icon: '👶', message: '🤖' }, ]; @@ -56,3 +82,5 @@ export const THEME = [ { id: 'light', name: 'LIGHT', icon: '🌞' }, { id: 'dark', name: 'DARK', icon: '🌚' }, ]; + +export const SPECIFIED_SKILLS = ['text_completion', 'web_search']; // for BabyDeerAGI diff --git a/src/utils/message.ts b/src/utils/message.ts index 0b876c60..583e1a6c 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -1,12 +1,15 @@ import { + AgentMessage, AgentStatus, AgentTask, Message, MessageBlock, MessageType, ToolType, + Block, } from '@/types'; import { translate } from './translate'; +import { v4 as uuidv4 } from 'uuid'; export const setupMessage = ( type: MessageType, @@ -336,3 +339,178 @@ export const getMessageBlocks = ( return messageBlocks; }; + +export const parseMessage = (json: string): AgentMessage | null => { + try { + const message = JSON.parse(json).message as AgentMessage; + + return { + ...message, + style: message.style ?? 'text', + status: message.status ?? 'incomplete', + }; + } catch (e) { + console.error( + `Failed to parse message: ${json}, length: ${json.length}`, + e, + ); + return null; + } +}; + +export const getEmoji = (type?: string) => { + switch (type) { + case 'objective': + return '🎯'; + case 'finish': + return '🏁'; + case 'task-list': + return '📝'; + case 'task': + return '📄'; + case 'session-summary': + return '📄'; + case 'result': + return '📜'; + default: + return '🤖'; + } +}; + +export const getTitle = (type?: string) => { + switch (type) { + case 'objective': + return translate('OBJECTIVE', 'message'); + case 'finish': + return translate('FINISHED', 'message'); + case 'task-list': + return translate('TASK_LIST', 'message'); + case 'task': + return translate('TASK', 'message'); + case 'session-summary': + return translate('SESSION_SUMMARY', 'message'); + case 'result': + return translate('FINAL_TASK_RESULT', 'message'); + default: + return type?.toUpperCase() || 'Untitled'; + } +}; + +export const groupMessages = (messages: AgentMessage[], isRunning: boolean) => { + const messageGroups: Block[] = []; + + let block: Block | null = null; + messages.forEach((message) => { + const id = message.taskId !== undefined ? message.taskId : message.id; + const existingBlock = messageGroups.find((block) => block.id === id); + if (!existingBlock) { + block = { + id: id, + status: message.status, + messages: [message], + style: message.taskId ? 'task' : 'label', + }; + messageGroups.push(block); + } else if ( + existingBlock && + existingBlock.id === id && + existingBlock.messages[existingBlock.messages.length - 1].type === + message.type && + existingBlock.messages[existingBlock.messages.length - 1].id === + message.id + ) { + existingBlock.messages[existingBlock.messages.length - 1].content += + message.content; + existingBlock.status = message.status; + } else { + existingBlock.messages.push(message); + existingBlock.status = message.status; + } + }); + + // if isRunning is false, set all running messageGroups to incomplete + if (!isRunning) { + messageGroups.forEach((messageGroup) => { + if (messageGroup.status === 'running') { + messageGroup.status = 'incomplete'; + } + }); + } + + return messageGroups; +}; + +export const getExportAgentMessage = (blocks: Block[]) => { + const text = blocks + .map((block) => { + const title = getTitle(block.messages[0].type); + const emoji = getEmoji(block.messages[0].type); + const messages = block.messages + .map((message) => message.content) + .join('\n'); + return `## ${emoji} ${title}\n${messages}`; + }) + .join('\n\n'); + + return text; +}; + +export const getAgentLoadingMessage = (blocks: Block[]) => { + const runningBlocks = blocks.filter((block) => block.status === 'running'); + const lastMessageTypes = runningBlocks.map( + (block) => block.messages[block.messages.length - 1].type, + ); + const blocksWithTaskId = runningBlocks.filter( + (block) => block.messages[block.messages.length - 1].taskId !== undefined, + ); + const taskExecuteIds = blocksWithTaskId.map( + (block) => block.messages[block.messages.length - 1].taskId, + ); + const execteMessage = `${translate( + 'EXECUTING', + 'message', + )} [${taskExecuteIds.join(', ')}]`; + + if (lastMessageTypes.includes('task-list')) { + return translate('CREATING', 'message'); + } else if (blocksWithTaskId.length > 0) { + return execteMessage; + } else { + return translate('THINKING', 'message'); + } +}; + +const convertToAgentMessage = (message: Message): AgentMessage => { + // 0 is for objective, 9999 is for finish + const hasTask = message.id !== 0 && message.id !== 9999 && message.id; + const style = message.type === 'search-logs' ? 'log' : 'text'; + // remove task number from task title + const taskTitle = message.text.replace(/^\d+\.\s/, ''); + // objective title + const title = + message.type === 'objective' + ? undefined + : hasTask + ? taskTitle + : message.title; + + return { + id: message.id?.toString() ?? uuidv4(), + taskId: hasTask ? message.id?.toString() : undefined, + type: message.type, + content: message.text, + title, + icon: message.icon, + style: style, + status: 'complete', + options: { + dependentTaskIds: message.dependentTaskIds?.join(', ') ?? '', + }, + }; +}; + +export const convertToAgentMessages = (messages: Message[]): AgentMessage[] => { + return messages + .filter((message) => message.type !== 'task-execute') + .map((message) => convertToAgentMessage(message)); +}; diff --git a/src/utils/objective.ts b/src/utils/objective.ts index 5fb7b4d4..1deb57e0 100644 --- a/src/utils/objective.ts +++ b/src/utils/objective.ts @@ -1,8 +1,5 @@ -import { getUserApiKey } from '@/utils/settings'; -import axios from 'axios'; import { OpenAIEmbeddings } from 'langchain/embeddings/openai'; -const CURRENT_OBJECTIVES_VERSION = '1.0.0'; const JSON_FILES = ['example3', 'example4', 'example_deer']; const JSON_FILES_FOR_DEV = [ 'example3', @@ -11,74 +8,61 @@ const JSON_FILES_FOR_DEV = [ 'example_code', 'example_code_review', ]; +const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; async function fetchJsonFiles(targetJsonFiles: string[]) { let loadedObjectives: any[] = []; for (const jsonFile of targetJsonFiles) { - const response = await fetch(`/api/json-provider?file=${jsonFile}`); - const data = await response.json(); - loadedObjectives.push(data); + const response = await fetch( + `${BASE_URL}/api/json-provider?file=${jsonFile}`, + ); + + if (!response.ok) { + console.error(`Error fetching ${jsonFile}: ${response.statusText}`); + continue; + } + + try { + const data = await response.json(); + loadedObjectives.push(data); + } catch (e) { + console.error(`Error parsing JSON for ${jsonFile}: ${e}`); + } } return loadedObjectives; } const getObjectivesExamples = async () => { - const storedObjectives = localStorage.getItem('BABYAGIUI_OBJECTIVES'); - - if (storedObjectives) { - const data = JSON.parse(storedObjectives); - if (data.version === CURRENT_OBJECTIVES_VERSION) { - return data.objectives; - } - } - const targetJsonFiles = process.env.NODE_ENV === 'development' ? JSON_FILES_FOR_DEV : JSON_FILES; const loadedObjectives = await fetchJsonFiles(targetJsonFiles); - const data = { - version: CURRENT_OBJECTIVES_VERSION, - objectives: loadedObjectives, - }; - - localStorage.setItem('BABYAGIUI_OBJECTIVES', JSON.stringify(data)); - return loadedObjectives; }; async function getEmbedding( text: string, modelName: string = 'text-embedding-ada-002', + userApiKey?: string, ) { - const openAIApiKey = getUserApiKey(); - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - if (openAIApiKey) { + try { const embedding = new OpenAIEmbeddings({ modelName, - openAIApiKey: getUserApiKey(), + openAIApiKey: userApiKey, }); return await embedding.embedQuery(text); - } else { - const response = await axios.post( - '/api/elf/embedding', - { - text: text, - model_name: modelName, - }, - { - signal: new AbortController().signal, - }, - ); - return response.data.response; + } catch (e) { + throw new Error(`error: ${e}`); } } function calculateSimilarity(embedding1: number[], embedding2: number[]) { + if (!embedding1 || !embedding2) { + throw new Error('Embedding is not defined'); + } + const dotProduct = embedding1.reduce( (sum, a, i) => sum + a * embedding2[i], 0, @@ -88,10 +72,14 @@ function calculateSimilarity(embedding1: number[], embedding2: number[]) { return dotProduct / (magnitude1 * magnitude2); } -export async function findMostRelevantObjective(userInput: string) { +export async function findMostRelevantObjective( + userInput: string, + userApiKey?: string, +) { const userInputEmbedding = await getEmbedding( userInput, 'text-embedding-ada-002', + userApiKey, ); const examples = await getObjectivesExamples(); @@ -99,17 +87,29 @@ export async function findMostRelevantObjective(userInput: string) { let mostRelevantObjective = null; for (const example of examples) { - const objectiveEmbedding = await getEmbedding(example.objective); - const similarity = calculateSimilarity( - objectiveEmbedding, - userInputEmbedding, - ); - - if (similarity > maxSimilarity) { - maxSimilarity = similarity; - mostRelevantObjective = example; + try { + const objectiveEmbedding = await getEmbedding( + example.objective, + 'text-embedding-ada-002', + userApiKey, + ); + const similarity = calculateSimilarity( + objectiveEmbedding, + userInputEmbedding, + ); + + if (similarity > maxSimilarity) { + maxSimilarity = similarity; + mostRelevantObjective = example; + } + } catch (e) { + console.error(`Error in processing example: ${e}`); } } + if (mostRelevantObjective === null) { + throw new Error(`No objective found ${examples.length}`); + } + return mostRelevantObjective; } diff --git a/src/utils/print.ts b/src/utils/print.ts index 4990248c..297ddcf2 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -1,12 +1,12 @@ -import { AgentTask, Message } from '@/types'; -import { getToolIcon, setupMessage, setupMessageWithTask } from './message'; +import { AgentTask, AgentMessage } from '@/types'; +import { v4 as uuidv4 } from 'uuid'; export class Printer { - messageCallback: (message: Message) => void; + messageCallback: (message: AgentMessage) => void; verbose: boolean = false; constructor( - messageCallback: (message: Message) => void, + messageCallback: (message: AgentMessage) => void, verbose: boolean = false, ) { this.messageCallback = messageCallback; @@ -14,88 +14,92 @@ export class Printer { } printObjective(objective: string) { - this.messageCallback(setupMessage('objective', objective)); + this.handleMessage({ + content: objective, + type: 'objective', + }); if (!this.verbose) return; - console.log('%c*****OBJECTIVE*****\n%c%s', 'color:fuchsia', '', objective); + console.log('\x1b[35m%s\x1b[0m', '*****OBJECTIVE*****\n' + objective); } printNextTask(task: AgentTask) { - const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ - task.tool - }]**`; - this.messageCallback( - setupMessage('next-task', nextTask, task.tool, undefined, task.id), - ); - if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); + console.log('\x1b[33m%s\x1b[0m', '*****NEXT TASK*****\n'); console.log(task); } printTaskExecute(task: AgentTask) { - this.messageCallback(setupMessageWithTask(task)); + this.handleMessage({ + taskId: task.id.toString(), + content: '', + type: task.skill, + title: task.task, + icon: task.icon || '', + options: { + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', + }, + status: 'running', + }); if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); + console.log('\x1b[35m%s\x1b[0m', '*****NEXT TASK*****\n'); console.log(task); } - printTaskList(taskList: AgentTask[], id?: number) { - const useSkill = taskList[0].skill !== undefined; + printTaskList(taskList: AgentTask[], id?: string) { let message = - '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - if (useSkill) { - message = - '| ID | Status | Task | Skill | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - } + '| ID | Status | Task | Skill | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; taskList.forEach((task) => { const dependentTask = task.dependentTaskIds ? `${task.dependentTaskIds.join(', ')}` : '-'; const status = task.status === 'complete' ? '✅' : '⬜️'; - if (useSkill) { - message += `| ${task.id} | ${status} | ${task.task} | ${task.icon} | ${dependentTask} |\n`; - return; - } - message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( - task.tool, - )} | ${dependentTask} |\n`; + message += `| ${task.id} | ${status} | ${task.task} | ${task.icon} | ${dependentTask} |\n`; }); - this.messageCallback( - setupMessage('task-list', message, undefined, `📝`, id), - ); + this.messageCallback({ + id, + content: message, + type: 'task-list', + style: 'text', + status: 'complete', + }); if (!this.verbose) return; - console.log('%c*****TASK LIST*****\n%c%s', 'color:fuchsia', '', message); + console.log('\x1b[34m%s\x1b[0m', '*****TASK LIST*****\n' + message); } printTaskOutput(output: string, task: AgentTask) { - if (task.tool !== 'text-completion') { - // code block for non-text-completion tools - // output = '```\n' + output + '\n```'; - } - this.messageCallback( - setupMessage('task-output', output, task?.tool, undefined, task?.id), - ); - if (!this.verbose) return; - console.log('%c*****TASK OUTPUT*****\n%c%s', 'color:fuchsia', '', output); + console.log('\x1b[32m%s\x1b[0m', '*****TASK OUTPUT*****\n' + output); } printTaskCompleted() { - this.messageCallback(setupMessage('done', 'Done!')); + // this.messageCallback(setupMessage('done', 'Done!')); if (!this.verbose) return; - console.log('%c*****DONE*****\n%c', 'color:fuchsia', ''); + console.log('\x1b[35m%s\x1b[0m', '*****DONE*****\n'); } printAllTaskCompleted() { - this.messageCallback(setupMessage('complete', 'All tasks completed!')); + this.handleMessage({ + content: 'All task completed!', + type: 'finish', + }); if (!this.verbose) return; - console.log('%c*****ALL TASK COMPLETED*****%c', 'color:fuchsia', ''); + console.log('\x1b[36m%s\x1b[0m', '*****ALL TASK COMPLETED*****\n'); + } + + // handleMessage() is called by the agent to send a message to the frontend + async handleMessage(message: AgentMessage) { + const msg = { + ...message, + id: message.id || uuidv4(), + status: message.status || 'complete', + }; + await this.messageCallback(msg); } } diff --git a/src/agents/babydeeragi/prompt.ts b/src/utils/prompt.ts similarity index 100% rename from src/agents/babydeeragi/prompt.ts rename to src/utils/prompt.ts