mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Merge branch 'dev' into swiftyos/open-2278-implement-agent-preset-functionality
This commit is contained in:
116
autogpt_platform/autogpt_libs/poetry.lock
generated
116
autogpt_platform/autogpt_libs/poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
@@ -6,6 +6,7 @@ version = "2.4.0"
|
||||
description = "Happy Eyeballs for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"},
|
||||
{file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"},
|
||||
@@ -17,6 +18,7 @@ version = "3.10.5"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"},
|
||||
{file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"},
|
||||
@@ -129,6 +131,7 @@ version = "1.3.1"
|
||||
description = "aiosignal: a list of registered asynchronous callbacks"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
|
||||
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
|
||||
@@ -143,6 +146,7 @@ version = "0.7.0"
|
||||
description = "Reusable constraint types to use with typing.Annotated"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||
@@ -154,6 +158,7 @@ version = "4.4.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
|
||||
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
|
||||
@@ -176,10 +181,12 @@ version = "4.0.3"
|
||||
description = "Timeout context manager for asyncio programs"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
||||
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
||||
]
|
||||
markers = {main = "python_version < \"3.11\"", dev = "python_full_version < \"3.11.3\""}
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
@@ -187,6 +194,7 @@ version = "24.2.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
|
||||
{file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
|
||||
@@ -206,6 +214,7 @@ version = "5.5.0"
|
||||
description = "Extensible memoizing collections and decorators"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"},
|
||||
{file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"},
|
||||
@@ -217,6 +226,7 @@ version = "2024.8.30"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
|
||||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
@@ -228,6 +238,7 @@ version = "3.3.2"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
|
||||
@@ -327,6 +338,7 @@ version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
@@ -338,6 +350,7 @@ version = "1.2.14"
|
||||
description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"},
|
||||
{file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"},
|
||||
@@ -355,6 +368,7 @@ version = "2.1.0"
|
||||
description = "A library to handle automated deprecations"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"},
|
||||
{file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"},
|
||||
@@ -369,6 +383,8 @@ version = "1.2.2"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
@@ -383,6 +399,7 @@ version = "1.2.2"
|
||||
description = "Dictionary with auto-expiring values for caching purposes"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "expiringdict-1.2.2-py3-none-any.whl", hash = "sha256:09a5d20bc361163e6432a874edd3179676e935eb81b925eccef48d409a8a45e8"},
|
||||
{file = "expiringdict-1.2.2.tar.gz", hash = "sha256:300fb92a7e98f15b05cf9a856c1415b3bc4f2e132be07daa326da6414c23ee09"},
|
||||
@@ -397,6 +414,7 @@ version = "1.4.1"
|
||||
description = "A list-like structure which implements collections.abc.MutableSequence"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"},
|
||||
{file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"},
|
||||
@@ -483,6 +501,7 @@ version = "2.19.2"
|
||||
description = "Google API client core library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google_api_core-2.19.2-py3-none-any.whl", hash = "sha256:53ec0258f2837dd53bbd3d3df50f5359281b3cc13f800c941dd15a9b5a415af4"},
|
||||
{file = "google_api_core-2.19.2.tar.gz", hash = "sha256:ca07de7e8aa1c98a8bfca9321890ad2340ef7f2eb136e558cee68f24b94b0a8f"},
|
||||
@@ -514,6 +533,7 @@ version = "2.34.0"
|
||||
description = "Google Authentication Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google_auth-2.34.0-py2.py3-none-any.whl", hash = "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65"},
|
||||
{file = "google_auth-2.34.0.tar.gz", hash = "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc"},
|
||||
@@ -537,6 +557,7 @@ version = "1.4.5"
|
||||
description = "Google Cloud Appengine Logging API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google_cloud_appengine_logging-1.4.5-py2.py3-none-any.whl", hash = "sha256:344e0244404049b42164e4d6dc718ca2c81b393d066956e7cb85fd9407ed9c48"},
|
||||
{file = "google_cloud_appengine_logging-1.4.5.tar.gz", hash = "sha256:de7d766e5d67b19fc5833974b505b32d2a5bbdfb283fd941e320e7cfdae4cb83"},
|
||||
@@ -554,6 +575,7 @@ version = "0.3.0"
|
||||
description = "Google Cloud Audit Protos"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google_cloud_audit_log-0.3.0-py2.py3-none-any.whl", hash = "sha256:8340793120a1d5aa143605def8704ecdcead15106f754ef1381ae3bab533722f"},
|
||||
{file = "google_cloud_audit_log-0.3.0.tar.gz", hash = "sha256:901428b257020d8c1d1133e0fa004164a555e5a395c7ca3cdbb8486513df3a65"},
|
||||
@@ -569,6 +591,7 @@ version = "2.4.1"
|
||||
description = "Google Cloud API client core library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"},
|
||||
{file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"},
|
||||
@@ -583,13 +606,14 @@ grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-cloud-logging"
|
||||
version = "3.11.3"
|
||||
version = "3.11.4"
|
||||
description = "Stackdriver Logging API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google_cloud_logging-3.11.3-py2.py3-none-any.whl", hash = "sha256:b8ec23f2998f76a58f8492db26a0f4151dd500425c3f08448586b85972f3c494"},
|
||||
{file = "google_cloud_logging-3.11.3.tar.gz", hash = "sha256:0a73cd94118875387d4535371d9e9426861edef8e44fba1261e86782d5b8d54f"},
|
||||
{file = "google_cloud_logging-3.11.4-py2.py3-none-any.whl", hash = "sha256:1d465ac62df29fb94bba4d6b4891035e57d573d84541dd8a40eebbc74422b2f0"},
|
||||
{file = "google_cloud_logging-3.11.4.tar.gz", hash = "sha256:32305d989323f3c58603044e2ac5d9cf23e9465ede511bbe90b4309270d3195c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -601,7 +625,8 @@ google-cloud-core = ">=2.0.0,<3.0.0dev"
|
||||
grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev"
|
||||
opentelemetry-api = ">=1.9.0"
|
||||
proto-plus = [
|
||||
{version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""},
|
||||
{version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""},
|
||||
{version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\" and python_version < \"3.13\""},
|
||||
{version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""},
|
||||
]
|
||||
protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev"
|
||||
@@ -612,6 +637,7 @@ version = "1.65.0"
|
||||
description = "Common protobufs used in Google APIs"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"},
|
||||
{file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"},
|
||||
@@ -630,6 +656,7 @@ version = "2.11.1"
|
||||
description = "Python Client Library for Supabase Auth"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "gotrue-2.11.1-py3-none-any.whl", hash = "sha256:1b2d915bdc65fd0ad608532759ce9c72fa2e910145c1e6901f2188519e7bcd2d"},
|
||||
{file = "gotrue-2.11.1.tar.gz", hash = "sha256:5594ceee60bd873e5f4fdd028b08dece3906f6013b6ed08e7786b71c0092fed0"},
|
||||
@@ -645,6 +672,7 @@ version = "0.13.1"
|
||||
description = "IAM API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "grpc-google-iam-v1-0.13.1.tar.gz", hash = "sha256:3ff4b2fd9d990965e410965253c0da6f66205d5a8291c4c31c6ebecca18a9001"},
|
||||
{file = "grpc_google_iam_v1-0.13.1-py2.py3-none-any.whl", hash = "sha256:c3e86151a981811f30d5e7330f271cee53e73bb87755e88cc3b6f0c7b5fe374e"},
|
||||
@@ -661,6 +689,7 @@ version = "1.66.1"
|
||||
description = "HTTP/2-based RPC framework"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "grpcio-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:4877ba180591acdf127afe21ec1c7ff8a5ecf0fe2600f0d3c50e8c4a1cbc6492"},
|
||||
{file = "grpcio-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3750c5a00bd644c75f4507f77a804d0189d97a107eb1481945a0cf3af3e7a5ac"},
|
||||
@@ -719,6 +748,7 @@ version = "1.66.1"
|
||||
description = "Status proto mapping for gRPC"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "grpcio_status-1.66.1-py3-none-any.whl", hash = "sha256:cf9ed0b4a83adbe9297211c95cb5488b0cd065707e812145b842c85c4782ff02"},
|
||||
{file = "grpcio_status-1.66.1.tar.gz", hash = "sha256:b3f7d34ccc46d83fea5261eea3786174459f763c31f6e34f1d24eba6d515d024"},
|
||||
@@ -735,6 +765,7 @@ version = "0.14.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
@@ -746,6 +777,7 @@ version = "4.1.0"
|
||||
description = "HTTP/2 State-Machine based protocol implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
|
||||
{file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
|
||||
@@ -761,6 +793,7 @@ version = "4.0.0"
|
||||
description = "Pure-Python HPACK header compression"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
|
||||
{file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
|
||||
@@ -772,6 +805,7 @@ version = "1.0.5"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
|
||||
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
|
||||
@@ -793,6 +827,7 @@ version = "0.27.2"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
|
||||
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
|
||||
@@ -819,6 +854,7 @@ version = "6.0.1"
|
||||
description = "HTTP/2 framing layer for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
|
||||
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
|
||||
@@ -830,6 +866,7 @@ version = "3.8"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"},
|
||||
{file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"},
|
||||
@@ -841,6 +878,7 @@ version = "8.4.0"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"},
|
||||
{file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"},
|
||||
@@ -860,6 +898,7 @@ version = "2.0.0"
|
||||
description = "brain-dead simple config-ini parsing"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||
@@ -871,6 +910,7 @@ version = "6.1.0"
|
||||
description = "multidict implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"},
|
||||
{file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"},
|
||||
@@ -975,6 +1015,7 @@ version = "1.27.0"
|
||||
description = "OpenTelemetry Python API"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7"},
|
||||
{file = "opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342"},
|
||||
@@ -990,6 +1031,7 @@ version = "24.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
@@ -1001,6 +1043,7 @@ version = "1.5.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
@@ -1016,6 +1059,7 @@ version = "0.19.1"
|
||||
description = "PostgREST client for Python. This library provides an ORM interface to PostgREST."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "postgrest-0.19.1-py3-none-any.whl", hash = "sha256:a8e7be4e1abc69fd8eee5a49d7dc3a76dfbffbd778beed0b2bd7accb3f4f3a2a"},
|
||||
{file = "postgrest-0.19.1.tar.gz", hash = "sha256:d8fa88953cced4f45efa0f412056c364f64ece8a35b5b35f458a7e58c133fbca"},
|
||||
@@ -1029,13 +1073,14 @@ strenum = {version = ">=0.4.9,<0.5.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "proto-plus"
|
||||
version = "1.24.0"
|
||||
description = "Beautiful, Pythonic protocol buffers."
|
||||
version = "1.26.0"
|
||||
description = "Beautiful, Pythonic protocol buffers"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"},
|
||||
{file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"},
|
||||
{file = "proto_plus-1.26.0-py3-none-any.whl", hash = "sha256:bf2dfaa3da281fc3187d12d224c707cb57214fb2c22ba854eb0c105a3fb2d4d7"},
|
||||
{file = "proto_plus-1.26.0.tar.gz", hash = "sha256:6e93d5f5ca267b54300880fff156b6a3386b3fa3f43b1da62e680fc0c586ef22"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1050,6 +1095,7 @@ version = "5.28.0"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "protobuf-5.28.0-cp310-abi3-win32.whl", hash = "sha256:66c3edeedb774a3508ae70d87b3a19786445fe9a068dd3585e0cefa8a77b83d0"},
|
||||
{file = "protobuf-5.28.0-cp310-abi3-win_amd64.whl", hash = "sha256:6d7cc9e60f976cf3e873acb9a40fed04afb5d224608ed5c1a105db4a3f09c5b6"},
|
||||
@@ -1070,6 +1116,7 @@ version = "0.6.1"
|
||||
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
|
||||
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
|
||||
@@ -1081,6 +1128,7 @@ version = "0.4.1"
|
||||
description = "A collection of ASN.1-based protocols modules"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"},
|
||||
{file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"},
|
||||
@@ -1091,13 +1139,14 @@ pyasn1 = ">=0.4.6,<0.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.5"
|
||||
version = "2.10.6"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"},
|
||||
{file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"},
|
||||
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
|
||||
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1115,6 +1164,7 @@ version = "2.27.2"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
||||
@@ -1227,6 +1277,7 @@ version = "2.7.1"
|
||||
description = "Settings management using Pydantic"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd"},
|
||||
{file = "pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93"},
|
||||
@@ -1247,6 +1298,7 @@ version = "2.10.1"
|
||||
description = "JSON Web Token implementation in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"},
|
||||
{file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"},
|
||||
@@ -1264,6 +1316,7 @@ version = "8.3.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
|
||||
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
|
||||
@@ -1282,13 +1335,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.25.2"
|
||||
version = "0.25.3"
|
||||
description = "Pytest support for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075"},
|
||||
{file = "pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f"},
|
||||
{file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"},
|
||||
{file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1304,6 +1358,7 @@ version = "3.14.0"
|
||||
description = "Thin-wrapper around the mock package for easier use with pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
|
||||
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
|
||||
@@ -1321,6 +1376,7 @@ version = "2.9.0.post0"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
|
||||
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
|
||||
@@ -1335,6 +1391,7 @@ version = "1.0.1"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
|
||||
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
|
||||
@@ -1349,6 +1406,7 @@ version = "2.0.2"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "realtime-2.0.2-py3-none-any.whl", hash = "sha256:2634c915bc38807f2013f21e8bcc4d2f79870dfd81460ddb9393883d0489928a"},
|
||||
{file = "realtime-2.0.2.tar.gz", hash = "sha256:519da9325b3b8102139d51785013d592f6b2403d81fa21d838a0b0234723ed7d"},
|
||||
@@ -1366,6 +1424,7 @@ version = "5.2.1"
|
||||
description = "Python client for Redis database and key-value store"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"},
|
||||
{file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"},
|
||||
@@ -1384,6 +1443,7 @@ version = "2.32.3"
|
||||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||
@@ -1405,6 +1465,7 @@ version = "4.9"
|
||||
description = "Pure-Python RSA implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
|
||||
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
|
||||
@@ -1419,6 +1480,7 @@ version = "0.9.3"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"},
|
||||
{file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"},
|
||||
@@ -1446,6 +1508,7 @@ version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
@@ -1457,6 +1520,7 @@ version = "1.3.1"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
@@ -1468,6 +1532,7 @@ version = "0.11.0"
|
||||
description = "Supabase Storage client for Python."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "storage3-0.11.0-py3-none-any.whl", hash = "sha256:de2d8f9c9103ca91a9a9d0d69d80b07a3ab6f647b93e023e6a1a97d3607b9728"},
|
||||
{file = "storage3-0.11.0.tar.gz", hash = "sha256:243583f2180686c0f0a19e6117d8a9796fd60c0ca72ec567d62b75a5af0d57a1"},
|
||||
@@ -1483,6 +1548,7 @@ version = "0.4.15"
|
||||
description = "An Enum that inherits from str."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"},
|
||||
{file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"},
|
||||
@@ -1495,13 +1561,14 @@ test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
|
||||
|
||||
[[package]]
|
||||
name = "supabase"
|
||||
version = "2.11.0"
|
||||
version = "2.13.0"
|
||||
description = "Supabase client for Python."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "supabase-2.11.0-py3-none-any.whl", hash = "sha256:67a0da498895f4cd6554935e2854b4c41f87b297b78fb9c9414902a382041406"},
|
||||
{file = "supabase-2.11.0.tar.gz", hash = "sha256:2a906f7909fd9a50f944cd9332ce66c684e2d37c0864284d34c5815e6c63cc01"},
|
||||
{file = "supabase-2.13.0-py3-none-any.whl", hash = "sha256:6cfccc055be21dab311afc5e9d5b37f3a4966f8394703763fbc8f8e86f36eaa6"},
|
||||
{file = "supabase-2.13.0.tar.gz", hash = "sha256:452574d34bd978c8d11b5f02b0182b48e8854e511c969483c83875ec01495f11"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1518,6 +1585,7 @@ version = "0.9.2"
|
||||
description = "Library for Supabase Functions"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "supafunc-0.9.2-py3-none-any.whl", hash = "sha256:be5ee9f53842c4b0ba5f4abfb5bddf9f9e37e69e755ec0526852bb15af9d2ff5"},
|
||||
{file = "supafunc-0.9.2.tar.gz", hash = "sha256:f5164114a3e65e7e552539f3f1050aa3d4970885abdd7405555c17fd216e2da1"},
|
||||
@@ -1533,6 +1601,8 @@ version = "2.1.0"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
|
||||
{file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
|
||||
@@ -1544,6 +1614,7 @@ version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
@@ -1555,6 +1626,7 @@ version = "2.2.2"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
@@ -1572,6 +1644,7 @@ version = "12.0"
|
||||
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"},
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"},
|
||||
@@ -1653,6 +1726,7 @@ version = "1.16.0"
|
||||
description = "Module for decorators, wrappers and monkey patching."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"},
|
||||
{file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"},
|
||||
@@ -1732,6 +1806,7 @@ version = "1.11.1"
|
||||
description = "Yet another URL library"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"},
|
||||
{file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"},
|
||||
@@ -1837,6 +1912,7 @@ version = "3.20.1"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"},
|
||||
{file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"},
|
||||
@@ -1851,6 +1927,6 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<4.0"
|
||||
content-hash = "62c4ef3f1ae73546a66783a2dc0672e664ce415a7b0514a7b92fc9fe1a23239e"
|
||||
content-hash = "a4d81b3b55a67036ca7a441793e13e8fbe20af973fcf1623f36cdee7bc82999f"
|
||||
|
||||
@@ -9,15 +9,15 @@ packages = [{ include = "autogpt_libs" }]
|
||||
[tool.poetry.dependencies]
|
||||
colorama = "^0.4.6"
|
||||
expiringdict = "^1.2.2"
|
||||
google-cloud-logging = "^3.11.3"
|
||||
pydantic = "^2.10.5"
|
||||
google-cloud-logging = "^3.11.4"
|
||||
pydantic = "^2.10.6"
|
||||
pydantic-settings = "^2.7.1"
|
||||
pyjwt = "^2.10.1"
|
||||
pytest-asyncio = "^0.25.2"
|
||||
pytest-asyncio = "^0.25.3"
|
||||
pytest-mock = "^3.14.0"
|
||||
python = ">=3.10,<4.0"
|
||||
python-dotenv = "^1.0.1"
|
||||
supabase = "^2.11.0"
|
||||
supabase = "^2.13.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
redis = "^5.2.1"
|
||||
|
||||
@@ -31,6 +31,12 @@ SUPABASE_URL=http://localhost:8000
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
|
||||
SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
|
||||
|
||||
# RabbitMQ credentials -- Used for communication between services
|
||||
RABBITMQ_HOST=localhost
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_DEFAULT_USER=rabbitmq_user_default
|
||||
RABBITMQ_DEFAULT_PASS=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
|
||||
|
||||
## For local development, you may need to set FRONTEND_BASE_URL for the OAuth flow
|
||||
## for integrations to work. Defaults to the value of PLATFORM_BASE_URL if not set.
|
||||
# FRONTEND_BASE_URL=http://localhost:3000
|
||||
|
||||
@@ -297,6 +297,7 @@ class AgentOutputBlock(Block):
|
||||
|
||||
class Output(BlockSchema):
|
||||
output: Any = SchemaField(description="The value recorded as output.")
|
||||
name: Any = SchemaField(description="The name of the value recorded as output.")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
@@ -348,6 +349,7 @@ class AgentOutputBlock(Block):
|
||||
yield "output", f"Error: {e}, {input_data.value}"
|
||||
else:
|
||||
yield "output", input_data.value
|
||||
yield "name", input_data.name
|
||||
|
||||
|
||||
class AddToDictionaryBlock(Block):
|
||||
|
||||
@@ -69,6 +69,7 @@ def AICredentialsField() -> AICredentials:
|
||||
class ModelMetadata(NamedTuple):
|
||||
provider: str
|
||||
context_window: int
|
||||
max_output_tokens: int | None
|
||||
|
||||
|
||||
class LlmModelMeta(EnumMeta):
|
||||
@@ -92,6 +93,8 @@ class LlmModelMeta(EnumMeta):
|
||||
|
||||
class LlmModel(str, Enum, metaclass=LlmModelMeta):
|
||||
# OpenAI models
|
||||
O3_MINI = "o3-mini"
|
||||
O1 = "o1"
|
||||
O1_PREVIEW = "o1-preview"
|
||||
O1_MINI = "o1-mini"
|
||||
GPT4O_MINI = "gpt-4o-mini"
|
||||
@@ -100,30 +103,31 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
|
||||
GPT3_5_TURBO = "gpt-3.5-turbo"
|
||||
# Anthropic models
|
||||
CLAUDE_3_5_SONNET = "claude-3-5-sonnet-latest"
|
||||
CLAUDE_3_5_HAIKU = "claude-3-5-haiku-latest"
|
||||
CLAUDE_3_HAIKU = "claude-3-haiku-20240307"
|
||||
# Groq models
|
||||
LLAMA3_8B = "llama3-8b-8192"
|
||||
LLAMA3_70B = "llama3-70b-8192"
|
||||
MIXTRAL_8X7B = "mixtral-8x7b-32768"
|
||||
GEMMA_7B = "gemma-7b-it"
|
||||
GEMMA2_9B = "gemma2-9b-it"
|
||||
# New Groq models (Preview)
|
||||
LLAMA3_1_405B = "llama-3.1-405b-reasoning"
|
||||
LLAMA3_1_70B = "llama-3.1-70b-versatile"
|
||||
LLAMA3_3_70B = "llama-3.3-70b-versatile"
|
||||
LLAMA3_1_8B = "llama-3.1-8b-instant"
|
||||
LLAMA3_70B = "llama3-70b-8192"
|
||||
LLAMA3_8B = "llama3-8b-8192"
|
||||
MIXTRAL_8X7B = "mixtral-8x7b-32768"
|
||||
# Groq preview models
|
||||
DEEPSEEK_LLAMA_70B = "deepseek-r1-distill-llama-70b"
|
||||
# Ollama models
|
||||
OLLAMA_LLAMA3_3 = "llama3.3"
|
||||
OLLAMA_LLAMA3_2 = "llama3.2"
|
||||
OLLAMA_LLAMA3_8B = "llama3"
|
||||
OLLAMA_LLAMA3_405B = "llama3.1:405b"
|
||||
OLLAMA_DOLPHIN = "dolphin-mistral:latest"
|
||||
# OpenRouter models
|
||||
GEMINI_FLASH_1_5_8B = "google/gemini-flash-1.5"
|
||||
GEMINI_FLASH_1_5 = "google/gemini-flash-1.5"
|
||||
GROK_BETA = "x-ai/grok-beta"
|
||||
MISTRAL_NEMO = "mistralai/mistral-nemo"
|
||||
COHERE_COMMAND_R_08_2024 = "cohere/command-r-08-2024"
|
||||
COHERE_COMMAND_R_PLUS_08_2024 = "cohere/command-r-plus-08-2024"
|
||||
EVA_QWEN_2_5_32B = "eva-unit-01/eva-qwen-2.5-32b"
|
||||
DEEPSEEK_CHAT = "deepseek/deepseek-chat"
|
||||
DEEPSEEK_CHAT = "deepseek/deepseek-chat" # Actually: DeepSeek V3
|
||||
PERPLEXITY_LLAMA_3_1_SONAR_LARGE_128K_ONLINE = (
|
||||
"perplexity/llama-3.1-sonar-large-128k-online"
|
||||
)
|
||||
@@ -148,47 +152,74 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
|
||||
def context_window(self) -> int:
|
||||
return self.metadata.context_window
|
||||
|
||||
@property
|
||||
def max_output_tokens(self) -> int | None:
|
||||
return self.metadata.max_output_tokens
|
||||
|
||||
|
||||
MODEL_METADATA = {
|
||||
LlmModel.O1_PREVIEW: ModelMetadata("openai", 32000),
|
||||
LlmModel.O1_MINI: ModelMetadata("openai", 62000),
|
||||
LlmModel.GPT4O_MINI: ModelMetadata("openai", 128000),
|
||||
LlmModel.GPT4O: ModelMetadata("openai", 128000),
|
||||
LlmModel.GPT4_TURBO: ModelMetadata("openai", 128000),
|
||||
LlmModel.GPT3_5_TURBO: ModelMetadata("openai", 16385),
|
||||
LlmModel.CLAUDE_3_5_SONNET: ModelMetadata("anthropic", 200000),
|
||||
LlmModel.CLAUDE_3_HAIKU: ModelMetadata("anthropic", 200000),
|
||||
LlmModel.LLAMA3_8B: ModelMetadata("groq", 8192),
|
||||
LlmModel.LLAMA3_70B: ModelMetadata("groq", 8192),
|
||||
LlmModel.MIXTRAL_8X7B: ModelMetadata("groq", 32768),
|
||||
LlmModel.GEMMA_7B: ModelMetadata("groq", 8192),
|
||||
LlmModel.GEMMA2_9B: ModelMetadata("groq", 8192),
|
||||
LlmModel.LLAMA3_1_405B: ModelMetadata("groq", 8192),
|
||||
# Limited to 16k during preview
|
||||
LlmModel.LLAMA3_1_70B: ModelMetadata("groq", 131072),
|
||||
LlmModel.LLAMA3_1_8B: ModelMetadata("groq", 131072),
|
||||
LlmModel.OLLAMA_LLAMA3_2: ModelMetadata("ollama", 8192),
|
||||
LlmModel.OLLAMA_LLAMA3_8B: ModelMetadata("ollama", 8192),
|
||||
LlmModel.OLLAMA_LLAMA3_405B: ModelMetadata("ollama", 8192),
|
||||
LlmModel.OLLAMA_DOLPHIN: ModelMetadata("ollama", 32768),
|
||||
LlmModel.GEMINI_FLASH_1_5_8B: ModelMetadata("open_router", 8192),
|
||||
LlmModel.GROK_BETA: ModelMetadata("open_router", 8192),
|
||||
LlmModel.MISTRAL_NEMO: ModelMetadata("open_router", 4000),
|
||||
LlmModel.COHERE_COMMAND_R_08_2024: ModelMetadata("open_router", 4000),
|
||||
LlmModel.COHERE_COMMAND_R_PLUS_08_2024: ModelMetadata("open_router", 4000),
|
||||
LlmModel.EVA_QWEN_2_5_32B: ModelMetadata("open_router", 4000),
|
||||
LlmModel.DEEPSEEK_CHAT: ModelMetadata("open_router", 8192),
|
||||
# https://platform.openai.com/docs/models
|
||||
LlmModel.O3_MINI: ModelMetadata("openai", 200000, 100000), # o3-mini-2025-01-31
|
||||
LlmModel.O1: ModelMetadata("openai", 200000, 100000), # o1-2024-12-17
|
||||
LlmModel.O1_PREVIEW: ModelMetadata(
|
||||
"openai", 128000, 32768
|
||||
), # o1-preview-2024-09-12
|
||||
LlmModel.O1_MINI: ModelMetadata("openai", 128000, 65536), # o1-mini-2024-09-12
|
||||
LlmModel.GPT4O_MINI: ModelMetadata(
|
||||
"openai", 128000, 16384
|
||||
), # gpt-4o-mini-2024-07-18
|
||||
LlmModel.GPT4O: ModelMetadata("openai", 128000, 16384), # gpt-4o-2024-08-06
|
||||
LlmModel.GPT4_TURBO: ModelMetadata(
|
||||
"openai", 128000, 4096
|
||||
), # gpt-4-turbo-2024-04-09
|
||||
LlmModel.GPT3_5_TURBO: ModelMetadata("openai", 16385, 4096), # gpt-3.5-turbo-0125
|
||||
# https://docs.anthropic.com/en/docs/about-claude/models
|
||||
LlmModel.CLAUDE_3_5_SONNET: ModelMetadata(
|
||||
"anthropic", 200000, 8192
|
||||
), # claude-3-5-sonnet-20241022
|
||||
LlmModel.CLAUDE_3_5_HAIKU: ModelMetadata(
|
||||
"anthropic", 200000, 8192
|
||||
), # claude-3-5-haiku-20241022
|
||||
LlmModel.CLAUDE_3_HAIKU: ModelMetadata(
|
||||
"anthropic", 200000, 4096
|
||||
), # claude-3-haiku-20240307
|
||||
# https://console.groq.com/docs/models
|
||||
LlmModel.GEMMA2_9B: ModelMetadata("groq", 8192, None),
|
||||
LlmModel.LLAMA3_3_70B: ModelMetadata("groq", 128000, 32768),
|
||||
LlmModel.LLAMA3_1_8B: ModelMetadata("groq", 128000, 8192),
|
||||
LlmModel.LLAMA3_70B: ModelMetadata("groq", 8192, None),
|
||||
LlmModel.LLAMA3_8B: ModelMetadata("groq", 8192, None),
|
||||
LlmModel.MIXTRAL_8X7B: ModelMetadata("groq", 32768, None),
|
||||
LlmModel.DEEPSEEK_LLAMA_70B: ModelMetadata("groq", 128000, None),
|
||||
# https://ollama.com/library
|
||||
LlmModel.OLLAMA_LLAMA3_3: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_LLAMA3_2: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_LLAMA3_8B: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_LLAMA3_405B: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_DOLPHIN: ModelMetadata("ollama", 32768, None),
|
||||
# https://openrouter.ai/models
|
||||
LlmModel.GEMINI_FLASH_1_5: ModelMetadata("open_router", 1000000, 8192),
|
||||
LlmModel.GROK_BETA: ModelMetadata("open_router", 131072, 131072),
|
||||
LlmModel.MISTRAL_NEMO: ModelMetadata("open_router", 128000, 4096),
|
||||
LlmModel.COHERE_COMMAND_R_08_2024: ModelMetadata("open_router", 128000, 4096),
|
||||
LlmModel.COHERE_COMMAND_R_PLUS_08_2024: ModelMetadata("open_router", 128000, 4096),
|
||||
LlmModel.EVA_QWEN_2_5_32B: ModelMetadata("open_router", 16384, 4096),
|
||||
LlmModel.DEEPSEEK_CHAT: ModelMetadata("open_router", 64000, 2048),
|
||||
LlmModel.PERPLEXITY_LLAMA_3_1_SONAR_LARGE_128K_ONLINE: ModelMetadata(
|
||||
"open_router", 8192
|
||||
"open_router", 127072, 127072
|
||||
),
|
||||
LlmModel.QWEN_QWQ_32B_PREVIEW: ModelMetadata("open_router", 4000),
|
||||
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B: ModelMetadata("open_router", 4000),
|
||||
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B: ModelMetadata("open_router", 4000),
|
||||
LlmModel.AMAZON_NOVA_LITE_V1: ModelMetadata("open_router", 4000),
|
||||
LlmModel.AMAZON_NOVA_MICRO_V1: ModelMetadata("open_router", 4000),
|
||||
LlmModel.AMAZON_NOVA_PRO_V1: ModelMetadata("open_router", 4000),
|
||||
LlmModel.MICROSOFT_WIZARDLM_2_8X22B: ModelMetadata("open_router", 4000),
|
||||
LlmModel.GRYPHE_MYTHOMAX_L2_13B: ModelMetadata("open_router", 4000),
|
||||
LlmModel.QWEN_QWQ_32B_PREVIEW: ModelMetadata("open_router", 32768, 32768),
|
||||
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B: ModelMetadata(
|
||||
"open_router", 131000, 4096
|
||||
),
|
||||
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B: ModelMetadata(
|
||||
"open_router", 12288, 12288
|
||||
),
|
||||
LlmModel.AMAZON_NOVA_LITE_V1: ModelMetadata("open_router", 300000, 5120),
|
||||
LlmModel.AMAZON_NOVA_MICRO_V1: ModelMetadata("open_router", 128000, 5120),
|
||||
LlmModel.AMAZON_NOVA_PRO_V1: ModelMetadata("open_router", 300000, 5120),
|
||||
LlmModel.MICROSOFT_WIZARDLM_2_8X22B: ModelMetadata("open_router", 65536, 4096),
|
||||
LlmModel.GRYPHE_MYTHOMAX_L2_13B: ModelMetadata("open_router", 4096, 4096),
|
||||
}
|
||||
|
||||
for model in LlmModel:
|
||||
@@ -314,7 +345,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase):
|
||||
llm_model: LlmModel,
|
||||
prompt: list[dict],
|
||||
json_format: bool,
|
||||
max_tokens: int | None = None,
|
||||
max_tokens: int | None,
|
||||
ollama_host: str = "localhost:11434",
|
||||
) -> tuple[str, int, int]:
|
||||
"""
|
||||
@@ -332,6 +363,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase):
|
||||
The number of tokens used in the completion.
|
||||
"""
|
||||
provider = llm_model.metadata.provider
|
||||
max_tokens = max_tokens or llm_model.max_output_tokens or 4096
|
||||
|
||||
if provider == "openai":
|
||||
oai_client = openai.OpenAI(api_key=credentials.api_key.get_secret_value())
|
||||
@@ -381,7 +413,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase):
|
||||
model=llm_model.value,
|
||||
system=sysprompt,
|
||||
messages=messages,
|
||||
max_tokens=max_tokens or 8192,
|
||||
max_tokens=max_tokens,
|
||||
)
|
||||
self.prompt = json.dumps(prompt)
|
||||
|
||||
|
||||
174
autogpt_platform/backend/backend/blocks/screenshotone.py
Normal file
174
autogpt_platform/backend/backend/blocks/screenshotone.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from base64 import b64encode
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import (
|
||||
APIKeyCredentials,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
SchemaField,
|
||||
)
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.file import MediaFile, store_media_file
|
||||
from backend.util.request import Requests
|
||||
|
||||
|
||||
class Format(str, Enum):
|
||||
PNG = "png"
|
||||
JPEG = "jpeg"
|
||||
WEBP = "webp"
|
||||
|
||||
|
||||
class ScreenshotWebPageBlock(Block):
|
||||
"""Block for taking screenshots using ScreenshotOne API"""
|
||||
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput[
|
||||
Literal[ProviderName.SCREENSHOTONE], Literal["api_key"]
|
||||
] = CredentialsField(description="The ScreenshotOne API key")
|
||||
url: str = SchemaField(
|
||||
description="URL of the website to screenshot",
|
||||
placeholder="https://example.com",
|
||||
)
|
||||
viewport_width: int = SchemaField(
|
||||
description="Width of the viewport in pixels", default=1920
|
||||
)
|
||||
viewport_height: int = SchemaField(
|
||||
description="Height of the viewport in pixels", default=1080
|
||||
)
|
||||
full_page: bool = SchemaField(
|
||||
description="Whether to capture the full page length", default=False
|
||||
)
|
||||
format: Format = SchemaField(
|
||||
description="Output format (png, jpeg, webp)", default=Format.PNG
|
||||
)
|
||||
block_ads: bool = SchemaField(description="Whether to block ads", default=True)
|
||||
block_cookie_banners: bool = SchemaField(
|
||||
description="Whether to block cookie banners", default=True
|
||||
)
|
||||
block_chats: bool = SchemaField(
|
||||
description="Whether to block chat widgets", default=True
|
||||
)
|
||||
cache: bool = SchemaField(
|
||||
description="Whether to enable caching", default=False
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
image: MediaFile = SchemaField(description="The screenshot image data")
|
||||
error: str = SchemaField(description="Error message if the screenshot failed")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="3a7c4b8d-6e2f-4a5d-b9c1-f8d23c5a9b0e", # Generated UUID
|
||||
description="Takes a screenshot of a specified website using ScreenshotOne API",
|
||||
categories={BlockCategory.DATA},
|
||||
input_schema=ScreenshotWebPageBlock.Input,
|
||||
output_schema=ScreenshotWebPageBlock.Output,
|
||||
test_input={
|
||||
"url": "https://example.com",
|
||||
"viewport_width": 1920,
|
||||
"viewport_height": 1080,
|
||||
"full_page": False,
|
||||
"format": "png",
|
||||
"block_ads": True,
|
||||
"block_cookie_banners": True,
|
||||
"block_chats": True,
|
||||
"cache": False,
|
||||
"credentials": {
|
||||
"provider": "screenshotone",
|
||||
"type": "api_key",
|
||||
"id": "test-id",
|
||||
"title": "Test API Key",
|
||||
},
|
||||
},
|
||||
test_credentials=APIKeyCredentials(
|
||||
id="test-id",
|
||||
provider="screenshotone",
|
||||
api_key=SecretStr("test-key"),
|
||||
title="Test API Key",
|
||||
expires_at=None,
|
||||
),
|
||||
test_output=[
|
||||
(
|
||||
"image",
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAB5JREFUOE9jZPjP8J+BAsA4agDDaBgwjIYBw7AIAwCV5B/xAsMbygAAAABJRU5ErkJggg==",
|
||||
),
|
||||
],
|
||||
test_mock={
|
||||
"take_screenshot": lambda *args, **kwargs: {
|
||||
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAB5JREFUOE9jZPjP8J+BAsA4agDDaBgwjIYBw7AIAwCV5B/xAsMbygAAAABJRU5ErkJggg==",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def take_screenshot(
|
||||
credentials: APIKeyCredentials,
|
||||
graph_exec_id: str,
|
||||
url: str,
|
||||
viewport_width: int,
|
||||
viewport_height: int,
|
||||
full_page: bool,
|
||||
format: Format,
|
||||
block_ads: bool,
|
||||
block_cookie_banners: bool,
|
||||
block_chats: bool,
|
||||
cache: bool,
|
||||
) -> dict:
|
||||
"""
|
||||
Takes a screenshot using the ScreenshotOne API
|
||||
"""
|
||||
api = Requests(trusted_origins=["https://api.screenshotone.com"])
|
||||
|
||||
# Build API URL with parameters
|
||||
params = {
|
||||
"access_key": credentials.api_key.get_secret_value(),
|
||||
"url": url,
|
||||
"viewport_width": viewport_width,
|
||||
"viewport_height": viewport_height,
|
||||
"full_page": str(full_page).lower(),
|
||||
"format": format.value,
|
||||
"block_ads": str(block_ads).lower(),
|
||||
"block_cookie_banners": str(block_cookie_banners).lower(),
|
||||
"block_chats": str(block_chats).lower(),
|
||||
"cache": str(cache).lower(),
|
||||
}
|
||||
|
||||
response = api.get("https://api.screenshotone.com/take", params=params)
|
||||
|
||||
return {
|
||||
"image": store_media_file(
|
||||
graph_exec_id=graph_exec_id,
|
||||
file=f"data:image/{format.value};base64,{b64encode(response.content).decode('utf-8')}",
|
||||
return_content=True,
|
||||
)
|
||||
}
|
||||
|
||||
def run(
|
||||
self,
|
||||
input_data: Input,
|
||||
*,
|
||||
credentials: APIKeyCredentials,
|
||||
graph_exec_id: str,
|
||||
**kwargs,
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
screenshot_data = self.take_screenshot(
|
||||
credentials=credentials,
|
||||
graph_exec_id=graph_exec_id,
|
||||
url=input_data.url,
|
||||
viewport_width=input_data.viewport_width,
|
||||
viewport_height=input_data.viewport_height,
|
||||
full_page=input_data.full_page,
|
||||
format=input_data.format,
|
||||
block_ads=input_data.block_ads,
|
||||
block_cookie_banners=input_data.block_cookie_banners,
|
||||
block_chats=input_data.block_chats,
|
||||
cache=input_data.cache,
|
||||
)
|
||||
yield "image", screenshot_data["image"]
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
37
autogpt_platform/backend/backend/blocks/xml_parser.py
Normal file
37
autogpt_platform/backend/backend/blocks/xml_parser.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from gravitasml.parser import Parser
|
||||
from gravitasml.token import tokenize
|
||||
|
||||
from backend.data.block import Block, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
|
||||
|
||||
class XMLParserBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
input_xml: str = SchemaField(description="input xml to be parsed")
|
||||
|
||||
class Output(BlockSchema):
|
||||
parsed_xml: dict = SchemaField(description="output parsed xml to dict")
|
||||
error: str = SchemaField(description="Error in parsing")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="286380af-9529-4b55-8be0-1d7c854abdb5",
|
||||
description="Parses XML using gravitasml to tokenize and coverts it to dict",
|
||||
input_schema=XMLParserBlock.Input,
|
||||
output_schema=XMLParserBlock.Output,
|
||||
test_input={"input_xml": "<tag1><tag2>content</tag2></tag1>"},
|
||||
test_output=[
|
||||
("parsed_xml", {"tag1": {"tag2": "content"}}),
|
||||
],
|
||||
)
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
try:
|
||||
tokens = tokenize(input_data.input_xml)
|
||||
parser = Parser(tokens)
|
||||
parsed_result = parser.parse()
|
||||
yield "parsed_xml", parsed_result
|
||||
except ValueError as val_e:
|
||||
raise ValueError(f"Validation error for dict:{val_e}") from val_e
|
||||
except SyntaxError as syn_e:
|
||||
raise SyntaxError(f"Error in input xml syntax: {syn_e}") from syn_e
|
||||
@@ -221,7 +221,8 @@ def event():
|
||||
@test.command()
|
||||
@click.argument("server_address")
|
||||
@click.argument("graph_id")
|
||||
def websocket(server_address: str, graph_id: str):
|
||||
@click.argument("graph_version")
|
||||
def websocket(server_address: str, graph_id: str, graph_version: int):
|
||||
"""
|
||||
Tests the websocket connection.
|
||||
"""
|
||||
@@ -237,7 +238,9 @@ def websocket(server_address: str, graph_id: str):
|
||||
try:
|
||||
msg = WsMessage(
|
||||
method=Methods.SUBSCRIBE,
|
||||
data=ExecutionSubscription(graph_id=graph_id).model_dump(),
|
||||
data=ExecutionSubscription(
|
||||
graph_id=graph_id, graph_version=graph_version
|
||||
).model_dump(),
|
||||
).model_dump_json()
|
||||
await websocket.send(msg)
|
||||
print(f"Sending: {msg}")
|
||||
|
||||
@@ -35,6 +35,8 @@ from backend.integrations.credentials_store import (
|
||||
# =============== Configure the cost for each LLM Model call =============== #
|
||||
|
||||
MODEL_COST: dict[LlmModel, int] = {
|
||||
LlmModel.O3_MINI: 2, # $1.10 / $4.40
|
||||
LlmModel.O1: 16, # $15 / $60
|
||||
LlmModel.O1_PREVIEW: 16,
|
||||
LlmModel.O1_MINI: 4,
|
||||
LlmModel.GPT4O_MINI: 1,
|
||||
@@ -42,20 +44,21 @@ MODEL_COST: dict[LlmModel, int] = {
|
||||
LlmModel.GPT4_TURBO: 10,
|
||||
LlmModel.GPT3_5_TURBO: 1,
|
||||
LlmModel.CLAUDE_3_5_SONNET: 4,
|
||||
LlmModel.CLAUDE_3_5_HAIKU: 1, # $0.80 / $4.00
|
||||
LlmModel.CLAUDE_3_HAIKU: 1,
|
||||
LlmModel.LLAMA3_8B: 1,
|
||||
LlmModel.LLAMA3_70B: 1,
|
||||
LlmModel.MIXTRAL_8X7B: 1,
|
||||
LlmModel.GEMMA_7B: 1,
|
||||
LlmModel.GEMMA2_9B: 1,
|
||||
LlmModel.LLAMA3_1_405B: 1,
|
||||
LlmModel.LLAMA3_1_70B: 1,
|
||||
LlmModel.LLAMA3_3_70B: 1, # $0.59 / $0.79
|
||||
LlmModel.LLAMA3_1_8B: 1,
|
||||
LlmModel.OLLAMA_LLAMA3_3: 1,
|
||||
LlmModel.OLLAMA_LLAMA3_2: 1,
|
||||
LlmModel.OLLAMA_LLAMA3_8B: 1,
|
||||
LlmModel.OLLAMA_LLAMA3_405B: 1,
|
||||
LlmModel.DEEPSEEK_LLAMA_70B: 1, # ? / ?
|
||||
LlmModel.OLLAMA_DOLPHIN: 1,
|
||||
LlmModel.GEMINI_FLASH_1_5_8B: 1,
|
||||
LlmModel.GEMINI_FLASH_1_5: 1,
|
||||
LlmModel.GROK_BETA: 5,
|
||||
LlmModel.MISTRAL_NEMO: 1,
|
||||
LlmModel.COHERE_COMMAND_R_08_2024: 1,
|
||||
|
||||
@@ -10,7 +10,6 @@ class BlockCostType(str, Enum):
|
||||
RUN = "run" # cost X credits per run
|
||||
BYTE = "byte" # cost X credits per byte
|
||||
SECOND = "second" # cost X credits per second
|
||||
DOLLAR = "dollar" # cost X dollars per run
|
||||
|
||||
|
||||
class BlockCost(BaseModel):
|
||||
|
||||
@@ -202,15 +202,37 @@ class UserCreditBase(ABC):
|
||||
transaction_type: CreditTransactionType,
|
||||
is_active: bool = True,
|
||||
transaction_key: str | None = None,
|
||||
ceiling_balance: int | None = None,
|
||||
metadata: Json = Json({}),
|
||||
) -> int:
|
||||
) -> tuple[int, str]:
|
||||
"""
|
||||
Add a new transaction for the user.
|
||||
This is the only method that should be used to add a new transaction.
|
||||
|
||||
Args:
|
||||
user_id (str): The user ID.
|
||||
amount (int): The amount of credits to add.
|
||||
transaction_type (CreditTransactionType): The type of transaction.
|
||||
is_active (bool): Whether the transaction is active or needs to be manually activated through _enable_transaction.
|
||||
transaction_key (str | None): The transaction key. Avoids adding transaction if the key already exists.
|
||||
ceiling_balance (int | None): The ceiling balance. Avoids adding more credits if the balance is already above the ceiling.
|
||||
metadata (Json): The metadata of the transaction.
|
||||
|
||||
Returns:
|
||||
tuple[int, str]: The new balance & the transaction key.
|
||||
"""
|
||||
async with db.locked_transaction(f"usr_trx_{user_id}"):
|
||||
# Get latest balance snapshot
|
||||
user_balance, _ = await self._get_credits(user_id)
|
||||
|
||||
if ceiling_balance and user_balance >= ceiling_balance:
|
||||
raise ValueError(
|
||||
f"You already have enough balance for user {user_id}, balance: {user_balance}, ceiling: {ceiling_balance}"
|
||||
)
|
||||
|
||||
if amount < 0 and user_balance < abs(amount):
|
||||
raise ValueError(
|
||||
f"Insufficient balance for user {user_id}, balance: {user_balance}, amount: {amount}"
|
||||
f"Insufficient balance of ${user_balance/100} to run the block that costs ${abs(amount)/100}"
|
||||
)
|
||||
|
||||
# Create the transaction
|
||||
@@ -225,9 +247,8 @@ class UserCreditBase(ABC):
|
||||
}
|
||||
if transaction_key:
|
||||
transaction_data["transactionKey"] = transaction_key
|
||||
await CreditTransaction.prisma().create(data=transaction_data)
|
||||
|
||||
return user_balance + amount
|
||||
tx = await CreditTransaction.prisma().create(data=transaction_data)
|
||||
return user_balance + amount, tx.transactionKey
|
||||
|
||||
|
||||
class UsageTransactionMetadata(BaseModel):
|
||||
@@ -308,7 +329,7 @@ class UserCredit(UserCreditBase):
|
||||
if cost == 0:
|
||||
return 0
|
||||
|
||||
balance = await self._add_transaction(
|
||||
balance, _ = await self._add_transaction(
|
||||
user_id=entry.user_id,
|
||||
amount=-cost,
|
||||
transaction_type=CreditTransactionType.USAGE,
|
||||
@@ -326,11 +347,17 @@ class UserCredit(UserCreditBase):
|
||||
)
|
||||
user_id = entry.user_id
|
||||
|
||||
# Auto top-up if balance just went below threshold due to this transaction.
|
||||
# Auto top-up if balance is below threshold.
|
||||
auto_top_up = await get_auto_top_up(user_id)
|
||||
if balance < auto_top_up.threshold <= balance - cost:
|
||||
if auto_top_up.threshold and balance < auto_top_up.threshold:
|
||||
try:
|
||||
await self.top_up_credits(user_id=user_id, amount=auto_top_up.amount)
|
||||
await self._top_up_credits(
|
||||
user_id=user_id,
|
||||
amount=auto_top_up.amount,
|
||||
# Avoid multiple auto top-ups within the same graph execution.
|
||||
key=f"AUTO-TOP-UP-{user_id}-{entry.graph_exec_id}",
|
||||
ceiling_balance=auto_top_up.threshold,
|
||||
)
|
||||
except Exception as e:
|
||||
# Failed top-up is not critical, we can move on.
|
||||
logger.error(
|
||||
@@ -340,9 +367,34 @@ class UserCredit(UserCreditBase):
|
||||
return cost
|
||||
|
||||
async def top_up_credits(self, user_id: str, amount: int):
|
||||
await self._top_up_credits(user_id, amount)
|
||||
|
||||
async def _top_up_credits(
|
||||
self,
|
||||
user_id: str,
|
||||
amount: int,
|
||||
key: str | None = None,
|
||||
ceiling_balance: int | None = None,
|
||||
):
|
||||
if amount < 0:
|
||||
raise ValueError(f"Top up amount must not be negative: {amount}")
|
||||
|
||||
if key is not None and (
|
||||
await CreditTransaction.prisma().find_first(
|
||||
where={"transactionKey": key, "userId": user_id}
|
||||
)
|
||||
):
|
||||
raise ValueError(f"Transaction key {key} already exists for user {user_id}")
|
||||
|
||||
_, transaction_key = await self._add_transaction(
|
||||
user_id=user_id,
|
||||
amount=amount,
|
||||
transaction_type=CreditTransactionType.TOP_UP,
|
||||
is_active=False,
|
||||
transaction_key=key,
|
||||
ceiling_balance=ceiling_balance,
|
||||
)
|
||||
|
||||
customer_id = await get_stripe_customer_id(user_id)
|
||||
|
||||
payment_methods = stripe.PaymentMethod.list(customer=customer_id, type="card")
|
||||
@@ -379,13 +431,10 @@ class UserCredit(UserCreditBase):
|
||||
},
|
||||
)
|
||||
if payment_intent.status == "succeeded":
|
||||
await self._add_transaction(
|
||||
await self._enable_transaction(
|
||||
transaction_key=transaction_key,
|
||||
user_id=user_id,
|
||||
amount=amount,
|
||||
transaction_type=CreditTransactionType.TOP_UP,
|
||||
transaction_key=payment_intent.id,
|
||||
metadata=Json({"payment_intent": payment_intent}),
|
||||
is_active=True,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -425,6 +474,7 @@ class UserCredit(UserCreditBase):
|
||||
+ "/marketplace/credits?topup=success",
|
||||
cancel_url=settings.config.frontend_base_url
|
||||
+ "/marketplace/credits?topup=cancel",
|
||||
allow_promotion_codes=True,
|
||||
)
|
||||
|
||||
await self._add_transaction(
|
||||
@@ -504,7 +554,11 @@ class UserCredit(UserCreditBase):
|
||||
)
|
||||
tx_time = None
|
||||
for t in transactions:
|
||||
metadata = UsageTransactionMetadata.model_validate(t.metadata)
|
||||
metadata = (
|
||||
UsageTransactionMetadata.model_validate(t.metadata)
|
||||
if t.metadata
|
||||
else UsageTransactionMetadata()
|
||||
)
|
||||
tx_time = t.createdAt.replace(tzinfo=None)
|
||||
|
||||
if t.type == CreditTransactionType.USAGE and metadata.graph_exec_id:
|
||||
@@ -551,12 +605,13 @@ class BetaUserCredit(UserCredit):
|
||||
return balance
|
||||
|
||||
try:
|
||||
return await self._add_transaction(
|
||||
balance, _ = await self._add_transaction(
|
||||
user_id=user_id,
|
||||
amount=max(self.num_user_credits_refill - balance, 0),
|
||||
transaction_type=CreditTransactionType.TOP_UP,
|
||||
transaction_key=f"MONTHLY-CREDIT-TOP-UP-{cur_time}",
|
||||
)
|
||||
return balance
|
||||
except UniqueViolationError:
|
||||
# Already refilled this month
|
||||
return (await self._get_credits(user_id))[0]
|
||||
|
||||
@@ -23,6 +23,7 @@ class GraphExecutionEntry(BaseModel):
|
||||
user_id: str
|
||||
graph_exec_id: str
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
start_node_execs: list["NodeExecutionEntry"]
|
||||
|
||||
|
||||
|
||||
296
autogpt_platform/backend/backend/data/rabbitmq.py
Normal file
296
autogpt_platform/backend/backend/data/rabbitmq.py
Normal file
@@ -0,0 +1,296 @@
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Awaitable, Optional
|
||||
|
||||
import aio_pika
|
||||
import pika
|
||||
import pika.adapters.blocking_connection
|
||||
from pika.spec import BasicProperties
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.util.retry import conn_retry
|
||||
from backend.util.settings import Settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExchangeType(str, Enum):
|
||||
DIRECT = "direct"
|
||||
FANOUT = "fanout"
|
||||
TOPIC = "topic"
|
||||
HEADERS = "headers"
|
||||
|
||||
|
||||
class Exchange(BaseModel):
|
||||
name: str
|
||||
type: ExchangeType
|
||||
durable: bool = True
|
||||
auto_delete: bool = False
|
||||
|
||||
|
||||
class Queue(BaseModel):
|
||||
name: str
|
||||
durable: bool = True
|
||||
auto_delete: bool = False
|
||||
# Optional exchange binding configuration
|
||||
exchange: Optional[Exchange] = None
|
||||
routing_key: Optional[str] = None
|
||||
arguments: Optional[dict] = None
|
||||
|
||||
|
||||
class RabbitMQConfig(BaseModel):
|
||||
"""Configuration for a RabbitMQ service instance"""
|
||||
|
||||
vhost: str = "/"
|
||||
exchanges: list[Exchange]
|
||||
queues: list[Queue]
|
||||
|
||||
|
||||
class RabbitMQBase(ABC):
|
||||
"""Base class for RabbitMQ connections with shared configuration"""
|
||||
|
||||
def __init__(self, config: RabbitMQConfig):
|
||||
settings = Settings()
|
||||
self.host = settings.config.rabbitmq_host
|
||||
self.port = settings.config.rabbitmq_port
|
||||
self.username = settings.secrets.rabbitmq_default_user
|
||||
self.password = settings.secrets.rabbitmq_default_pass
|
||||
self.config = config
|
||||
|
||||
self._connection = None
|
||||
self._channel = None
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
"""Check if we have a valid connection"""
|
||||
return bool(self._connection)
|
||||
|
||||
@property
|
||||
def is_ready(self) -> bool:
|
||||
"""Check if we have a valid channel"""
|
||||
return bool(self.is_connected and self._channel)
|
||||
|
||||
@abstractmethod
|
||||
def connect(self) -> None | Awaitable[None]:
|
||||
"""Establish connection to RabbitMQ"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def disconnect(self) -> None | Awaitable[None]:
|
||||
"""Close connection to RabbitMQ"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def declare_infrastructure(self) -> None | Awaitable[None]:
|
||||
"""Declare exchanges and queues for this service"""
|
||||
pass
|
||||
|
||||
|
||||
class SyncRabbitMQ(RabbitMQBase):
|
||||
"""Synchronous RabbitMQ client"""
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
return bool(self._connection and self._connection.is_open)
|
||||
|
||||
@property
|
||||
def is_ready(self) -> bool:
|
||||
return bool(self.is_connected and self._channel and self._channel.is_open)
|
||||
|
||||
@conn_retry("RabbitMQ", "Acquiring connection")
|
||||
def connect(self) -> None:
|
||||
if self.is_connected:
|
||||
return
|
||||
|
||||
credentials = pika.PlainCredentials(self.username, self.password)
|
||||
parameters = pika.ConnectionParameters(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
virtual_host=self.config.vhost,
|
||||
credentials=credentials,
|
||||
heartbeat=600,
|
||||
blocked_connection_timeout=300,
|
||||
)
|
||||
|
||||
self._connection = pika.BlockingConnection(parameters)
|
||||
self._channel = self._connection.channel()
|
||||
self._channel.basic_qos(prefetch_count=1)
|
||||
|
||||
self.declare_infrastructure()
|
||||
|
||||
def disconnect(self) -> None:
|
||||
if self._channel:
|
||||
if self._channel.is_open:
|
||||
self._channel.close()
|
||||
self._channel = None
|
||||
if self._connection:
|
||||
if self._connection.is_open:
|
||||
self._connection.close()
|
||||
self._connection = None
|
||||
|
||||
def declare_infrastructure(self) -> None:
|
||||
"""Declare exchanges and queues for this service"""
|
||||
if not self.is_ready:
|
||||
self.connect()
|
||||
|
||||
if self._channel is None:
|
||||
raise RuntimeError("Channel should be established after connect")
|
||||
|
||||
# Declare exchanges
|
||||
for exchange in self.config.exchanges:
|
||||
self._channel.exchange_declare(
|
||||
exchange=exchange.name,
|
||||
exchange_type=exchange.type.value,
|
||||
durable=exchange.durable,
|
||||
auto_delete=exchange.auto_delete,
|
||||
)
|
||||
|
||||
# Declare queues and bind them to exchanges
|
||||
for queue in self.config.queues:
|
||||
self._channel.queue_declare(
|
||||
queue=queue.name,
|
||||
durable=queue.durable,
|
||||
auto_delete=queue.auto_delete,
|
||||
arguments=queue.arguments,
|
||||
)
|
||||
if queue.exchange:
|
||||
self._channel.queue_bind(
|
||||
queue=queue.name,
|
||||
exchange=queue.exchange.name,
|
||||
routing_key=queue.routing_key or queue.name,
|
||||
)
|
||||
|
||||
def publish_message(
|
||||
self,
|
||||
routing_key: str,
|
||||
message: str,
|
||||
exchange: Optional[Exchange] = None,
|
||||
properties: Optional[BasicProperties] = None,
|
||||
mandatory: bool = True,
|
||||
) -> None:
|
||||
if not self.is_ready:
|
||||
self.connect()
|
||||
|
||||
if self._channel is None:
|
||||
raise RuntimeError("Channel should be established after connect")
|
||||
|
||||
self._channel.basic_publish(
|
||||
exchange=exchange.name if exchange else "",
|
||||
routing_key=routing_key,
|
||||
body=message.encode(),
|
||||
properties=properties or BasicProperties(delivery_mode=2),
|
||||
mandatory=mandatory,
|
||||
)
|
||||
|
||||
def get_channel(self) -> pika.adapters.blocking_connection.BlockingChannel:
|
||||
if not self.is_ready:
|
||||
self.connect()
|
||||
if self._channel is None:
|
||||
raise RuntimeError("Channel should be established after connect")
|
||||
return self._channel
|
||||
|
||||
|
||||
class AsyncRabbitMQ(RabbitMQBase):
|
||||
"""Asynchronous RabbitMQ client"""
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
return bool(self._connection and not self._connection.is_closed)
|
||||
|
||||
@property
|
||||
def is_ready(self) -> bool:
|
||||
return bool(self.is_connected and self._channel and not self._channel.is_closed)
|
||||
|
||||
@conn_retry("AsyncRabbitMQ", "Acquiring async connection")
|
||||
async def connect(self):
|
||||
if self.is_connected:
|
||||
return
|
||||
|
||||
self._connection = await aio_pika.connect_robust(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
login=self.username,
|
||||
password=self.password,
|
||||
virtualhost=self.config.vhost.lstrip("/"),
|
||||
)
|
||||
self._channel = await self._connection.channel()
|
||||
await self._channel.set_qos(prefetch_count=1)
|
||||
|
||||
await self.declare_infrastructure()
|
||||
|
||||
async def disconnect(self):
|
||||
if self._channel:
|
||||
await self._channel.close()
|
||||
self._channel = None
|
||||
if self._connection:
|
||||
await self._connection.close()
|
||||
self._connection = None
|
||||
|
||||
async def declare_infrastructure(self):
|
||||
"""Declare exchanges and queues for this service"""
|
||||
if not self.is_ready:
|
||||
await self.connect()
|
||||
|
||||
if self._channel is None:
|
||||
raise RuntimeError("Channel should be established after connect")
|
||||
|
||||
# Declare exchanges
|
||||
for exchange in self.config.exchanges:
|
||||
await self._channel.declare_exchange(
|
||||
name=exchange.name,
|
||||
type=exchange.type.value,
|
||||
durable=exchange.durable,
|
||||
auto_delete=exchange.auto_delete,
|
||||
)
|
||||
|
||||
# Declare queues and bind them to exchanges
|
||||
for queue in self.config.queues:
|
||||
queue_obj = await self._channel.declare_queue(
|
||||
name=queue.name,
|
||||
durable=queue.durable,
|
||||
auto_delete=queue.auto_delete,
|
||||
arguments=queue.arguments,
|
||||
)
|
||||
if queue.exchange:
|
||||
exchange = await self._channel.get_exchange(queue.exchange.name)
|
||||
await queue_obj.bind(
|
||||
exchange, routing_key=queue.routing_key or queue.name
|
||||
)
|
||||
|
||||
async def publish_message(
|
||||
self,
|
||||
routing_key: str,
|
||||
message: str,
|
||||
exchange: Optional[Exchange] = None,
|
||||
persistent: bool = True,
|
||||
) -> None:
|
||||
if not self.is_ready:
|
||||
await self.connect()
|
||||
|
||||
if self._channel is None:
|
||||
raise RuntimeError("Channel should be established after connect")
|
||||
|
||||
if exchange:
|
||||
exchange_obj = await self._channel.get_exchange(exchange.name)
|
||||
else:
|
||||
exchange_obj = self._channel.default_exchange
|
||||
|
||||
await exchange_obj.publish(
|
||||
aio_pika.Message(
|
||||
body=message.encode(),
|
||||
delivery_mode=(
|
||||
aio_pika.DeliveryMode.PERSISTENT
|
||||
if persistent
|
||||
else aio_pika.DeliveryMode.NOT_PERSISTENT
|
||||
),
|
||||
),
|
||||
routing_key=routing_key,
|
||||
)
|
||||
|
||||
async def get_channel(self) -> aio_pika.abc.AbstractChannel:
|
||||
if not self.is_ready:
|
||||
await self.connect()
|
||||
if self._channel is None:
|
||||
raise RuntimeError("Channel should be established after connect")
|
||||
return self._channel
|
||||
@@ -163,6 +163,7 @@ def execute_node(
|
||||
# AgentExecutorBlock specially separate the node input_data & its input_default.
|
||||
if isinstance(node_block, AgentExecutorBlock):
|
||||
input_data = {**node.input_default, "data": input_data}
|
||||
data.data = input_data
|
||||
|
||||
# Execute the node
|
||||
input_data_str = json.dumps(input_data)
|
||||
@@ -192,6 +193,11 @@ def execute_node(
|
||||
|
||||
output_size = 0
|
||||
try:
|
||||
# Charge the user for the execution before running the block.
|
||||
# TODO: We assume the block is executed within 0 seconds.
|
||||
# This is fine because for now, there is no block that is charged by time.
|
||||
db_client.spend_credits(data, input_size + output_size, 0)
|
||||
|
||||
for output_name, output_data in node_block.execute(
|
||||
input_data, **extra_exec_kwargs
|
||||
):
|
||||
@@ -210,16 +216,7 @@ def execute_node(
|
||||
):
|
||||
yield execution
|
||||
|
||||
# Update execution status and spend credits
|
||||
res = update_execution(ExecutionStatus.COMPLETED)
|
||||
s = input_size + output_size
|
||||
t = (
|
||||
(res.end_time - res.start_time).total_seconds()
|
||||
if res.end_time and res.start_time
|
||||
else 0
|
||||
)
|
||||
data.data = input_data
|
||||
db_client.spend_credits(data, s, t)
|
||||
update_execution(ExecutionStatus.COMPLETED)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
@@ -875,6 +872,7 @@ class ExecutionManager(AppService):
|
||||
graph_exec = GraphExecutionEntry(
|
||||
user_id=user_id,
|
||||
graph_id=graph_id,
|
||||
graph_version=graph_version or 0,
|
||||
graph_exec_id=graph_exec_id,
|
||||
start_node_execs=starting_node_execs,
|
||||
)
|
||||
|
||||
@@ -130,6 +130,13 @@ nvidia_credentials = APIKeyCredentials(
|
||||
title="Use Credits for Nvidia",
|
||||
expires_at=None,
|
||||
)
|
||||
screenshotone_credentials = APIKeyCredentials(
|
||||
id="3b1bdd16-8818-4bc2-8cbb-b23f9a3439ed",
|
||||
provider="screenshotone",
|
||||
api_key=SecretStr(settings.secrets.screenshotone_api_key),
|
||||
title="Use Credits for ScreenshotOne",
|
||||
expires_at=None,
|
||||
)
|
||||
mem0_credentials = APIKeyCredentials(
|
||||
id="ed55ac19-356e-4243-a6cb-bc599e9b716f",
|
||||
provider="mem0",
|
||||
@@ -154,8 +161,9 @@ DEFAULT_CREDENTIALS = [
|
||||
fal_credentials,
|
||||
exa_credentials,
|
||||
e2b_credentials,
|
||||
nvidia_credentials,
|
||||
mem0_credentials,
|
||||
nvidia_credentials,
|
||||
screenshotone_credentials,
|
||||
]
|
||||
|
||||
|
||||
@@ -219,6 +227,8 @@ class IntegrationCredentialsStore:
|
||||
all_credentials.append(e2b_credentials)
|
||||
if settings.secrets.nvidia_api_key:
|
||||
all_credentials.append(nvidia_credentials)
|
||||
if settings.secrets.screenshotone_api_key:
|
||||
all_credentials.append(screenshotone_credentials)
|
||||
if settings.secrets.mem0_api_key:
|
||||
all_credentials.append(mem0_credentials)
|
||||
return all_credentials
|
||||
|
||||
@@ -30,6 +30,7 @@ class ProviderName(str, Enum):
|
||||
REDDIT = "reddit"
|
||||
REPLICATE = "replicate"
|
||||
REVID = "revid"
|
||||
SCREENSHOTONE = "screenshotone"
|
||||
SLANT3D = "slant3d"
|
||||
SMTP = "smtp"
|
||||
TWITTER = "twitter"
|
||||
|
||||
@@ -20,24 +20,28 @@ class ConnectionManager:
|
||||
for subscribers in self.subscriptions.values():
|
||||
subscribers.discard(websocket)
|
||||
|
||||
async def subscribe(self, graph_id: str, websocket: WebSocket):
|
||||
if graph_id not in self.subscriptions:
|
||||
self.subscriptions[graph_id] = set()
|
||||
self.subscriptions[graph_id].add(websocket)
|
||||
async def subscribe(self, graph_id: str, graph_version: int, websocket: WebSocket):
|
||||
key = f"{graph_id}_{graph_version}"
|
||||
if key not in self.subscriptions:
|
||||
self.subscriptions[key] = set()
|
||||
self.subscriptions[key].add(websocket)
|
||||
|
||||
async def unsubscribe(self, graph_id: str, websocket: WebSocket):
|
||||
if graph_id in self.subscriptions:
|
||||
self.subscriptions[graph_id].discard(websocket)
|
||||
if not self.subscriptions[graph_id]:
|
||||
del self.subscriptions[graph_id]
|
||||
async def unsubscribe(
|
||||
self, graph_id: str, graph_version: int, websocket: WebSocket
|
||||
):
|
||||
key = f"{graph_id}_{graph_version}"
|
||||
if key in self.subscriptions:
|
||||
self.subscriptions[key].discard(websocket)
|
||||
if not self.subscriptions[key]:
|
||||
del self.subscriptions[key]
|
||||
|
||||
async def send_execution_result(self, result: execution.ExecutionResult):
|
||||
graph_id = result.graph_id
|
||||
if graph_id in self.subscriptions:
|
||||
key = f"{result.graph_id}_{result.graph_version}"
|
||||
if key in self.subscriptions:
|
||||
message = WsMessage(
|
||||
method=Methods.EXECUTION_EVENT,
|
||||
channel=graph_id,
|
||||
channel=key,
|
||||
data=result.model_dump(),
|
||||
).model_dump_json()
|
||||
for connection in self.subscriptions[graph_id]:
|
||||
for connection in self.subscriptions[key]:
|
||||
await connection.send_text(message)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from typing import Any, Sequence
|
||||
from typing import Any, Dict, List, Optional, Sequence
|
||||
|
||||
from autogpt_libs.utils.cache import thread_cached
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from prisma.enums import APIKeyPermission
|
||||
from prisma.enums import AgentExecutionStatus, APIKeyPermission
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
import backend.data.block
|
||||
from backend.data import execution as execution_db
|
||||
from backend.data import graph as graph_db
|
||||
from backend.data.api_key import APIKey
|
||||
from backend.data.block import BlockInput, CompletedBlockOutput
|
||||
from backend.data.execution import ExecutionResult
|
||||
from backend.executor import ExecutionManager
|
||||
from backend.server.external.middleware import require_permission
|
||||
from backend.util.service import get_service_client
|
||||
@@ -28,6 +30,40 @@ logger = logging.getLogger(__name__)
|
||||
v1_router = APIRouter()
|
||||
|
||||
|
||||
class NodeOutput(TypedDict):
|
||||
key: str
|
||||
value: Any
|
||||
|
||||
|
||||
class ExecutionNode(TypedDict):
|
||||
node_id: str
|
||||
input: Any
|
||||
output: Dict[str, Any]
|
||||
|
||||
|
||||
class ExecutionNodeOutput(TypedDict):
|
||||
node_id: str
|
||||
outputs: List[NodeOutput]
|
||||
|
||||
|
||||
class GraphExecutionResult(TypedDict):
|
||||
execution_id: str
|
||||
status: str
|
||||
nodes: List[ExecutionNode]
|
||||
output: Optional[List[Dict[str, str]]]
|
||||
|
||||
|
||||
def get_outputs_with_names(results: List[ExecutionResult]) -> List[Dict[str, str]]:
|
||||
outputs = []
|
||||
for result in results:
|
||||
if "output" in result.output_data:
|
||||
output_value = result.output_data["output"][0]
|
||||
name = result.output_data.get("name", [None])[0]
|
||||
if output_value and name:
|
||||
outputs.append({name: output_value})
|
||||
return outputs
|
||||
|
||||
|
||||
@v1_router.get(
|
||||
path="/blocks",
|
||||
tags=["blocks"],
|
||||
@@ -59,17 +95,21 @@ def execute_graph_block(
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
path="/graphs/{graph_id}/execute",
|
||||
path="/graphs/{graph_id}/execute/{graph_version}",
|
||||
tags=["graphs"],
|
||||
)
|
||||
def execute_graph(
|
||||
graph_id: str,
|
||||
graph_version: int,
|
||||
node_input: dict[Any, Any],
|
||||
api_key: APIKey = Depends(require_permission(APIKeyPermission.EXECUTE_GRAPH)),
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
graph_exec = execution_manager_client().add_execution(
|
||||
graph_id, node_input, user_id=api_key.user_id
|
||||
graph_id,
|
||||
graph_version=graph_version,
|
||||
data=node_input,
|
||||
user_id=api_key.user_id,
|
||||
)
|
||||
return {"id": graph_exec.graph_exec_id}
|
||||
except Exception as e:
|
||||
@@ -85,27 +125,28 @@ async def get_graph_execution_results(
|
||||
graph_id: str,
|
||||
graph_exec_id: str,
|
||||
api_key: APIKey = Depends(require_permission(APIKeyPermission.READ_GRAPH)),
|
||||
) -> dict:
|
||||
) -> GraphExecutionResult:
|
||||
graph = await graph_db.get_graph(graph_id, user_id=api_key.user_id)
|
||||
if not graph:
|
||||
raise HTTPException(status_code=404, detail=f"Graph #{graph_id} not found.")
|
||||
|
||||
results = await execution_db.get_execution_results(graph_exec_id)
|
||||
last_result = results[-1] if results else None
|
||||
execution_status = (
|
||||
last_result.status if last_result else AgentExecutionStatus.INCOMPLETE
|
||||
)
|
||||
outputs = get_outputs_with_names(results)
|
||||
|
||||
return {
|
||||
"execution_id": graph_exec_id,
|
||||
"nodes": [
|
||||
{
|
||||
"node_id": result.node_id,
|
||||
"input": (
|
||||
result.input_data.get("value")
|
||||
if "value" in result.input_data
|
||||
else result.input_data
|
||||
),
|
||||
"output": result.output_data.get(
|
||||
"response", result.output_data.get("result", [])
|
||||
),
|
||||
}
|
||||
return GraphExecutionResult(
|
||||
execution_id=graph_exec_id,
|
||||
status=execution_status,
|
||||
nodes=[
|
||||
ExecutionNode(
|
||||
node_id=result.node_id,
|
||||
input=result.input_data.get("value", result.input_data),
|
||||
output={k: v for k, v in result.output_data.items()},
|
||||
)
|
||||
for result in results
|
||||
],
|
||||
}
|
||||
output=outputs if execution_status == AgentExecutionStatus.COMPLETED else None,
|
||||
)
|
||||
|
||||
@@ -25,12 +25,11 @@ class WsMessage(pydantic.BaseModel):
|
||||
|
||||
class ExecutionSubscription(pydantic.BaseModel):
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
|
||||
|
||||
class SubscriptionDetails(pydantic.BaseModel):
|
||||
event_type: str
|
||||
channel: str
|
||||
graph_id: str
|
||||
class ExecuteGraphResponse(pydantic.BaseModel):
|
||||
graph_exec_id: str
|
||||
|
||||
|
||||
class CreateGraph(pydantic.BaseModel):
|
||||
|
||||
@@ -51,6 +51,7 @@ from backend.server.model import (
|
||||
CreateAPIKeyRequest,
|
||||
CreateAPIKeyResponse,
|
||||
CreateGraph,
|
||||
ExecuteGraphResponse,
|
||||
RequestTopUp,
|
||||
SetGraphActiveVersion,
|
||||
UpdatePermissionsRequest,
|
||||
@@ -178,6 +179,8 @@ async def configure_user_auto_top_up(
|
||||
) -> str:
|
||||
if request.threshold < 0:
|
||||
raise ValueError("Threshold must be greater than 0")
|
||||
if request.amount < 500 and request.amount != 0:
|
||||
raise ValueError("Amount must be greater than or equal to 500")
|
||||
if request.amount < request.threshold:
|
||||
raise ValueError("Amount must be greater than or equal to threshold")
|
||||
|
||||
@@ -240,7 +243,7 @@ async def manage_payment_method(
|
||||
) -> dict[str, str]:
|
||||
session = stripe.billing_portal.Session.create(
|
||||
customer=await get_stripe_customer_id(user_id),
|
||||
return_url=settings.config.platform_base_url + "/marketplace/credits",
|
||||
return_url=settings.config.frontend_base_url + "/marketplace/credits",
|
||||
)
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
@@ -509,7 +512,7 @@ async def set_graph_active_version(
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
path="/graphs/{graph_id}/execute",
|
||||
path="/graphs/{graph_id}/execute/{graph_version}",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -518,13 +521,13 @@ def execute_graph(
|
||||
node_input: dict[Any, Any],
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
graph_version: Optional[int] = None,
|
||||
) -> dict[str, Any]: # FIXME: add proper return type
|
||||
) -> ExecuteGraphResponse:
|
||||
try:
|
||||
logger.info("Node input: %s", node_input)
|
||||
graph_exec = execution_manager_client().add_execution(
|
||||
graph_id, node_input, user_id=user_id, graph_version=graph_version
|
||||
)
|
||||
return {"id": graph_exec.graph_exec_id}
|
||||
return ExecuteGraphResponse(graph_exec_id=graph_exec.graph_exec_id)
|
||||
except Exception as e:
|
||||
msg = e.__str__().encode().decode("unicode_escape")
|
||||
raise HTTPException(status_code=400, detail=msg)
|
||||
@@ -586,6 +589,7 @@ class ScheduleCreationRequest(pydantic.BaseModel):
|
||||
cron: str
|
||||
input_data: dict[Any, Any]
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
@@ -597,10 +601,13 @@ async def create_schedule(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
schedule: ScheduleCreationRequest,
|
||||
) -> scheduler.JobInfo:
|
||||
graph = await graph_db.get_graph(schedule.graph_id, user_id=user_id)
|
||||
graph = await graph_db.get_graph(
|
||||
schedule.graph_id, schedule.graph_version, user_id=user_id
|
||||
)
|
||||
if not graph:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Graph #{schedule.graph_id} not found."
|
||||
status_code=404,
|
||||
detail=f"Graph #{schedule.graph_id} v.{schedule.graph_version} not found.",
|
||||
)
|
||||
|
||||
return await asyncio.to_thread(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import random
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
@@ -17,6 +16,25 @@ from backend.data.graph import GraphModel
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def sanitize_query(query: str | None) -> str | None:
|
||||
if query is None:
|
||||
return query
|
||||
query = query.strip()[:100]
|
||||
return (
|
||||
query.replace("\\", "\\\\")
|
||||
.replace("%", "\\%")
|
||||
.replace("_", "\\_")
|
||||
.replace("[", "\\[")
|
||||
.replace("]", "\\]")
|
||||
.replace("'", "\\'")
|
||||
.replace('"', '\\"')
|
||||
.replace(";", "\\;")
|
||||
.replace("--", "\\--")
|
||||
.replace("/*", "\\/*")
|
||||
.replace("*/", "\\*/")
|
||||
)
|
||||
|
||||
|
||||
async def get_store_agents(
|
||||
featured: bool = False,
|
||||
creator: str | None = None,
|
||||
@@ -29,29 +47,7 @@ async def get_store_agents(
|
||||
logger.debug(
|
||||
f"Getting store agents. featured={featured}, creator={creator}, sorted_by={sorted_by}, search={search_query}, category={category}, page={page}"
|
||||
)
|
||||
sanitized_query = None
|
||||
# Sanitize and validate search query by escaping special characters
|
||||
if search_query is not None:
|
||||
sanitized_query = search_query.strip()
|
||||
if not sanitized_query or len(sanitized_query) > 100: # Reasonable length limit
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
f"Invalid search query: len({len(sanitized_query)}) query: {search_query}"
|
||||
)
|
||||
|
||||
# Escape special SQL characters
|
||||
sanitized_query = (
|
||||
sanitized_query.replace("\\", "\\\\")
|
||||
.replace("%", "\\%")
|
||||
.replace("_", "\\_")
|
||||
.replace("[", "\\[")
|
||||
.replace("]", "\\]")
|
||||
.replace("'", "\\'")
|
||||
.replace('"', '\\"')
|
||||
.replace(";", "\\;")
|
||||
.replace("--", "\\--")
|
||||
.replace("/*", "\\/*")
|
||||
.replace("*/", "\\*/")
|
||||
)
|
||||
sanitized_query = sanitize_query(search_query)
|
||||
|
||||
where_clause = {}
|
||||
if featured:
|
||||
@@ -93,8 +89,8 @@ async def get_store_agents(
|
||||
slug=agent.slug,
|
||||
agent_name=agent.agent_name,
|
||||
agent_image=agent.agent_image[0] if agent.agent_image else "",
|
||||
creator=agent.creator_username,
|
||||
creator_avatar=agent.creator_avatar,
|
||||
creator=agent.creator_username or "Needs Profile",
|
||||
creator_avatar=agent.creator_avatar or "",
|
||||
sub_heading=agent.sub_heading,
|
||||
description=agent.description,
|
||||
runs=agent.runs,
|
||||
@@ -114,7 +110,7 @@ async def get_store_agents(
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting store agents: {str(e)}")
|
||||
logger.error(f"Error getting store agents: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to fetch store agents"
|
||||
) from e
|
||||
@@ -156,7 +152,7 @@ async def get_store_agent_details(
|
||||
except backend.server.v2.store.exceptions.AgentNotFoundError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting store agent details: {str(e)}")
|
||||
logger.error(f"Error getting store agent details: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to fetch agent details"
|
||||
) from e
|
||||
@@ -270,7 +266,7 @@ async def get_store_creators(
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting store creators: {str(e)}")
|
||||
logger.error(f"Error getting store creators: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to fetch store creators"
|
||||
) from e
|
||||
@@ -307,7 +303,7 @@ async def get_store_creator_details(
|
||||
except backend.server.v2.store.exceptions.CreatorNotFoundError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting store creator details: {str(e)}")
|
||||
logger.error(f"Error getting store creator details: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to fetch creator details"
|
||||
) from e
|
||||
@@ -366,7 +362,7 @@ async def get_store_submissions(
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching store submissions: {str(e)}")
|
||||
logger.error(f"Error fetching store submissions: {e}")
|
||||
# Return empty response rather than exposing internal errors
|
||||
return backend.server.v2.store.model.StoreSubmissionsResponse(
|
||||
submissions=[],
|
||||
@@ -416,7 +412,7 @@ async def delete_store_submission(
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting store submission: {str(e)}")
|
||||
logger.error(f"Error deleting store submission: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -539,7 +535,7 @@ async def create_store_submission(
|
||||
):
|
||||
raise
|
||||
except prisma.errors.PrismaError as e:
|
||||
logger.error(f"Database error creating store submission: {str(e)}")
|
||||
logger.error(f"Database error creating store submission: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to create store submission"
|
||||
) from e
|
||||
@@ -579,7 +575,7 @@ async def create_store_review(
|
||||
)
|
||||
|
||||
except prisma.errors.PrismaError as e:
|
||||
logger.error(f"Database error creating store review: {str(e)}")
|
||||
logger.error(f"Database error creating store review: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to create store review"
|
||||
) from e
|
||||
@@ -587,7 +583,7 @@ async def create_store_review(
|
||||
|
||||
async def get_user_profile(
|
||||
user_id: str,
|
||||
) -> backend.server.v2.store.model.ProfileDetails:
|
||||
) -> backend.server.v2.store.model.ProfileDetails | None:
|
||||
logger.debug(f"Getting user profile for {user_id}")
|
||||
|
||||
try:
|
||||
@@ -596,25 +592,7 @@ async def get_user_profile(
|
||||
)
|
||||
|
||||
if not profile:
|
||||
logger.warning(f"Profile not found for user {user_id}")
|
||||
new_profile = await prisma.models.Profile.prisma().create(
|
||||
data=prisma.types.ProfileCreateInput(
|
||||
userId=user_id,
|
||||
name="No Profile Data",
|
||||
username=f"{random.choice(['happy', 'clever', 'swift', 'bright', 'wise'])}-{random.choice(['fox', 'wolf', 'bear', 'eagle', 'owl'])}_{random.randint(1000,9999)}".lower(),
|
||||
description="No Profile Data",
|
||||
links=[],
|
||||
avatarUrl="",
|
||||
)
|
||||
)
|
||||
return backend.server.v2.store.model.ProfileDetails(
|
||||
name=new_profile.name,
|
||||
username=new_profile.username,
|
||||
description=new_profile.description,
|
||||
links=new_profile.links,
|
||||
avatar_url=new_profile.avatarUrl,
|
||||
)
|
||||
|
||||
return None
|
||||
return backend.server.v2.store.model.ProfileDetails(
|
||||
name=profile.name,
|
||||
username=profile.username,
|
||||
@@ -623,115 +601,90 @@ async def get_user_profile(
|
||||
avatar_url=profile.avatarUrl,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user profile: {str(e)}")
|
||||
return backend.server.v2.store.model.ProfileDetails(
|
||||
name="No Profile Data",
|
||||
username="No Profile Data",
|
||||
description="No Profile Data",
|
||||
links=[],
|
||||
avatar_url="",
|
||||
)
|
||||
logger.error("Error getting user profile: %s", e)
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to get user profile"
|
||||
) from e
|
||||
|
||||
|
||||
async def update_or_create_profile(
|
||||
async def update_profile(
|
||||
user_id: str, profile: backend.server.v2.store.model.Profile
|
||||
) -> backend.server.v2.store.model.CreatorDetails:
|
||||
"""
|
||||
Update the store profile for a user. Creates a new profile if one doesn't exist.
|
||||
Only allows updating if the user_id matches the owning user.
|
||||
If a field is None, it will not overwrite the existing value in the case of an update.
|
||||
|
||||
Update the store profile for a user or create a new one if it doesn't exist.
|
||||
Args:
|
||||
user_id: ID of the authenticated user
|
||||
profile: Updated profile details
|
||||
|
||||
Returns:
|
||||
CreatorDetails: The updated profile
|
||||
|
||||
CreatorDetails: The updated or created profile details
|
||||
Raises:
|
||||
HTTPException: If user is not authorized to update this profile
|
||||
DatabaseError: If profile cannot be updated due to database issues
|
||||
DatabaseError: If there's an issue updating or creating the profile
|
||||
"""
|
||||
logger.info(f"Updating profile for user {user_id} data: {profile}")
|
||||
|
||||
logger.info("Updating profile for user %s with data: %s", user_id, profile)
|
||||
try:
|
||||
# Sanitize username to only allow letters and hyphens
|
||||
# Sanitize username to allow only letters, numbers, and hyphens
|
||||
username = "".join(
|
||||
c if c.isalpha() or c == "-" or c.isnumeric() else ""
|
||||
for c in profile.username
|
||||
).lower()
|
||||
|
||||
# Check if profile exists for the given user_id
|
||||
existing_profile = await prisma.models.Profile.prisma().find_first(
|
||||
where={"userId": user_id}
|
||||
)
|
||||
|
||||
# If no profile exists, create a new one
|
||||
if not existing_profile:
|
||||
logger.debug(
|
||||
f"No existing profile found. Creating new profile for user {user_id}"
|
||||
)
|
||||
# Create new profile since one doesn't exist
|
||||
new_profile = await prisma.models.Profile.prisma().create(
|
||||
data={
|
||||
"userId": user_id,
|
||||
"name": profile.name,
|
||||
"username": username,
|
||||
"description": profile.description,
|
||||
"links": profile.links or [],
|
||||
"avatarUrl": profile.avatar_url,
|
||||
"isFeatured": False,
|
||||
}
|
||||
raise backend.server.v2.store.exceptions.ProfileNotFoundError(
|
||||
f"Profile not found for user {user_id}. This should not be possible."
|
||||
)
|
||||
|
||||
return backend.server.v2.store.model.CreatorDetails(
|
||||
name=new_profile.name,
|
||||
username=new_profile.username,
|
||||
description=new_profile.description,
|
||||
links=new_profile.links,
|
||||
avatar_url=new_profile.avatarUrl or "",
|
||||
agent_rating=0.0,
|
||||
agent_runs=0,
|
||||
top_categories=[],
|
||||
# Verify that the user is authorized to update this profile
|
||||
if existing_profile.userId != user_id:
|
||||
logger.error(
|
||||
"Unauthorized update attempt for profile %s by user %s",
|
||||
existing_profile.userId,
|
||||
user_id,
|
||||
)
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
f"Unauthorized update attempt for profile {existing_profile.id} by user {user_id}"
|
||||
)
|
||||
else:
|
||||
logger.debug(f"Updating existing profile for user {user_id}")
|
||||
# Update only provided fields for the existing profile
|
||||
update_data = {}
|
||||
if profile.name is not None:
|
||||
update_data["name"] = profile.name
|
||||
if profile.username is not None:
|
||||
update_data["username"] = username
|
||||
if profile.description is not None:
|
||||
update_data["description"] = profile.description
|
||||
if profile.links is not None:
|
||||
update_data["links"] = profile.links
|
||||
if profile.avatar_url is not None:
|
||||
update_data["avatarUrl"] = profile.avatar_url
|
||||
|
||||
# Update the existing profile
|
||||
updated_profile = await prisma.models.Profile.prisma().update(
|
||||
where={"id": existing_profile.id},
|
||||
data=prisma.types.ProfileUpdateInput(**update_data),
|
||||
)
|
||||
if updated_profile is None:
|
||||
logger.error(f"Failed to update profile for user {user_id}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to update profile"
|
||||
)
|
||||
logger.debug("Updating existing profile for user %s", user_id)
|
||||
# Prepare update data, only including non-None values
|
||||
update_data = {}
|
||||
if profile.name is not None:
|
||||
update_data["name"] = profile.name
|
||||
if profile.username is not None:
|
||||
update_data["username"] = username
|
||||
if profile.description is not None:
|
||||
update_data["description"] = profile.description
|
||||
if profile.links is not None:
|
||||
update_data["links"] = profile.links
|
||||
if profile.avatar_url is not None:
|
||||
update_data["avatarUrl"] = profile.avatar_url
|
||||
|
||||
return backend.server.v2.store.model.CreatorDetails(
|
||||
name=updated_profile.name,
|
||||
username=updated_profile.username,
|
||||
description=updated_profile.description,
|
||||
links=updated_profile.links,
|
||||
avatar_url=updated_profile.avatarUrl or "",
|
||||
agent_rating=0.0,
|
||||
agent_runs=0,
|
||||
top_categories=[],
|
||||
# Update the existing profile
|
||||
updated_profile = await prisma.models.Profile.prisma().update(
|
||||
where={"id": existing_profile.id},
|
||||
data=prisma.types.ProfileUpdateInput(**update_data),
|
||||
)
|
||||
if updated_profile is None:
|
||||
logger.error("Failed to update profile for user %s", user_id)
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to update profile"
|
||||
)
|
||||
|
||||
return backend.server.v2.store.model.CreatorDetails(
|
||||
name=updated_profile.name,
|
||||
username=updated_profile.username,
|
||||
description=updated_profile.description,
|
||||
links=updated_profile.links,
|
||||
avatar_url=updated_profile.avatarUrl or "",
|
||||
agent_rating=0.0,
|
||||
agent_runs=0,
|
||||
top_categories=[],
|
||||
)
|
||||
|
||||
except prisma.errors.PrismaError as e:
|
||||
logger.error(f"Database error updating profile: {str(e)}")
|
||||
logger.error("Database error updating profile: %s", e)
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to update profile"
|
||||
) from e
|
||||
@@ -796,7 +749,7 @@ async def get_my_agents(
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting my agents: {str(e)}")
|
||||
logger.error(f"Error getting my agents: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to fetch my agents"
|
||||
) from e
|
||||
@@ -809,7 +762,7 @@ async def get_agent(
|
||||
try:
|
||||
store_listing_version = (
|
||||
await prisma.models.StoreListingVersion.prisma().find_unique(
|
||||
where={"id": store_listing_version_id}
|
||||
where={"id": store_listing_version_id}, include={"Agent": True}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -840,7 +793,7 @@ async def get_agent(
|
||||
return graph
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting agent: {str(e)}")
|
||||
logger.error(f"Error getting agent: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to fetch agent"
|
||||
) from e
|
||||
@@ -905,7 +858,7 @@ async def review_store_submission(
|
||||
return submission
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Could not create store submission review: {str(e)}")
|
||||
logger.error(f"Could not create store submission review: {e}")
|
||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||
"Failed to create store submission review"
|
||||
) from e
|
||||
|
||||
@@ -158,6 +158,26 @@ async def test_create_store_submission(mocker):
|
||||
agentId="agent-id",
|
||||
agentVersion=1,
|
||||
owningUserId="user-id",
|
||||
StoreListingVersions=[
|
||||
prisma.models.StoreListingVersion(
|
||||
id="version-id",
|
||||
agentId="agent-id",
|
||||
agentVersion=1,
|
||||
slug="test-agent",
|
||||
name="Test Agent",
|
||||
description="Test description",
|
||||
createdAt=datetime.now(),
|
||||
updatedAt=datetime.now(),
|
||||
subHeading="Test heading",
|
||||
imageUrls=["image.jpg"],
|
||||
categories=["test"],
|
||||
isFeatured=False,
|
||||
isDeleted=False,
|
||||
version=1,
|
||||
isAvailable=True,
|
||||
isApproved=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Mock prisma calls
|
||||
@@ -181,6 +201,7 @@ async def test_create_store_submission(mocker):
|
||||
# Verify results
|
||||
assert result.name == "Test Agent"
|
||||
assert result.description == "Test description"
|
||||
assert result.store_listing_version_id == "version-id"
|
||||
|
||||
# Verify mocks called correctly
|
||||
mock_agent_graph.return_value.find_first.assert_called_once()
|
||||
@@ -195,6 +216,7 @@ async def test_update_profile(mocker):
|
||||
id="profile-id",
|
||||
name="Test Creator",
|
||||
username="creator",
|
||||
userId="user-id",
|
||||
description="Test description",
|
||||
links=["link1"],
|
||||
avatarUrl="avatar.jpg",
|
||||
@@ -221,7 +243,7 @@ async def test_update_profile(mocker):
|
||||
)
|
||||
|
||||
# Call function
|
||||
result = await db.update_or_create_profile("user-id", profile)
|
||||
result = await db.update_profile("user-id", profile)
|
||||
|
||||
# Verify results
|
||||
assert result.username == "creator"
|
||||
@@ -237,7 +259,7 @@ async def test_get_user_profile(mocker):
|
||||
# Mock data
|
||||
mock_profile = prisma.models.Profile(
|
||||
id="profile-id",
|
||||
name="No Profile Data",
|
||||
name="Test User",
|
||||
username="testuser",
|
||||
description="Test description",
|
||||
links=["link1", "link2"],
|
||||
@@ -245,20 +267,22 @@ async def test_get_user_profile(mocker):
|
||||
isFeatured=False,
|
||||
createdAt=datetime.now(),
|
||||
updatedAt=datetime.now(),
|
||||
userId="user-id",
|
||||
)
|
||||
|
||||
# Mock prisma calls
|
||||
mock_profile_db = mocker.patch("prisma.models.Profile.prisma")
|
||||
mock_profile_db.return_value.find_unique = mocker.AsyncMock(
|
||||
mock_profile_db.return_value.find_first = mocker.AsyncMock(
|
||||
return_value=mock_profile
|
||||
)
|
||||
|
||||
# Call function
|
||||
result = await db.get_user_profile("user-id")
|
||||
|
||||
assert result is not None
|
||||
# Verify results
|
||||
assert result.name == "No Profile Data"
|
||||
assert result.username == "No Profile Data"
|
||||
assert result.description == "No Profile Data"
|
||||
assert result.links == []
|
||||
assert result.avatar_url == ""
|
||||
assert result.name == "Test User"
|
||||
assert result.username == "testuser"
|
||||
assert result.description == "Test description"
|
||||
assert result.links == ["link1", "link2"]
|
||||
assert result.avatar_url == "avatar.jpg"
|
||||
|
||||
@@ -42,6 +42,11 @@ async def get_profile(
|
||||
"""
|
||||
try:
|
||||
profile = await backend.server.v2.store.db.get_user_profile(user_id)
|
||||
if profile is None:
|
||||
return fastapi.responses.JSONResponse(
|
||||
status_code=404,
|
||||
content={"detail": "Profile not found"},
|
||||
)
|
||||
return profile
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst getting user profile")
|
||||
@@ -77,7 +82,7 @@ async def update_or_create_profile(
|
||||
HTTPException: If there is an error updating the profile
|
||||
"""
|
||||
try:
|
||||
updated_profile = await backend.server.v2.store.db.update_or_create_profile(
|
||||
updated_profile = await backend.server.v2.store.db.update_profile(
|
||||
user_id=user_id, profile=profile
|
||||
)
|
||||
return updated_profile
|
||||
|
||||
@@ -86,13 +86,13 @@ async def handle_subscribe(
|
||||
)
|
||||
else:
|
||||
ex_sub = ExecutionSubscription.model_validate(message.data)
|
||||
await manager.subscribe(ex_sub.graph_id, websocket)
|
||||
await manager.subscribe(ex_sub.graph_id, ex_sub.graph_version, websocket)
|
||||
logger.debug(f"New execution subscription for graph {ex_sub.graph_id}")
|
||||
await websocket.send_text(
|
||||
WsMessage(
|
||||
method=Methods.SUBSCRIBE,
|
||||
success=True,
|
||||
channel=ex_sub.graph_id,
|
||||
channel=f"{ex_sub.graph_id}_{ex_sub.graph_version}",
|
||||
).model_dump_json()
|
||||
)
|
||||
|
||||
@@ -110,13 +110,13 @@ async def handle_unsubscribe(
|
||||
)
|
||||
else:
|
||||
ex_sub = ExecutionSubscription.model_validate(message.data)
|
||||
await manager.unsubscribe(ex_sub.graph_id, websocket)
|
||||
await manager.unsubscribe(ex_sub.graph_id, ex_sub.graph_version, websocket)
|
||||
logger.debug(f"Removed execution subscription for graph {ex_sub.graph_id}")
|
||||
await websocket.send_text(
|
||||
WsMessage(
|
||||
method=Methods.UNSUBSCRIBE,
|
||||
success=True,
|
||||
channel=ex_sub.graph_id,
|
||||
channel=f"{ex_sub.graph_id}_{ex_sub.graph_version}",
|
||||
).model_dump_json()
|
||||
)
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ async def block_autogen_agent():
|
||||
print(response)
|
||||
result = await wait_execution(
|
||||
graph_id=test_graph.id,
|
||||
graph_exec_id=response["id"],
|
||||
graph_exec_id=response.graph_exec_id,
|
||||
timeout=1200,
|
||||
user_id=test_user.id,
|
||||
)
|
||||
|
||||
@@ -160,7 +160,9 @@ async def reddit_marketing_agent():
|
||||
test_graph.id, input_data, test_user.id
|
||||
)
|
||||
print(response)
|
||||
result = await wait_execution(test_user.id, test_graph.id, response["id"], 120)
|
||||
result = await wait_execution(
|
||||
test_user.id, test_graph.id, response.graph_exec_id, 120
|
||||
)
|
||||
print(result)
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,9 @@ async def sample_agent():
|
||||
test_graph.id, input_data, test_user.id
|
||||
)
|
||||
print(response)
|
||||
result = await wait_execution(test_user.id, test_graph.id, response["id"], 10)
|
||||
result = await wait_execution(
|
||||
test_user.id, test_graph.id, response.graph_exec_id, 10
|
||||
)
|
||||
print(result)
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from typing import (
|
||||
FrozenSet,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
@@ -33,7 +34,7 @@ from pydantic import BaseModel
|
||||
from Pyro5 import api as pyro
|
||||
from Pyro5 import config as pyro_config
|
||||
|
||||
from backend.data import db, redis
|
||||
from backend.data import db, rabbitmq, redis
|
||||
from backend.util.process import AppProcess
|
||||
from backend.util.retry import conn_retry
|
||||
from backend.util.settings import Config, Secrets
|
||||
@@ -116,6 +117,9 @@ class AppService(AppProcess, ABC):
|
||||
shared_event_loop: asyncio.AbstractEventLoop
|
||||
use_db: bool = False
|
||||
use_redis: bool = False
|
||||
use_async: bool = False
|
||||
use_rabbitmq: Optional[rabbitmq.RabbitMQConfig] = None
|
||||
rabbitmq_service: Optional[rabbitmq.SyncRabbitMQ | rabbitmq.AsyncRabbitMQ] = None
|
||||
use_supabase: bool = False
|
||||
|
||||
def __init__(self):
|
||||
@@ -130,6 +134,20 @@ class AppService(AppProcess, ABC):
|
||||
def get_host(cls) -> str:
|
||||
return os.environ.get(f"{cls.service_name.upper()}_HOST", config.pyro_host)
|
||||
|
||||
@property
|
||||
def rabbit(self) -> rabbitmq.SyncRabbitMQ | rabbitmq.AsyncRabbitMQ:
|
||||
"""Access the RabbitMQ service. Will raise if not configured."""
|
||||
if not self.rabbitmq_service:
|
||||
raise RuntimeError("RabbitMQ not configured for this service")
|
||||
return self.rabbitmq_service
|
||||
|
||||
@property
|
||||
def rabbit_config(self) -> rabbitmq.RabbitMQConfig:
|
||||
"""Access the RabbitMQ config. Will raise if not configured."""
|
||||
if not self.use_rabbitmq:
|
||||
raise RuntimeError("RabbitMQ not configured for this service")
|
||||
return self.use_rabbitmq
|
||||
|
||||
def run_service(self) -> None:
|
||||
while True:
|
||||
time.sleep(10)
|
||||
@@ -147,6 +165,16 @@ class AppService(AppProcess, ABC):
|
||||
self.shared_event_loop.run_until_complete(db.connect())
|
||||
if self.use_redis:
|
||||
redis.connect()
|
||||
if self.use_rabbitmq:
|
||||
logger.info(f"[{self.__class__.__name__}] ⏳ Configuring RabbitMQ...")
|
||||
if self.use_async:
|
||||
self.rabbitmq_service = rabbitmq.AsyncRabbitMQ(self.use_rabbitmq)
|
||||
self.shared_event_loop.run_until_complete(
|
||||
self.rabbitmq_service.connect()
|
||||
)
|
||||
else:
|
||||
self.rabbitmq_service = rabbitmq.SyncRabbitMQ(self.use_rabbitmq)
|
||||
self.rabbitmq_service.connect()
|
||||
if self.use_supabase:
|
||||
from supabase import create_client
|
||||
|
||||
@@ -175,6 +203,8 @@ class AppService(AppProcess, ABC):
|
||||
if self.use_redis:
|
||||
logger.info(f"[{self.__class__.__name__}] ⏳ Disconnecting Redis...")
|
||||
redis.disconnect()
|
||||
if self.use_rabbitmq:
|
||||
logger.info(f"[{self.__class__.__name__}] ⏳ Disconnecting RabbitMQ...")
|
||||
|
||||
@conn_retry("Pyro", "Starting Pyro Service")
|
||||
def __start_pyro(self):
|
||||
|
||||
@@ -167,6 +167,20 @@ class Config(UpdateTrackingModel["Config"], BaseSettings):
|
||||
description="The pool size for the scheduler database connection pool",
|
||||
)
|
||||
|
||||
rabbitmq_host: str = Field(
|
||||
default="localhost",
|
||||
description="The host for the RabbitMQ server",
|
||||
)
|
||||
rabbitmq_port: int = Field(
|
||||
default=5672,
|
||||
description="The port for the RabbitMQ server",
|
||||
)
|
||||
|
||||
rabbitmq_vhost: str = Field(
|
||||
default="/",
|
||||
description="The vhost for the RabbitMQ server",
|
||||
)
|
||||
|
||||
@field_validator("platform_base_url", "frontend_base_url")
|
||||
@classmethod
|
||||
def validate_platform_base_url(cls, v: str, info: ValidationInfo) -> str:
|
||||
@@ -258,6 +272,11 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
||||
|
||||
encryption_key: str = Field(default="", description="Encryption key")
|
||||
|
||||
rabbitmq_default_user: str = Field(default="", description="RabbitMQ default user")
|
||||
rabbitmq_default_pass: str = Field(
|
||||
default="", description="RabbitMQ default password"
|
||||
)
|
||||
|
||||
# OAuth server credentials for integrations
|
||||
# --8<-- [start:OAuthServerCredentialsExample]
|
||||
github_client_id: str = Field(default="", description="GitHub OAuth client ID")
|
||||
@@ -326,6 +345,8 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
||||
stripe_api_key: str = Field(default="", description="Stripe API Key")
|
||||
stripe_webhook_secret: str = Field(default="", description="Stripe Webhook Secret")
|
||||
|
||||
screenshotone_api_key: str = Field(default="", description="ScreenshotOne API Key")
|
||||
|
||||
# Add more secret fields as needed
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
|
||||
@@ -106,11 +106,11 @@ def execute_block_test(block: Block):
|
||||
|
||||
# Populate credentials argument(s)
|
||||
extra_exec_kwargs: dict = {
|
||||
"graph_id": uuid.uuid4(),
|
||||
"node_id": uuid.uuid4(),
|
||||
"graph_exec_id": uuid.uuid4(),
|
||||
"node_exec_id": uuid.uuid4(),
|
||||
"user_id": uuid.uuid4(),
|
||||
"graph_id": str(uuid.uuid4()),
|
||||
"node_id": str(uuid.uuid4()),
|
||||
"graph_exec_id": str(uuid.uuid4()),
|
||||
"node_exec_id": str(uuid.uuid4()),
|
||||
"user_id": str(uuid.uuid4()),
|
||||
}
|
||||
input_model = cast(type[BlockSchema], block.input_schema)
|
||||
credentials_input_fields = input_model.get_credentials_fields()
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
CREATE OR REPLACE FUNCTION generate_username()
|
||||
RETURNS TEXT AS $$
|
||||
DECLARE
|
||||
-- Random username generation
|
||||
selected_adjective TEXT;
|
||||
selected_animal TEXT;
|
||||
random_int INT;
|
||||
generated_username TEXT;
|
||||
BEGIN
|
||||
FOR i IN 1..10 LOOP
|
||||
SELECT unnest
|
||||
INTO selected_adjective
|
||||
FROM (VALUES ('happy'), ('clever'), ('swift'), ('bright'), ('wise'), ('funny'), ('cool'), ('awesome'), ('amazing'), ('fantastic'), ('wonderful')) AS t(unnest)
|
||||
ORDER BY random()
|
||||
LIMIT 1;
|
||||
|
||||
SELECT unnest
|
||||
INTO selected_animal
|
||||
FROM (VALUES ('fox'), ('wolf'), ('bear'), ('eagle'), ('owl'), ('tiger'), ('lion'), ('elephant'), ('giraffe'), ('zebra')) AS t(unnest)
|
||||
ORDER BY random()
|
||||
LIMIT 1;
|
||||
|
||||
SELECT floor(random() * (99999 - 10000 + 1) + 10000)::int
|
||||
INTO random_int;
|
||||
|
||||
generated_username := lower(selected_adjective || '-' || selected_animal || '-' || random_int);
|
||||
|
||||
-- Check if username is already taken
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM platform."Profile" WHERE username = generated_username
|
||||
) THEN
|
||||
-- Username is unique, exit the loop
|
||||
EXIT;
|
||||
END IF;
|
||||
|
||||
-- If we've tried 10 times and still haven't found a unique username
|
||||
IF i = 10 THEN
|
||||
RAISE EXCEPTION 'Unable to generate unique username after 10 attempts';
|
||||
END IF;
|
||||
END LOOP;
|
||||
RETURN generated_username;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE OR REPLACE FUNCTION add_user_and_profile_to_platform()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Exit early if NEW.id is null to prevent constraint violations
|
||||
IF NEW.id IS NULL THEN
|
||||
RAISE EXCEPTION 'Cannot create user/profile: id is null';
|
||||
END IF;
|
||||
|
||||
/*
|
||||
1) Insert into platform."User"
|
||||
(If you already have such a row or want different columns, adjust below.)
|
||||
*/
|
||||
INSERT INTO platform."User" (id, email, "updatedAt")
|
||||
VALUES (NEW.id, NEW.email, now());
|
||||
|
||||
/*
|
||||
2) Insert into platform."Profile"
|
||||
Adjust columns/types depending on how your "Profile" schema is defined:
|
||||
- "links" might be text[], jsonb, or something else in your table.
|
||||
- "avatarUrl" and "description" can be defaulted as well.
|
||||
*/
|
||||
INSERT INTO platform."Profile"
|
||||
("id", "userId", name, username, description, links, "avatarUrl", "updatedAt")
|
||||
VALUES
|
||||
(
|
||||
NEW.id,
|
||||
NEW.id,
|
||||
COALESCE(split_part(NEW.email, '@', 1), 'user'), -- handle null email
|
||||
platform.generate_username(),
|
||||
'I''m new here',
|
||||
'{}', -- empty array or empty JSON, depending on your column definition
|
||||
'',
|
||||
now()
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
-- Log the error details
|
||||
RAISE NOTICE 'Error in add_user_and_profile_to_platform: %', SQLERRM;
|
||||
-- Re-raise the error
|
||||
RAISE;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if the auth schema and users table exist
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'auth'
|
||||
AND table_name = 'users'
|
||||
) THEN
|
||||
-- Drop the trigger if it exists
|
||||
DROP TRIGGER IF EXISTS user_added_to_platform ON auth.users;
|
||||
|
||||
-- Create the trigger
|
||||
CREATE TRIGGER user_added_to_platform
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION add_user_and_profile_to_platform();
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'platform'
|
||||
AND table_name = 'User'
|
||||
) AND EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'platform'
|
||||
AND table_name = 'Profile'
|
||||
) THEN
|
||||
INSERT INTO platform."Profile"
|
||||
("id", "userId", name, username, description, links, "avatarUrl", "updatedAt")
|
||||
SELECT
|
||||
u.id,
|
||||
u.id,
|
||||
COALESCE(split_part(u.email, '@', 1), 'user'),
|
||||
platform.generate_username(),
|
||||
'I''m new here',
|
||||
'{}',
|
||||
'',
|
||||
now()
|
||||
FROM platform."User" u
|
||||
LEFT JOIN platform."Profile" p ON u.id = p."userId"
|
||||
WHERE p.id IS NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
171
autogpt_platform/backend/poetry.lock
generated
171
autogpt_platform/backend/poetry.lock
generated
@@ -118,7 +118,6 @@ files = [
|
||||
[package.dependencies]
|
||||
aiohappyeyeballs = ">=2.3.0"
|
||||
aiosignal = ">=1.1.2"
|
||||
async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""}
|
||||
attrs = ">=17.3.0"
|
||||
frozenlist = ">=1.1.1"
|
||||
multidict = ">=4.5,<7.0"
|
||||
@@ -209,7 +208,6 @@ files = [
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
||||
@@ -293,14 +291,14 @@ develop = true
|
||||
[package.dependencies]
|
||||
colorama = "^0.4.6"
|
||||
expiringdict = "^1.2.2"
|
||||
google-cloud-logging = "^3.11.3"
|
||||
pydantic = "^2.10.5"
|
||||
google-cloud-logging = "^3.11.4"
|
||||
pydantic = "^2.10.6"
|
||||
pydantic-settings = "^2.7.1"
|
||||
pyjwt = "^2.10.1"
|
||||
pytest-asyncio = "^0.25.2"
|
||||
pytest-asyncio = "^0.25.3"
|
||||
pytest-mock = "^3.14.0"
|
||||
python-dotenv = "^1.0.1"
|
||||
supabase = "^2.11.0"
|
||||
supabase = "^2.13.0"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -324,7 +322,7 @@ version = "24.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
|
||||
@@ -356,8 +354,6 @@ mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
@@ -763,14 +759,14 @@ typing-extensions = ">=4.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "e2b-code-interpreter"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
description = "E2B Code Interpreter - Stateful code execution"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "e2b_code_interpreter-1.0.4-py3-none-any.whl", hash = "sha256:e8cea4946b3457072a524250aee712f7f8d44834b91cd9c13da3bdf96eda1a6e"},
|
||||
{file = "e2b_code_interpreter-1.0.4.tar.gz", hash = "sha256:fec5651d98ca0d03dd038c5df943a0beaeb59c6d422112356f55f2b662d8dea1"},
|
||||
{file = "e2b_code_interpreter-1.0.5-py3-none-any.whl", hash = "sha256:4c7814e9eabba58097bf5e4019d327b3a82fab0813eafca4311b29ca6ea0639d"},
|
||||
{file = "e2b_code_interpreter-1.0.5.tar.gz", hash = "sha256:e7f70b039e6a70f8e592f90f806d696dc1056919414daabeb89e86c9b650a987"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -784,12 +780,11 @@ version = "1.2.2"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main", "dev"]
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
]
|
||||
markers = {dev = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
@@ -827,14 +822,14 @@ typing-extensions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.7"
|
||||
version = "0.115.8"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "fastapi-0.115.7-py3-none-any.whl", hash = "sha256:eb6a8c8bf7f26009e8147111ff15b5177a0e19bb4a45bc3486ab14804539d21e"},
|
||||
{file = "fastapi-0.115.7.tar.gz", hash = "sha256:0f106da6c01d88a6786b3248fb4d7a940d071f6f488488898ad5d354b25ed015"},
|
||||
{file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"},
|
||||
{file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -995,14 +990,8 @@ files = [
|
||||
[package.dependencies]
|
||||
google-auth = ">=2.14.1,<3.0.dev0"
|
||||
googleapis-common-protos = ">=1.56.2,<2.0.dev0"
|
||||
grpcio = [
|
||||
{version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
|
||||
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
|
||||
]
|
||||
grpcio-status = [
|
||||
{version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
|
||||
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
|
||||
]
|
||||
grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
|
||||
grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
|
||||
proto-plus = ">=1.22.3,<2.0.0dev"
|
||||
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0"
|
||||
requests = ">=2.18.0,<3.0.0.dev0"
|
||||
@@ -1147,14 +1136,14 @@ grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-cloud-logging"
|
||||
version = "3.11.3"
|
||||
version = "3.11.4"
|
||||
description = "Stackdriver Logging API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google_cloud_logging-3.11.3-py2.py3-none-any.whl", hash = "sha256:b8ec23f2998f76a58f8492db26a0f4151dd500425c3f08448586b85972f3c494"},
|
||||
{file = "google_cloud_logging-3.11.3.tar.gz", hash = "sha256:0a73cd94118875387d4535371d9e9426861edef8e44fba1261e86782d5b8d54f"},
|
||||
{file = "google_cloud_logging-3.11.4-py2.py3-none-any.whl", hash = "sha256:1d465ac62df29fb94bba4d6b4891035e57d573d84541dd8a40eebbc74422b2f0"},
|
||||
{file = "google_cloud_logging-3.11.4.tar.gz", hash = "sha256:32305d989323f3c58603044e2ac5d9cf23e9465ede511bbe90b4309270d3195c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1165,22 +1154,19 @@ google-cloud-audit-log = ">=0.2.4,<1.0.0dev"
|
||||
google-cloud-core = ">=2.0.0,<3.0.0dev"
|
||||
grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev"
|
||||
opentelemetry-api = ">=1.9.0"
|
||||
proto-plus = [
|
||||
{version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""},
|
||||
{version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""},
|
||||
]
|
||||
proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}
|
||||
protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev"
|
||||
|
||||
[[package]]
|
||||
name = "google-cloud-storage"
|
||||
version = "2.19.0"
|
||||
version = "3.0.0"
|
||||
description = "Google Cloud Storage API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba"},
|
||||
{file = "google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2"},
|
||||
{file = "google_cloud_storage-3.0.0-py2.py3-none-any.whl", hash = "sha256:f85fd059650d2dbb0ac158a9a6b304b66143b35ed2419afec2905ca522eb2c6a"},
|
||||
{file = "google_cloud_storage-3.0.0.tar.gz", hash = "sha256:2accb3e828e584888beff1165e5f3ac61aa9088965eb0165794a82d8c7f95297"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1303,6 +1289,23 @@ files = [
|
||||
httpx = {version = ">=0.26,<0.29", extras = ["http2"]}
|
||||
pydantic = ">=1.10,<3"
|
||||
|
||||
[[package]]
|
||||
name = "gravitasml"
|
||||
version = "0.1.2"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = "<3.13,>=3.11"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "gravitasml-0.1.2-py3-none-any.whl", hash = "sha256:eafa5a65a6f952f0d23c54762f5fcb654b3f000ede389519a7dc3c3179801121"},
|
||||
{file = "gravitasml-0.1.2.tar.gz", hash = "sha256:bbb651a9d5be669d47cc5bd7fbf19d55ce277b3dd9966cc37d2ec0ae042aab25"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
black = ">=24.10.0,<25.0.0"
|
||||
pydantic = ">=2.9.2,<3.0.0"
|
||||
pytest = ">=8.2.1,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.1.1"
|
||||
@@ -1393,14 +1396,14 @@ test = ["objgraph", "psutil"]
|
||||
|
||||
[[package]]
|
||||
name = "groq"
|
||||
version = "0.15.0"
|
||||
version = "0.18.0"
|
||||
description = "The official Python library for the groq API"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "groq-0.15.0-py3-none-any.whl", hash = "sha256:c200558b67fee4b4f2bb89cc166337e3419a68c23280065770f8f8b0729c79ef"},
|
||||
{file = "groq-0.15.0.tar.gz", hash = "sha256:9ad08ba6156c67d0975595a8515b517f22ff63158e063c55192e161ed3648af1"},
|
||||
{file = "groq-0.18.0-py3-none-any.whl", hash = "sha256:81d5ac00057a45d8ce559d23ab5d3b3893011d1f12c35187ab35a9182d826ea6"},
|
||||
{file = "groq-0.18.0.tar.gz", hash = "sha256:8e2ccfea406d68b3525af4b7c0e321fcb3d2a73fc60bb70b4156e6cd88c72f03"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2312,16 +2315,13 @@ files = [
|
||||
{file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
groups = ["dev"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
@@ -2439,14 +2439,14 @@ pydantic = ">=2.9.0,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.60.2"
|
||||
version = "1.61.1"
|
||||
description = "The official Python library for the openai API"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "openai-1.60.2-py3-none-any.whl", hash = "sha256:993bd11b96900b9098179c728026f016b4982ded7ee30dfcf4555eab1171fff9"},
|
||||
{file = "openai-1.60.2.tar.gz", hash = "sha256:a8f843e10f2855713007f491d96afb2694b11b5e02cb97c7d01a0be60bc5bb51"},
|
||||
{file = "openai-1.61.1-py3-none-any.whl", hash = "sha256:72b0826240ce26026ac2cd17951691f046e5be82ad122d20a8e1b30ca18bd11e"},
|
||||
{file = "openai-1.61.1.tar.gz", hash = "sha256:ce1851507218209961f89f3520e06726c0aa7d0512386f0f977e3ac3e4f2472e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2525,12 +2525,29 @@ version = "0.12.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pika"
|
||||
version = "1.3.2"
|
||||
description = "Pika Python AMQP Client Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pika-1.3.2-py3-none-any.whl", hash = "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f"},
|
||||
{file = "pika-1.3.2.tar.gz", hash = "sha256:b2a327ddddf8570b4965b3576ac77091b850262d34ce8c1d8cb4e4146aa4145f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
gevent = ["gevent"]
|
||||
tornado = ["tornado"]
|
||||
twisted = ["twisted"]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.4.0"
|
||||
@@ -2689,7 +2706,7 @@ version = "4.3.6"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||
@@ -2731,7 +2748,6 @@ files = [
|
||||
[package.dependencies]
|
||||
pastel = ">=0.2.1,<0.3.0"
|
||||
pyyaml = ">=6.0.2,<7.0.0"
|
||||
tomli = {version = ">=1.2.2", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
poetry-plugin = ["poetry (>=1.0,<3.0)"]
|
||||
@@ -2772,7 +2788,6 @@ files = [
|
||||
deprecation = ">=2.1.0,<3.0.0"
|
||||
httpx = {version = ">=0.26,<0.29", extras = ["http2"]}
|
||||
pydantic = ">=1.9,<3.0"
|
||||
strenum = {version = ">=0.4.9,<0.5.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "posthog"
|
||||
@@ -2863,7 +2878,6 @@ jinja2 = ">=2.11.2"
|
||||
nodeenv = "*"
|
||||
pydantic = ">=1.10.0,<3"
|
||||
python-dotenv = ">=0.12.0"
|
||||
StrEnum = {version = "*", markers = "python_version < \"3.11\""}
|
||||
tomlkit = "*"
|
||||
typing-extensions = ">=4.5.0"
|
||||
|
||||
@@ -3440,11 +3454,9 @@ files = [
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=1.5,<2"
|
||||
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
@@ -3499,7 +3511,6 @@ files = [
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
||||
watchdog = ">=2.0.0"
|
||||
|
||||
[[package]]
|
||||
@@ -4246,14 +4257,14 @@ typing-extensions = {version = ">=4.5.0", markers = "python_version >= \"3.7\""}
|
||||
|
||||
[[package]]
|
||||
name = "supabase"
|
||||
version = "2.12.0"
|
||||
version = "2.13.0"
|
||||
description = "Supabase client for Python."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "supabase-2.12.0-py3-none-any.whl", hash = "sha256:f8896f3314179fdf27f8bb8357947493ec32b98dcdac7114208aaf21cd59ce35"},
|
||||
{file = "supabase-2.12.0.tar.gz", hash = "sha256:284612c3e94ff4ed2f18c985f5eba4d6e17b5e2bf16a9a24290bb83c9c217078"},
|
||||
{file = "supabase-2.13.0-py3-none-any.whl", hash = "sha256:6cfccc055be21dab311afc5e9d5b37f3a4966f8394703763fbc8f8e86f36eaa6"},
|
||||
{file = "supabase-2.13.0.tar.gz", hash = "sha256:452574d34bd978c8d11b5f02b0182b48e8854e511c969483c83875ec01495f11"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -4311,49 +4322,6 @@ files = [
|
||||
[package.dependencies]
|
||||
requests = ">=2.32.3,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.2.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main", "dev"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
|
||||
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
version = "0.13.2"
|
||||
@@ -4524,7 +4492,6 @@ h11 = ">=0.8"
|
||||
httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""}
|
||||
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
|
||||
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
|
||||
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
|
||||
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
|
||||
@@ -5049,5 +5016,5 @@ type = ["pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "0f7dda46ad3cce4486a3ca4162babff974bd270c100c7b82b1ff8dec563488ad"
|
||||
python-versions = ">=3.11,<3.13"
|
||||
content-hash = "cf3242137be7977795e01a61c058ac01202a1b6e0a03172a22894eb652ff2aab"
|
||||
|
||||
@@ -8,25 +8,25 @@ packages = [{ include = "backend", format = "sdist" }]
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10,<3.13"
|
||||
python = ">=3.11,<3.13"
|
||||
aio-pika = "^9.5.4"
|
||||
anthropic = "^0.45.2"
|
||||
apscheduler = "^3.11.0"
|
||||
autogpt-libs = { path = "../autogpt_libs", develop = true }
|
||||
click = "^8.1.7"
|
||||
discord-py = "^2.4.0"
|
||||
e2b-code-interpreter = "^1.0.1"
|
||||
fastapi = "^0.115.5"
|
||||
e2b-code-interpreter = "^1.0.5"
|
||||
fastapi = "^0.115.8"
|
||||
feedparser = "^6.0.11"
|
||||
flake8 = "^7.0.0"
|
||||
google-api-python-client = "^2.160.0"
|
||||
google-auth-oauthlib = "^1.2.1"
|
||||
groq = "^0.15.0"
|
||||
groq = "^0.18.0"
|
||||
jinja2 = "^3.1.4"
|
||||
jsonref = "^1.1.0"
|
||||
jsonschema = "^4.22.0"
|
||||
ollama = "^0.4.1"
|
||||
openai = "^1.60.2"
|
||||
openai = "^1.61.1"
|
||||
praw = "~7.8.1"
|
||||
prisma = "^0.15.0"
|
||||
psutil = "^6.1.0"
|
||||
@@ -40,7 +40,7 @@ redis = "^5.2.0"
|
||||
sentry-sdk = "2.20.0"
|
||||
strenum = "^0.4.9"
|
||||
stripe = "^11.5.0"
|
||||
supabase = "2.12.0"
|
||||
supabase = "2.13.0"
|
||||
tenacity = "^9.0.0"
|
||||
tweepy = "^4.14.0"
|
||||
uvicorn = { extras = ["standard"], version = "^0.34.0" }
|
||||
@@ -53,11 +53,13 @@ cryptography = "^43.0"
|
||||
python-multipart = "^0.0.20"
|
||||
sqlalchemy = "^2.0.36"
|
||||
psycopg2-binary = "^2.9.10"
|
||||
google-cloud-storage = "^2.18.2"
|
||||
google-cloud-storage = "^3.0.0"
|
||||
launchdarkly-server-sdk = "^9.8.0"
|
||||
mem0ai = "^0.1.48"
|
||||
todoist-api-python = "^2.1.7"
|
||||
moviepy = "^2.1.2"
|
||||
gravitasml = "0.1.2"
|
||||
pika = "^1.3.2"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
poethepoet = "^0.32.1"
|
||||
|
||||
@@ -40,7 +40,7 @@ async def execute_graph(
|
||||
graph_version=test_graph.version,
|
||||
node_input=input_data,
|
||||
)
|
||||
graph_exec_id = response["id"]
|
||||
graph_exec_id = response.graph_exec_id
|
||||
logger.info(f"Created execution with ID: {graph_exec_id}")
|
||||
|
||||
# Execution queue should be empty
|
||||
|
||||
@@ -34,29 +34,29 @@ def test_disconnect(
|
||||
connection_manager: ConnectionManager, mock_websocket: AsyncMock
|
||||
) -> None:
|
||||
connection_manager.active_connections.add(mock_websocket)
|
||||
connection_manager.subscriptions["test_graph"] = {mock_websocket}
|
||||
connection_manager.subscriptions["test_graph_1"] = {mock_websocket}
|
||||
|
||||
connection_manager.disconnect(mock_websocket)
|
||||
|
||||
assert mock_websocket not in connection_manager.active_connections
|
||||
assert mock_websocket not in connection_manager.subscriptions["test_graph"]
|
||||
assert mock_websocket not in connection_manager.subscriptions["test_graph_1"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subscribe(
|
||||
connection_manager: ConnectionManager, mock_websocket: AsyncMock
|
||||
) -> None:
|
||||
await connection_manager.subscribe("test_graph", mock_websocket)
|
||||
assert mock_websocket in connection_manager.subscriptions["test_graph"]
|
||||
await connection_manager.subscribe("test_graph", 1, mock_websocket)
|
||||
assert mock_websocket in connection_manager.subscriptions["test_graph_1"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unsubscribe(
|
||||
connection_manager: ConnectionManager, mock_websocket: AsyncMock
|
||||
) -> None:
|
||||
connection_manager.subscriptions["test_graph"] = {mock_websocket}
|
||||
connection_manager.subscriptions["test_graph_1"] = {mock_websocket}
|
||||
|
||||
await connection_manager.unsubscribe("test_graph", mock_websocket)
|
||||
await connection_manager.unsubscribe("test_graph", 1, mock_websocket)
|
||||
|
||||
assert "test_graph" not in connection_manager.subscriptions
|
||||
|
||||
@@ -65,7 +65,7 @@ async def test_unsubscribe(
|
||||
async def test_send_execution_result(
|
||||
connection_manager: ConnectionManager, mock_websocket: AsyncMock
|
||||
) -> None:
|
||||
connection_manager.subscriptions["test_graph"] = {mock_websocket}
|
||||
connection_manager.subscriptions["test_graph_1"] = {mock_websocket}
|
||||
result: ExecutionResult = ExecutionResult(
|
||||
graph_id="test_graph",
|
||||
graph_version=1,
|
||||
@@ -87,7 +87,7 @@ async def test_send_execution_result(
|
||||
mock_websocket.send_text.assert_called_once_with(
|
||||
WsMessage(
|
||||
method=Methods.EXECUTION_EVENT,
|
||||
channel="test_graph",
|
||||
channel="test_graph_1",
|
||||
data=result.model_dump(),
|
||||
).model_dump_json()
|
||||
)
|
||||
|
||||
@@ -30,7 +30,8 @@ async def test_websocket_router_subscribe(
|
||||
) -> None:
|
||||
mock_websocket.receive_text.side_effect = [
|
||||
WsMessage(
|
||||
method=Methods.SUBSCRIBE, data={"graph_id": "test_graph"}
|
||||
method=Methods.SUBSCRIBE,
|
||||
data={"graph_id": "test_graph", "graph_version": 1},
|
||||
).model_dump_json(),
|
||||
WebSocketDisconnect(),
|
||||
]
|
||||
@@ -40,7 +41,7 @@ async def test_websocket_router_subscribe(
|
||||
)
|
||||
|
||||
mock_manager.connect.assert_called_once_with(mock_websocket)
|
||||
mock_manager.subscribe.assert_called_once_with("test_graph", mock_websocket)
|
||||
mock_manager.subscribe.assert_called_once_with("test_graph", 1, mock_websocket)
|
||||
mock_websocket.send_text.assert_called_once()
|
||||
assert '"method":"subscribe"' in mock_websocket.send_text.call_args[0][0]
|
||||
assert '"success":true' in mock_websocket.send_text.call_args[0][0]
|
||||
@@ -53,7 +54,8 @@ async def test_websocket_router_unsubscribe(
|
||||
) -> None:
|
||||
mock_websocket.receive_text.side_effect = [
|
||||
WsMessage(
|
||||
method=Methods.UNSUBSCRIBE, data={"graph_id": "test_graph"}
|
||||
method=Methods.UNSUBSCRIBE,
|
||||
data={"graph_id": "test_graph", "graph_version": 1},
|
||||
).model_dump_json(),
|
||||
WebSocketDisconnect(),
|
||||
]
|
||||
@@ -63,7 +65,7 @@ async def test_websocket_router_unsubscribe(
|
||||
)
|
||||
|
||||
mock_manager.connect.assert_called_once_with(mock_websocket)
|
||||
mock_manager.unsubscribe.assert_called_once_with("test_graph", mock_websocket)
|
||||
mock_manager.unsubscribe.assert_called_once_with("test_graph", 1, mock_websocket)
|
||||
mock_websocket.send_text.assert_called_once()
|
||||
assert '"method":"unsubscribe"' in mock_websocket.send_text.call_args[0][0]
|
||||
assert '"success":true' in mock_websocket.send_text.call_args[0][0]
|
||||
@@ -94,13 +96,15 @@ async def test_websocket_router_invalid_method(
|
||||
async def test_handle_subscribe_success(
|
||||
mock_websocket: AsyncMock, mock_manager: AsyncMock
|
||||
) -> None:
|
||||
message = WsMessage(method=Methods.SUBSCRIBE, data={"graph_id": "test_graph"})
|
||||
message = WsMessage(
|
||||
method=Methods.SUBSCRIBE, data={"graph_id": "test_graph", "graph_version": 1}
|
||||
)
|
||||
|
||||
await handle_subscribe(
|
||||
cast(WebSocket, mock_websocket), cast(ConnectionManager, mock_manager), message
|
||||
)
|
||||
|
||||
mock_manager.subscribe.assert_called_once_with("test_graph", mock_websocket)
|
||||
mock_manager.subscribe.assert_called_once_with("test_graph", 1, mock_websocket)
|
||||
mock_websocket.send_text.assert_called_once()
|
||||
assert '"method":"subscribe"' in mock_websocket.send_text.call_args[0][0]
|
||||
assert '"success":true' in mock_websocket.send_text.call_args[0][0]
|
||||
@@ -126,13 +130,15 @@ async def test_handle_subscribe_missing_data(
|
||||
async def test_handle_unsubscribe_success(
|
||||
mock_websocket: AsyncMock, mock_manager: AsyncMock
|
||||
) -> None:
|
||||
message = WsMessage(method=Methods.UNSUBSCRIBE, data={"graph_id": "test_graph"})
|
||||
message = WsMessage(
|
||||
method=Methods.UNSUBSCRIBE, data={"graph_id": "test_graph", "graph_version": 1}
|
||||
)
|
||||
|
||||
await handle_unsubscribe(
|
||||
cast(WebSocket, mock_websocket), cast(ConnectionManager, mock_manager), message
|
||||
)
|
||||
|
||||
mock_manager.unsubscribe.assert_called_once_with("test_graph", mock_websocket)
|
||||
mock_manager.unsubscribe.assert_called_once_with("test_graph", 1, mock_websocket)
|
||||
mock_websocket.send_text.assert_called_once()
|
||||
assert '"method":"unsubscribe"' in mock_websocket.send_text.call_args[0][0]
|
||||
assert '"success":true' in mock_websocket.send_text.call_args[0][0]
|
||||
|
||||
@@ -36,6 +36,21 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
rabbitmq:
|
||||
image: rabbitmq:management
|
||||
container_name: rabbitmq
|
||||
healthcheck:
|
||||
test: rabbitmq-diagnostics -q ping
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
environment:
|
||||
- RABBITMQ_DEFAULT_USER=rabbitmq_user_default
|
||||
- RABBITMQ_DEFAULT_PASS=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7 # CHANGE THIS TO A RANDOM PASSWORD IN PRODUCTION -- everywhere lol
|
||||
ports:
|
||||
- "5672:5672"
|
||||
- "15672:15672"
|
||||
|
||||
rest_server:
|
||||
build:
|
||||
@@ -55,6 +70,8 @@ services:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- SUPABASE_URL=http://kong:8000
|
||||
- SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
|
||||
@@ -62,6 +79,10 @@ services:
|
||||
- DATABASE_URL=postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=platform
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- RABBITMQ_HOST=rabbitmq
|
||||
- RABBITMQ_PORT=5672
|
||||
- RABBITMQ_USER=rabbitmq_user_default
|
||||
- RABBITMQ_PASSWORD=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
|
||||
- REDIS_PASSWORD=password
|
||||
- ENABLE_AUTH=true
|
||||
- PYRO_HOST=0.0.0.0
|
||||
@@ -69,7 +90,7 @@ services:
|
||||
- EXECUTIONMANAGER_HOST=executor
|
||||
- FRONTEND_BASE_URL=http://localhost:3000
|
||||
- BACKEND_CORS_ALLOW_ORIGINS=["http://localhost:3000"]
|
||||
- ENCRYPTION_KEY=dvziYgz0KSK8FENhju0ZYi8-fRTfAdlz6YLhdB_jhNw= # DO NOT USE IN PRODUCTION!!
|
||||
- ENCRYPTION_KEY=dvziYgz0KSK8FENhju0ZYi8-fRTfAdlz6YLhdB_jhNw= # DO NOT USE IN PRODUCTION!!
|
||||
ports:
|
||||
- "8006:8006"
|
||||
- "8003:8003" # execution scheduler
|
||||
@@ -90,6 +111,8 @@ services:
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
@@ -102,10 +125,14 @@ services:
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=password
|
||||
- RABBITMQ_HOST=rabbitmq
|
||||
- RABBITMQ_PORT=5672
|
||||
- RABBITMQ_USER=rabbitmq_user_default
|
||||
- RABBITMQ_PASSWORD=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
|
||||
- ENABLE_AUTH=true
|
||||
- PYRO_HOST=0.0.0.0
|
||||
- AGENTSERVER_HOST=rest_server
|
||||
- ENCRYPTION_KEY=dvziYgz0KSK8FENhju0ZYi8-fRTfAdlz6YLhdB_jhNw= # DO NOT USE IN PRODUCTION!!
|
||||
- ENCRYPTION_KEY=dvziYgz0KSK8FENhju0ZYi8-fRTfAdlz6YLhdB_jhNw= # DO NOT USE IN PRODUCTION!!
|
||||
ports:
|
||||
- "8002:8000"
|
||||
networks:
|
||||
@@ -127,14 +154,20 @@ services:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
# rabbitmq:
|
||||
# condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
- SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
|
||||
- DATABASE_URL=postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=platform
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=password
|
||||
# - RABBITMQ_HOST=rabbitmq # TODO: Uncomment this when we have a need for it in websocket (like nofifying when stuff went down)
|
||||
# - RABBITMQ_PORT=5672
|
||||
# - RABBITMQ_USER=rabbitmq_user_default
|
||||
# - RABBITMQ_PASSWORD=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
|
||||
- ENABLE_AUTH=true
|
||||
- PYRO_HOST=0.0.0.0
|
||||
- BACKEND_CORS_ALLOW_ORIGINS=["http://localhost:3000"]
|
||||
|
||||
@@ -33,6 +33,12 @@ services:
|
||||
file: ./docker-compose.platform.yml
|
||||
service: redis
|
||||
|
||||
rabbitmq:
|
||||
<<: *agpt-services
|
||||
extends:
|
||||
file: ./docker-compose.platform.yml
|
||||
service: rabbitmq
|
||||
|
||||
rest_server:
|
||||
<<: *agpt-services
|
||||
extends:
|
||||
@@ -146,3 +152,4 @@ services:
|
||||
- db
|
||||
- vector
|
||||
- redis
|
||||
- rabbitmq
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"@radix-ui/react-toast": "^1.2.5",
|
||||
"@radix-ui/react-tooltip": "^1.1.7",
|
||||
"@sentry/nextjs": "^8",
|
||||
"@stripe/stripe-js": "^5.5.0",
|
||||
"@stripe/stripe-js": "^5.6.0",
|
||||
"@supabase/ssr": "^0.5.2",
|
||||
"@supabase/supabase-js": "^2.48.1",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
@@ -60,9 +60,9 @@
|
||||
"dotenv": "^16.4.7",
|
||||
"elliptic": "6.6.1",
|
||||
"embla-carousel-react": "^8.5.2",
|
||||
"framer-motion": "^11.16.0",
|
||||
"framer-motion": "^12.0.11",
|
||||
"geist": "^1.3.1",
|
||||
"launchdarkly-react-client-sdk": "^3.6.0",
|
||||
"launchdarkly-react-client-sdk": "^3.6.1",
|
||||
"lucide-react": "^0.474.0",
|
||||
"moment": "^2.30.1",
|
||||
"next": "^14.2.21",
|
||||
@@ -75,7 +75,7 @@
|
||||
"react-markdown": "^9.0.3",
|
||||
"react-modal": "^3.16.3",
|
||||
"react-shepherd": "^6.1.7",
|
||||
"recharts": "^2.14.1",
|
||||
"recharts": "^2.15.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^11.0.5",
|
||||
@@ -83,24 +83,24 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^3.2.4",
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@storybook/addon-a11y": "^8.5.2",
|
||||
"@storybook/addon-essentials": "^8.5.2",
|
||||
"@storybook/addon-interactions": "^8.5.2",
|
||||
"@storybook/addon-links": "^8.5.2",
|
||||
"@storybook/addon-onboarding": "^8.5.2",
|
||||
"@storybook/blocks": "^8.5.2",
|
||||
"@storybook/nextjs": "^8.5.2",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@storybook/addon-a11y": "^8.5.3",
|
||||
"@storybook/addon-essentials": "^8.5.3",
|
||||
"@storybook/addon-interactions": "^8.5.3",
|
||||
"@storybook/addon-links": "^8.5.3",
|
||||
"@storybook/addon-onboarding": "^8.5.3",
|
||||
"@storybook/blocks": "^8.5.3",
|
||||
"@storybook/nextjs": "^8.5.3",
|
||||
"@storybook/react": "^8.3.5",
|
||||
"@storybook/test": "^8.3.5",
|
||||
"@storybook/test-runner": "^0.21.0",
|
||||
"@types/negotiator": "^0.6.3",
|
||||
"@types/node": "^22.10.10",
|
||||
"@types/node": "^22.13.0",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"axe-playwright": "^2.0.3",
|
||||
"chromatic": "^11.25.1",
|
||||
"chromatic": "^11.25.2",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.1.6",
|
||||
@@ -110,7 +110,7 @@
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"storybook": "^8.5.2",
|
||||
"storybook": "^8.5.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ export default function Home() {
|
||||
<FlowEditor
|
||||
className="flow-container"
|
||||
flowID={query.get("flowID") ?? undefined}
|
||||
flowVersion={query.get("flowVersion") ?? undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export default async function RootLayout({
|
||||
{
|
||||
icon: IconType.Edit,
|
||||
text: "Edit profile",
|
||||
href: "/marketplace/profile",
|
||||
href: "/profile",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -75,7 +75,7 @@ export default async function RootLayout({
|
||||
{
|
||||
icon: IconType.LayoutDashboard,
|
||||
text: "Creator Dashboard",
|
||||
href: "/marketplace/dashboard",
|
||||
href: "/profile/dashboard",
|
||||
},
|
||||
{
|
||||
icon: IconType.UploadCloud,
|
||||
@@ -88,7 +88,7 @@ export default async function RootLayout({
|
||||
{
|
||||
icon: IconType.Settings,
|
||||
text: "Settings",
|
||||
href: "/marketplace/settings",
|
||||
href: "/profile/settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { HeroSection } from "@/components/agptui/composite/HeroSection";
|
||||
import {
|
||||
FeaturedSection,
|
||||
FeaturedAgent,
|
||||
} from "@/components/agptui/composite/FeaturedSection";
|
||||
import { FeaturedSection } from "@/components/agptui/composite/FeaturedSection";
|
||||
import {
|
||||
AgentsSection,
|
||||
Agent,
|
||||
@@ -155,9 +152,7 @@ export default async function Page({}: {}) {
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="px-4">
|
||||
<HeroSection />
|
||||
<FeaturedSection
|
||||
featuredAgents={featuredAgents.agents as FeaturedAgent[]}
|
||||
/>
|
||||
<FeaturedSection featuredAgents={featuredAgents.agents} />
|
||||
<Separator />
|
||||
<AgentsSection
|
||||
sectionTitle="Top Agents"
|
||||
|
||||
@@ -37,8 +37,8 @@ const Monitor = () => {
|
||||
);
|
||||
|
||||
const fetchAgents = useCallback(() => {
|
||||
api.listLibraryAgents().then((agent) => {
|
||||
setFlows(agent);
|
||||
api.listLibraryAgents().then((agents) => {
|
||||
setFlows(agents);
|
||||
});
|
||||
api.getExecutions().then((executions) => {
|
||||
setExecutions(executions);
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function CreditsPage() {
|
||||
updateAutoTopUpConfig,
|
||||
transactionHistory,
|
||||
fetchTransactionHistory,
|
||||
formatCredits,
|
||||
} = useCredits();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -59,7 +60,8 @@ export default function CreditsPage() {
|
||||
const submitTopUp = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const form = e.currentTarget;
|
||||
const amount = parseInt(new FormData(form).get("topUpAmount") as string);
|
||||
const amount =
|
||||
parseInt(new FormData(form).get("topUpAmount") as string) * 100;
|
||||
toastOnFail("request top-up", () => requestTopUp(amount));
|
||||
};
|
||||
|
||||
@@ -67,8 +69,8 @@ export default function CreditsPage() {
|
||||
e.preventDefault();
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
const amount = parseInt(formData.get("topUpAmount") as string);
|
||||
const threshold = parseInt(formData.get("threshold") as string);
|
||||
const amount = parseInt(formData.get("topUpAmount") as string) * 100;
|
||||
const threshold = parseInt(formData.get("threshold") as string) * 100;
|
||||
toastOnFail("update auto top-up config", () =>
|
||||
updateAutoTopUpConfig(amount, threshold).then(() => {
|
||||
toast({ title: "Auto top-up config updated! 🎉" });
|
||||
@@ -79,7 +81,7 @@ export default function CreditsPage() {
|
||||
return (
|
||||
<div className="w-full min-w-[800px] px-4 sm:px-8">
|
||||
<h1 className="mb-6 text-[28px] font-normal text-neutral-900 dark:text-neutral-100 sm:mb-8 sm:text-[35px]">
|
||||
Credits
|
||||
Billing
|
||||
</h1>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
@@ -108,16 +110,16 @@ export default function CreditsPage() {
|
||||
htmlFor="topUpAmount"
|
||||
className="mb-1 block text-neutral-700"
|
||||
>
|
||||
Top-up Amount (Credits)
|
||||
Top-up amount (USD), minimum $5:
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="topUpAmount"
|
||||
name="topUpAmount"
|
||||
placeholder="Enter top-up amount"
|
||||
min="500"
|
||||
step="100"
|
||||
defaultValue={500}
|
||||
min="5"
|
||||
step="1"
|
||||
defaultValue={5}
|
||||
className="w-full rounded-md border border-slate-200 px-4 py-2 dark:border-slate-700 dark:bg-slate-800"
|
||||
required
|
||||
/>
|
||||
@@ -130,49 +132,83 @@ export default function CreditsPage() {
|
||||
|
||||
{/* Auto Top-up Form */}
|
||||
<form onSubmit={submitAutoTopUpConfig} className="mt-6 space-y-4">
|
||||
<h3 className="text-lg font-medium">Auto Top-up Configuration</h3>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="autoTopUpAmount"
|
||||
className="mb-1 block text-neutral-700"
|
||||
>
|
||||
Auto Top-up Amount (Credits)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="autoTopUpAmount"
|
||||
name="topUpAmount"
|
||||
defaultValue={autoTopUpConfig?.amount || ""}
|
||||
placeholder="Enter auto top-up amount"
|
||||
step="100"
|
||||
className="w-full rounded-md border border-slate-200 px-4 py-2 dark:border-slate-700 dark:bg-slate-800"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium">Automatic Refill Settings</h3>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="threshold"
|
||||
className="mb-1 block text-neutral-700"
|
||||
>
|
||||
Threshold (Credits)
|
||||
When my balance goes below this amount:
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="threshold"
|
||||
name="threshold"
|
||||
defaultValue={autoTopUpConfig?.threshold || ""}
|
||||
placeholder="Enter threshold value"
|
||||
step="100"
|
||||
defaultValue={
|
||||
autoTopUpConfig?.threshold
|
||||
? autoTopUpConfig.threshold / 100
|
||||
: ""
|
||||
}
|
||||
placeholder="Refill threshold, minimum $5"
|
||||
min="5"
|
||||
step="1"
|
||||
className="w-full rounded-md border border-slate-200 px-4 py-2 dark:border-slate-700 dark:bg-slate-800"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Save
|
||||
</Button>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="autoTopUpAmount"
|
||||
className="mb-1 block text-neutral-700"
|
||||
>
|
||||
Automatically refill my balance with this amount:
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="autoTopUpAmount"
|
||||
name="topUpAmount"
|
||||
defaultValue={
|
||||
autoTopUpConfig?.amount ? autoTopUpConfig.amount / 100 : ""
|
||||
}
|
||||
placeholder="Refill amount, minimum $5"
|
||||
min="5"
|
||||
step="1"
|
||||
className="w-full rounded-md border border-slate-200 px-4 py-2 dark:border-slate-700 dark:bg-slate-800"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-sm">
|
||||
<b>Note:</b> For your safety, we will top up your balance{" "}
|
||||
<b>at most once</b> per agent execution to prevent unintended
|
||||
excessive charges. Therefore, ensure that the automatic top-up
|
||||
amount is sufficient for your agent's operation.
|
||||
</p>
|
||||
|
||||
{autoTopUpConfig?.amount ? (
|
||||
<>
|
||||
<Button type="submit" className="w-full">
|
||||
Save Changes
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
updateAutoTopUpConfig(0, 0).then(() => {
|
||||
toast({ title: "Auto top-up config disabled! 🎉" });
|
||||
})
|
||||
}
|
||||
>
|
||||
Disable Auto-Refill
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button type="submit" className="w-full">
|
||||
Enable Auto-Refill
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -232,9 +268,9 @@ export default function CreditsPage() {
|
||||
transaction.amount > 0 ? "text-green-500" : "text-red-500"
|
||||
}
|
||||
>
|
||||
<b>{transaction.amount}</b>
|
||||
<b>{formatCredits(transaction.amount)}</b>
|
||||
</TableCell>
|
||||
<TableCell>{transaction.balance}</TableCell>
|
||||
<TableCell>{formatCredits(transaction.balance)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import * as React from "react";
|
||||
import { AgentTable } from "@/components/agptui/AgentTable";
|
||||
import { AgentTableRowProps } from "@/components/agptui/AgentTableRow";
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { StatusType } from "@/components/agptui/Status";
|
||||
@@ -2,10 +2,9 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useContext, useMemo, useState } from "react";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { IconKey, IconUser } from "@/components/ui/icons";
|
||||
import { LogOutIcon, Trash2Icon } from "lucide-react";
|
||||
import { Trash2Icon } from "lucide-react";
|
||||
import { providerIcons } from "@/components/integrations/credentials-input";
|
||||
import { CredentialsProvidersContext } from "@/components/integrations/credentials-provider";
|
||||
import {
|
||||
@@ -5,13 +5,13 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const sidebarLinkGroups = [
|
||||
{
|
||||
links: [
|
||||
{ text: "Creator Dashboard", href: "/marketplace/dashboard" },
|
||||
{ text: "Agent dashboard", href: "/marketplace/agent-dashboard" },
|
||||
{ text: "Credits", href: "/marketplace/credits" },
|
||||
{ text: "Integrations", href: "/marketplace/integrations" },
|
||||
{ text: "API Keys", href: "/marketplace/api_keys" },
|
||||
{ text: "Profile", href: "/marketplace/profile" },
|
||||
{ text: "Settings", href: "/marketplace/settings" },
|
||||
{ text: "Creator Dashboard", href: "/profile/dashboard" },
|
||||
{ text: "Agent dashboard", href: "/profile/agent-dashboard" },
|
||||
{ text: "Billing", href: "/profile/credits" },
|
||||
{ text: "Integrations", href: "/profile/integrations" },
|
||||
{ text: "API Keys", href: "/profile/api_keys" },
|
||||
{ text: "Profile", href: "/profile" },
|
||||
{ text: "Settings", href: "/profile/settings" },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -1,242 +0,0 @@
|
||||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useContext, useMemo, useState } from "react";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { IconKey, IconUser } from "@/components/ui/icons";
|
||||
import { LogOutIcon, Trash2Icon } from "lucide-react";
|
||||
import { providerIcons } from "@/components/integrations/credentials-input";
|
||||
import { CredentialsProvidersContext } from "@/components/integrations/credentials-provider";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { CredentialsProviderName } from "@/lib/autogpt-server-api";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import useSupabase from "@/hooks/useSupabase";
|
||||
import Spinner from "@/components/Spinner";
|
||||
|
||||
export default function PrivatePage() {
|
||||
const { supabase, user, isUserLoading } = useSupabase();
|
||||
const router = useRouter();
|
||||
const providers = useContext(CredentialsProvidersContext);
|
||||
const { toast } = useToast();
|
||||
|
||||
const [confirmationDialogState, setConfirmationDialogState] = useState<
|
||||
| {
|
||||
open: true;
|
||||
message: string;
|
||||
onConfirm: () => void;
|
||||
onReject: () => void;
|
||||
}
|
||||
| { open: false }
|
||||
>({ open: false });
|
||||
|
||||
const removeCredentials = useCallback(
|
||||
async (
|
||||
provider: CredentialsProviderName,
|
||||
id: string,
|
||||
force: boolean = false,
|
||||
) => {
|
||||
if (!providers || !providers[provider]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await providers[provider].deleteCredentials(id, force);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: "Something went wrong when deleting credentials: " + error,
|
||||
variant: "destructive",
|
||||
duration: 2000,
|
||||
});
|
||||
setConfirmationDialogState({ open: false });
|
||||
return;
|
||||
}
|
||||
if (result.deleted) {
|
||||
if (result.revoked) {
|
||||
toast({
|
||||
title: "Credentials deleted",
|
||||
duration: 2000,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Credentials deleted from AutoGPT",
|
||||
description: `You may also manually remove the connection to AutoGPT at ${provider}!`,
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
setConfirmationDialogState({ open: false });
|
||||
} else if (result.need_confirmation) {
|
||||
setConfirmationDialogState({
|
||||
open: true,
|
||||
message: result.message,
|
||||
onConfirm: () => removeCredentials(provider, id, true),
|
||||
onReject: () => setConfirmationDialogState({ open: false }),
|
||||
});
|
||||
}
|
||||
},
|
||||
[providers, toast],
|
||||
);
|
||||
|
||||
//TODO: remove when the way system credentials are handled is updated
|
||||
// This contains ids for built-in "Use Credits for X" credentials
|
||||
const hiddenCredentials = useMemo(
|
||||
() => [
|
||||
"744fdc56-071a-4761-b5a5-0af0ce10a2b5", // Ollama
|
||||
"fdb7f412-f519-48d1-9b5f-d2f73d0e01fe", // Revid
|
||||
"760f84fc-b270-42de-91f6-08efe1b512d0", // Ideogram
|
||||
"6b9fc200-4726-4973-86c9-cd526f5ce5db", // Replicate
|
||||
"53c25cb8-e3ee-465c-a4d1-e75a4c899c2a", // OpenAI
|
||||
"24e5d942-d9e3-4798-8151-90143ee55629", // Anthropic
|
||||
"4ec22295-8f97-4dd1-b42b-2c6957a02545", // Groq
|
||||
"7f7b0654-c36b-4565-8fa7-9a52575dfae2", // D-ID
|
||||
"7f26de70-ba0d-494e-ba76-238e65e7b45f", // Jina
|
||||
"66f20754-1b81-48e4-91d0-f4f0dd82145f", // Unreal Speech
|
||||
"b5a0e27d-0c98-4df3-a4b9-10193e1f3c40", // Open Router
|
||||
"6c0f5bd0-9008-4638-9d79-4b40b631803e", // FAL
|
||||
"96153e04-9c6c-4486-895f-5bb683b1ecec", // Exa
|
||||
"78d19fd7-4d59-4a16-8277-3ce310acf2b7", // E2B
|
||||
"96b83908-2789-4dec-9968-18f0ece4ceb3", // Nvidia
|
||||
"ed55ac19-356e-4243-a6cb-bc599e9b716f", // Mem0
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
if (isUserLoading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (!user || !supabase) {
|
||||
router.push("/login");
|
||||
return null;
|
||||
}
|
||||
|
||||
const allCredentials = providers
|
||||
? Object.values(providers).flatMap((provider) =>
|
||||
[
|
||||
...provider.savedOAuthCredentials,
|
||||
...provider.savedApiKeys,
|
||||
...provider.savedUserPasswordCredentials,
|
||||
]
|
||||
.filter((cred) => !hiddenCredentials.includes(cred.id))
|
||||
.map((credentials) => ({
|
||||
...credentials,
|
||||
provider: provider.provider,
|
||||
providerName: provider.providerName,
|
||||
ProviderIcon: providerIcons[provider.provider],
|
||||
TypeIcon: {
|
||||
oauth2: IconUser,
|
||||
api_key: IconKey,
|
||||
user_password: IconKey,
|
||||
}[credentials.type],
|
||||
})),
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl md:py-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<p>
|
||||
Hello <span data-testid="profile-email">{user.email}</span>
|
||||
</p>
|
||||
<Button onClick={() => supabase.auth.signOut()}>
|
||||
<LogOutIcon className="mr-1.5 size-4" />
|
||||
Log out
|
||||
</Button>
|
||||
</div>
|
||||
<Separator className="my-6" />
|
||||
<h2 className="mb-4 text-lg">Connections & Credentials</h2>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Provider</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{allCredentials.map((cred) => (
|
||||
<TableRow key={cred.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<cred.ProviderIcon className="h-4 w-4" />
|
||||
<strong>{cred.providerName}</strong>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex h-full items-center space-x-1.5">
|
||||
<cred.TypeIcon />
|
||||
<span>{cred.title || cred.username}</span>
|
||||
</div>
|
||||
<small className="text-muted-foreground">
|
||||
{
|
||||
{
|
||||
oauth2: "OAuth2 credentials",
|
||||
api_key: "API key",
|
||||
user_password: "User password",
|
||||
}[cred.type]
|
||||
}{" "}
|
||||
- <code>{cred.id}</code>
|
||||
</small>
|
||||
</TableCell>
|
||||
<TableCell className="w-0 whitespace-nowrap">
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => removeCredentials(cred.provider, cred.id)}
|
||||
>
|
||||
<Trash2Icon className="mr-1.5 size-4" /> Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<AlertDialog open={confirmationDialogState.open}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{confirmationDialogState.open && confirmationDialogState.message}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel
|
||||
onClick={() =>
|
||||
confirmationDialogState.open &&
|
||||
confirmationDialogState.onReject()
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
confirmationDialogState.open &&
|
||||
confirmationDialogState.onConfirm()
|
||||
}
|
||||
>
|
||||
Continue
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -36,9 +36,8 @@ export async function signup(values: z.infer<typeof signupFormSchema>) {
|
||||
if (data.session) {
|
||||
await supabase.auth.setSession(data.session);
|
||||
}
|
||||
console.log("Signed up");
|
||||
revalidatePath("/", "layout");
|
||||
redirect("/marketplace/profile");
|
||||
redirect("/");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ export function CustomEdge({
|
||||
targetY,
|
||||
markerEnd,
|
||||
}: EdgeProps<CustomEdge>) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [beads, setBeads] = useState<{
|
||||
beads: Bead[];
|
||||
created: number;
|
||||
@@ -182,13 +181,7 @@ export function CustomEdge({
|
||||
<BaseEdge
|
||||
path={svgPath}
|
||||
markerEnd={markerEnd}
|
||||
style={{
|
||||
strokeWidth: (isHovered ? 3 : 2) + (data?.isStatic ? 0.5 : 0),
|
||||
stroke:
|
||||
(data?.edgeColor ?? "#555555") +
|
||||
(selected || isHovered ? "" : "80"),
|
||||
strokeDasharray: data?.isStatic ? "5 3" : "0",
|
||||
}}
|
||||
className={`transition-all duration-200 ${data?.isStatic ? "[stroke-dasharray:5_3]" : "[stroke-dasharray:0]"} [stroke-width:${data?.isStatic ? 2.5 : 2}px] hover:[stroke-width:${data?.isStatic ? 3.5 : 3}px] ${selected ? `[stroke:${data?.edgeColor ?? "#555555"}]` : `[stroke:${data?.edgeColor ?? "#555555"}80] hover:[stroke:${data?.edgeColor ?? "#555555"}]`}`}
|
||||
/>
|
||||
<path
|
||||
d={svgPath}
|
||||
@@ -196,8 +189,6 @@ export function CustomEdge({
|
||||
strokeOpacity={0}
|
||||
strokeWidth={20}
|
||||
className="react-flow__edge-interaction"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
/>
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
@@ -209,9 +200,7 @@ export function CustomEdge({
|
||||
className="edge-label-renderer"
|
||||
>
|
||||
<button
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className={`edge-label-button ${isHovered ? "visible" : ""}`}
|
||||
className="edge-label-button opacity-0 transition-opacity duration-200 hover:opacity-100"
|
||||
onClick={onEdgeRemoveClick}
|
||||
>
|
||||
<X className="size-4" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ import {
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { CustomNode } from "./CustomNode";
|
||||
import "./flow.css";
|
||||
import { BlockUIType, Link } from "@/lib/autogpt-server-api";
|
||||
import { BlockUIType, formatEdgeID } from "@/lib/autogpt-server-api";
|
||||
import { getTypeColor, findNewlyAddedBlockCoordinates } from "@/lib/utils";
|
||||
import { history } from "./history";
|
||||
import { CustomEdge } from "./CustomEdge";
|
||||
@@ -70,8 +70,9 @@ export const FlowContext = createContext<FlowContextType | null>(null);
|
||||
|
||||
const FlowEditor: React.FC<{
|
||||
flowID?: string;
|
||||
flowVersion?: string;
|
||||
className?: string;
|
||||
}> = ({ flowID, className }) => {
|
||||
}> = ({ flowID, flowVersion, className }) => {
|
||||
const {
|
||||
addNodes,
|
||||
addEdges,
|
||||
@@ -85,6 +86,7 @@ const FlowEditor: React.FC<{
|
||||
const [visualizeBeads, setVisualizeBeads] = useState<
|
||||
"no" | "static" | "animate"
|
||||
>("animate");
|
||||
const [flowExecutionID, setFlowExecutionID] = useState<string | undefined>();
|
||||
const {
|
||||
agentName,
|
||||
setAgentName,
|
||||
@@ -107,7 +109,12 @@ const FlowEditor: React.FC<{
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = useAgentGraph(flowID, visualizeBeads !== "no");
|
||||
} = useAgentGraph(
|
||||
flowID,
|
||||
flowVersion ? parseInt(flowVersion) : undefined,
|
||||
flowExecutionID,
|
||||
visualizeBeads !== "no",
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
@@ -157,6 +164,7 @@ const FlowEditor: React.FC<{
|
||||
if (params.get("open_scheduling") === "true") {
|
||||
setOpenCron(true);
|
||||
}
|
||||
setFlowExecutionID(params.get("flowExecutionID") || undefined);
|
||||
}, [params]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -267,14 +275,6 @@ const FlowEditor: React.FC<{
|
||||
[deleteElements, setNodes, nodes, edges, addNodes],
|
||||
);
|
||||
|
||||
const formatEdgeID = useCallback((conn: Link | Connection): string => {
|
||||
if ("sink_id" in conn) {
|
||||
return `${conn.source_id}_${conn.source_name}_${conn.sink_id}_${conn.sink_name}`;
|
||||
} else {
|
||||
return `${conn.source}_${conn.sourceHandle}_${conn.target}_${conn.targetHandle}`;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onConnect: OnConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
// Check if this exact connection already exists
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import React from "react";
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||
import Image from "next/image";
|
||||
import getServerUser from "@/lib/supabase/getServerUser";
|
||||
import ProfileDropdown from "./ProfileDropdown";
|
||||
import { IconCircleUser, IconMenu } from "@/components/ui/icons";
|
||||
import CreditButton from "@/components/nav/CreditButton";
|
||||
import { NavBarButtons } from "./nav/NavBarButtons";
|
||||
|
||||
export async function NavBar() {
|
||||
const isAvailable = Boolean(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL &&
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
);
|
||||
const { user } = await getServerUser();
|
||||
|
||||
return user ? (
|
||||
<header className="sticky top-0 z-50 mx-4 flex h-16 select-none items-center gap-4 border border-gray-300 bg-background p-3 md:rounded-b-2xl md:px-6 md:shadow">
|
||||
<div className="flex flex-1 items-center gap-4">
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="shrink-0 md:hidden"
|
||||
>
|
||||
<IconMenu />
|
||||
<span className="sr-only">Toggle navigation menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left">
|
||||
<nav className="grid gap-6 text-lg font-medium">
|
||||
<NavBarButtons className="flex flex-row items-center gap-2" />
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<nav className="hidden md:flex md:flex-row md:items-center md:gap-5 lg:gap-8">
|
||||
<div className="flex h-10 w-20 flex-1 flex-row items-center justify-center gap-2">
|
||||
<a href="https://agpt.co/">
|
||||
<Image
|
||||
src="/AUTOgpt_Logo_dark.png"
|
||||
alt="AutoGPT Logo"
|
||||
width={100}
|
||||
height={40}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<NavBarButtons className="flex flex-row items-center gap-1 border border-white font-semibold hover:border-gray-900" />
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-end gap-4">
|
||||
{isAvailable && user && <CreditButton />}
|
||||
|
||||
{isAvailable && !user && (
|
||||
<Link
|
||||
href="/login"
|
||||
className="flex flex-row items-center gap-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Log In
|
||||
<IconCircleUser />
|
||||
</Link>
|
||||
)}
|
||||
{isAvailable && user && <ProfileDropdown />}
|
||||
</div>
|
||||
</header>
|
||||
) : (
|
||||
<nav className="flex w-full items-center p-2 pt-8">
|
||||
<div className="flex h-10 w-20 flex-1 flex-row items-center justify-center gap-2">
|
||||
<a href="https://agpt.co/">
|
||||
<Image
|
||||
src="/AUTOgpt_Logo_dark.png"
|
||||
alt="AutoGPT Logo"
|
||||
width={100}
|
||||
height={40}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BlockIOSubSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { beautifyString, getTypeBgColor, getTypeTextColor } from "@/lib/utils";
|
||||
import { FC } from "react";
|
||||
import { FC, memo, useCallback } from "react";
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
import SchemaTooltip from "./SchemaTooltip";
|
||||
|
||||
@@ -13,6 +13,32 @@ type HandleProps = {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
// Move the constant out of the component to avoid re-creation on every render.
|
||||
const TYPE_NAME: Record<string, string> = {
|
||||
string: "text",
|
||||
number: "number",
|
||||
integer: "integer",
|
||||
boolean: "true/false",
|
||||
object: "object",
|
||||
array: "list",
|
||||
null: "null",
|
||||
};
|
||||
|
||||
// Extract and memoize the Dot component so that it doesn't re-render unnecessarily.
|
||||
const Dot: FC<{ isConnected: boolean; type?: string }> = memo(
|
||||
({ isConnected, type }) => {
|
||||
const color = isConnected
|
||||
? getTypeBgColor(type || "any")
|
||||
: "border-gray-300 dark:border-gray-600";
|
||||
return (
|
||||
<div
|
||||
className={`${color} m-1 h-4 w-4 rounded-full border-2 bg-white transition-colors duration-100 group-hover:bg-gray-300 dark:bg-slate-800 dark:group-hover:bg-gray-700`}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Dot.displayName = "Dot";
|
||||
|
||||
const NodeHandle: FC<HandleProps> = ({
|
||||
keyName,
|
||||
schema,
|
||||
@@ -21,17 +47,9 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
side,
|
||||
title,
|
||||
}) => {
|
||||
const typeName: Record<string, string> = {
|
||||
string: "text",
|
||||
number: "number",
|
||||
integer: "integer",
|
||||
boolean: "true/false",
|
||||
object: "object",
|
||||
array: "list",
|
||||
null: "null",
|
||||
};
|
||||
|
||||
const typeClass = `text-sm ${getTypeTextColor(schema.type || "any")} ${side === "left" ? "text-left" : "text-right"}`;
|
||||
const typeClass = `text-sm ${getTypeTextColor(schema.type || "any")} ${
|
||||
side === "left" ? "text-left" : "text-right"
|
||||
}`;
|
||||
|
||||
const label = (
|
||||
<div className="flex flex-grow flex-row">
|
||||
@@ -40,25 +58,27 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
{isRequired ? "*" : ""}
|
||||
</span>
|
||||
<span className={`${typeClass} flex items-end`}>
|
||||
({typeName[schema.type as keyof typeof typeName] || "any"})
|
||||
({TYPE_NAME[schema.type as keyof typeof TYPE_NAME] || "any"})
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Dot = () => {
|
||||
const color = isConnected
|
||||
? getTypeBgColor(schema.type || "any")
|
||||
: "border-gray-300 dark:border-gray-600";
|
||||
return (
|
||||
<div
|
||||
className={`${color} m-1 h-4 w-4 rounded-full border-2 bg-white transition-colors duration-100 group-hover:bg-gray-300 dark:bg-slate-800 dark:group-hover:bg-gray-700`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
// Use a native HTML onContextMenu handler instead of wrapping a large node with a Radix ContextMenu trigger.
|
||||
const handleContextMenu = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
// Optionally, you can trigger a custom, lightweight context menu here.
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
if (side === "left") {
|
||||
return (
|
||||
<div key={keyName} className="handle-container">
|
||||
<div
|
||||
key={keyName}
|
||||
className="handle-container"
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
data-testid={`input-handle-${keyName}`}
|
||||
@@ -67,7 +87,7 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
className="group -ml-[38px]"
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
<Dot />
|
||||
<Dot isConnected={isConnected} type={schema.type} />
|
||||
{label}
|
||||
</div>
|
||||
</Handle>
|
||||
@@ -76,7 +96,11 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div key={keyName} className="handle-container justify-end">
|
||||
<div
|
||||
key={keyName}
|
||||
className="handle-container justify-end"
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<Handle
|
||||
type="source"
|
||||
data-testid={`output-handle-${keyName}`}
|
||||
@@ -86,7 +110,7 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
{label}
|
||||
<Dot />
|
||||
<Dot isConnected={isConnected} type={schema.type} />
|
||||
</div>
|
||||
</Handle>
|
||||
</div>
|
||||
@@ -94,4 +118,4 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
export default NodeHandle;
|
||||
export default memo(NodeHandle);
|
||||
|
||||
@@ -8,28 +8,21 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import useCredits from "@/hooks/useCredits";
|
||||
|
||||
interface CreditsCardProps {
|
||||
credits: number;
|
||||
}
|
||||
|
||||
const CreditsCard = ({ credits }: CreditsCardProps) => {
|
||||
const [currentCredits, setCurrentCredits] = useState(credits);
|
||||
const CreditsCard = () => {
|
||||
const { credits, formatCredits, fetchCredits } = useCredits();
|
||||
const api = useBackendAPI();
|
||||
|
||||
const onRefresh = async () => {
|
||||
const { credits } = await api.getUserCredit("credits-card");
|
||||
setCurrentCredits(credits);
|
||||
fetchCredits();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="inline-flex h-[48px] items-center gap-2.5 rounded-2xl bg-neutral-200 p-4 dark:bg-neutral-800">
|
||||
<div className="flex items-center gap-0.5">
|
||||
<span className="p-ui-semibold text-base leading-7 text-neutral-900 dark:text-neutral-50">
|
||||
{currentCredits.toLocaleString()}
|
||||
</span>
|
||||
<span className="p-ui pl-1 text-base leading-7 text-neutral-900 dark:text-neutral-50">
|
||||
credits
|
||||
Balance: {formatCredits(credits)}
|
||||
</span>
|
||||
</div>
|
||||
<Tooltip key="RefreshCredits" delayDuration={500}>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import Image from "next/image";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { useState } from "react";
|
||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||
|
||||
interface FeaturedStoreCardProps {
|
||||
agent: StoreAgent;
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export const FeaturedAgentCard: React.FC<FeaturedStoreCardProps> = ({
|
||||
agent,
|
||||
backgroundColor,
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<Card
|
||||
data-testid="featured-store-card"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className={backgroundColor}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>{agent.agent_name}</CardTitle>
|
||||
<CardDescription>{agent.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="relative h-[397px] w-full overflow-hidden rounded-xl">
|
||||
<div
|
||||
className={`transition-opacity duration-200 ${isHovered ? "opacity-0" : "opacity-100"}`}
|
||||
>
|
||||
<Image
|
||||
src={agent.agent_image || "/AUTOgpt_Logo_dark.png"}
|
||||
alt={`${agent.agent_name} preview`}
|
||||
fill
|
||||
sizes="100%"
|
||||
className="rounded-xl object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`absolute inset-0 overflow-y-auto transition-opacity duration-200 ${
|
||||
isHovered ? "opacity-100" : "opacity-0"
|
||||
} rounded-xl dark:bg-neutral-700`}
|
||||
>
|
||||
<p className="text-base text-neutral-800 dark:text-neutral-200">
|
||||
{agent.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex items-center justify-between">
|
||||
<div className="font-semibold">
|
||||
{agent.runs?.toLocaleString() ?? "0"} runs
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<p>{agent.rating.toFixed(1) ?? "0.0"}</p>
|
||||
<div
|
||||
className="inline-flex items-center justify-start gap-px"
|
||||
role="img"
|
||||
aria-label={`Rating: ${agent.rating.toFixed(1)} out of 5 stars`}
|
||||
>
|
||||
{StarRatingIcons(agent.rating)}
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { FeaturedStoreCard } from "./FeaturedStoreCard";
|
||||
import { FeaturedAgentCard } from "./FeaturedAgentCard";
|
||||
import { userEvent, within } from "@storybook/test";
|
||||
|
||||
const meta = {
|
||||
title: "AGPT UI/Featured Store Card",
|
||||
component: FeaturedStoreCard,
|
||||
component: FeaturedAgentCard,
|
||||
parameters: {
|
||||
layout: {
|
||||
center: true,
|
||||
@@ -13,123 +13,63 @@ const meta = {
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
agentName: { control: "text" },
|
||||
subHeading: { control: "text" },
|
||||
agentImage: { control: "text" },
|
||||
creatorImage: { control: "text" },
|
||||
creatorName: { control: "text" },
|
||||
description: { control: "text" },
|
||||
runs: { control: "number" },
|
||||
rating: { control: "number", min: 0, max: 5, step: 0.1 },
|
||||
onClick: { action: "clicked" },
|
||||
agent: {
|
||||
agent_name: { control: "text" },
|
||||
sub_heading: { control: "text" },
|
||||
agent_image: { control: "text" },
|
||||
creator_avatar: { control: "text" },
|
||||
creator: { control: "text" },
|
||||
runs: { control: "number" },
|
||||
rating: { control: "number", min: 0, max: 5, step: 0.1 },
|
||||
slug: { control: "text" },
|
||||
},
|
||||
backgroundColor: {
|
||||
control: "color",
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof FeaturedStoreCard>;
|
||||
} satisfies Meta<typeof FeaturedAgentCard>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
agentName: "Personalized Morning Coffee Newsletter example of three lines",
|
||||
subHeading:
|
||||
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
||||
description:
|
||||
"Elevate your web content with this powerful AI Webpage Copy Improver. Designed for marketers, SEO specialists, and web developers, this tool analyses and enhances website copy for maximum impact. Using advanced language models, it optimizes text for better clarity, SEO performance, and increased conversion rates.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "AI Solutions Inc.",
|
||||
runs: 50000,
|
||||
rating: 4.7,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const LowRating: Story = {
|
||||
args: {
|
||||
agentName: "Data Analyzer Lite",
|
||||
subHeading: "Basic data analysis tool",
|
||||
description:
|
||||
"A lightweight data analysis tool for basic data processing needs.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "DataTech",
|
||||
runs: 10000,
|
||||
rating: 2.8,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const HighRuns: Story = {
|
||||
args: {
|
||||
agentName: "CodeAssist AI",
|
||||
subHeading: "Your AI coding companion",
|
||||
description:
|
||||
"An intelligent coding assistant that helps developers write better code faster.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "DevTools Co.",
|
||||
runs: 1000000,
|
||||
rating: 4.9,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const NoCreatorImage: Story = {
|
||||
args: {
|
||||
agentName: "MultiTasker",
|
||||
subHeading: "All-in-one productivity suite",
|
||||
description:
|
||||
"A comprehensive productivity suite that combines task management, note-taking, and project planning into one seamless interface.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "Productivity Plus",
|
||||
runs: 75000,
|
||||
rating: 4.5,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const ShortDescription: Story = {
|
||||
args: {
|
||||
agentName: "QuickTask",
|
||||
subHeading: "Fast task automation",
|
||||
description: "Simple and efficient task automation tool.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "EfficientWorks",
|
||||
runs: 50000,
|
||||
rating: 4.2,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
agent: {
|
||||
agent_name:
|
||||
"Personalized Morning Coffee Newsletter example of three lines",
|
||||
sub_heading:
|
||||
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
||||
description:
|
||||
"Elevate your web content with this powerful AI Webpage Copy Improver. Designed for marketers, SEO specialists, and web developers, this tool analyses and enhances website copy for maximum impact. Using advanced language models, it optimizes text for better clarity, SEO performance, and increased conversion rates.",
|
||||
agent_image:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator_avatar:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator: "AI Solutions Inc.",
|
||||
runs: 50000,
|
||||
rating: 4.7,
|
||||
slug: "",
|
||||
},
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithInteraction: Story = {
|
||||
args: {
|
||||
agentName: "AI Writing Assistant",
|
||||
subHeading: "Enhance your writing",
|
||||
description:
|
||||
"An AI-powered writing assistant that helps improve your writing style and clarity.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "WordCraft AI",
|
||||
runs: 200000,
|
||||
rating: 4.6,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
agent: {
|
||||
slug: "",
|
||||
agent_name: "AI Writing Assistant",
|
||||
sub_heading: "Enhance your writing",
|
||||
description:
|
||||
"An AI-powered writing assistant that helps improve your writing style and clarity.",
|
||||
agent_image:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator_avatar:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator: "WordCraft AI",
|
||||
runs: 200000,
|
||||
rating: 4.6,
|
||||
},
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
|
||||
interface FeaturedStoreCardProps {
|
||||
agentName: string;
|
||||
subHeading: string;
|
||||
agentImage: string;
|
||||
creatorImage?: string;
|
||||
creatorName: string;
|
||||
description: string; // Added description prop
|
||||
runs: number;
|
||||
rating: number;
|
||||
onClick: () => void;
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export const FeaturedStoreCard: React.FC<FeaturedStoreCardProps> = ({
|
||||
agentName,
|
||||
subHeading,
|
||||
agentImage,
|
||||
creatorImage,
|
||||
creatorName,
|
||||
description,
|
||||
runs,
|
||||
rating,
|
||||
onClick,
|
||||
backgroundColor,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`group h-[755px] w-[440px] px-[22px] pb-5 pt-[30px] ${backgroundColor} inline-flex flex-col items-start justify-start gap-7 rounded-[26px] transition-all duration-200 hover:brightness-95 dark:bg-neutral-800`}
|
||||
onClick={onClick}
|
||||
data-testid="featured-store-card"
|
||||
>
|
||||
<div className="flex h-[188px] flex-col items-start justify-start gap-3 self-stretch">
|
||||
<h2 className="font-poppins self-stretch text-[35px] font-medium leading-10 text-neutral-900 dark:text-neutral-100">
|
||||
{agentName}
|
||||
</h2>
|
||||
<div className="font-lead self-stretch text-xl font-normal leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
{subHeading}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-[489px] flex-col items-start justify-start gap-[18px] self-stretch">
|
||||
<div className="font-lead self-stretch text-xl font-normal leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
by {creatorName}
|
||||
</div>
|
||||
|
||||
<div className="relative h-[397px] self-stretch">
|
||||
<Image
|
||||
src={agentImage}
|
||||
alt={`${agentName} preview`}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="rounded-xl transition-opacity duration-200 group-hover:opacity-0"
|
||||
/>
|
||||
<div className="absolute inset-0 overflow-y-auto rounded-xl bg-white p-4 opacity-0 transition-opacity duration-200 group-hover:opacity-100 dark:bg-neutral-700">
|
||||
<div className="font-geist text-base font-normal leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
{creatorImage && (
|
||||
<div className="absolute left-[8.74px] top-[313px] h-[74px] w-[74px] overflow-hidden rounded-full transition-opacity duration-200 group-hover:opacity-0">
|
||||
<Image
|
||||
src={creatorImage}
|
||||
alt={`${creatorName} image`}
|
||||
layout="fill"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="inline-flex items-center justify-between self-stretch">
|
||||
<div className="font-large-geist text-lg font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
{runs.toLocaleString()} runs
|
||||
</div>
|
||||
<div className="flex items-center justify-start gap-[5px]">
|
||||
<div className="font-large-geist text-lg font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
{rating.toFixed(1)}
|
||||
</div>
|
||||
<div
|
||||
className="inline-flex items-center justify-start gap-px"
|
||||
role="img"
|
||||
aria-label={`Rating: ${rating.toFixed(1)} out of 5 stars`}
|
||||
>
|
||||
{StarRatingIcons(rating)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -33,26 +33,17 @@ interface NavbarProps {
|
||||
|
||||
async function getProfileData() {
|
||||
const api = new BackendAPI();
|
||||
const [profile, credits] = await Promise.all([
|
||||
api.getStoreProfile("navbar"),
|
||||
api.getUserCredit("navbar"),
|
||||
]);
|
||||
const profile = await Promise.resolve(api.getStoreProfile("navbar"));
|
||||
|
||||
return {
|
||||
profile,
|
||||
credits,
|
||||
};
|
||||
return profile;
|
||||
}
|
||||
|
||||
export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
||||
const { user } = await getServerUser();
|
||||
const isLoggedIn = user !== null;
|
||||
let profile: ProfileDetails | null = null;
|
||||
let credits: { credits: number } = { credits: 0 };
|
||||
if (isLoggedIn) {
|
||||
const { profile: t_profile, credits: t_credits } = await getProfileData();
|
||||
profile = t_profile;
|
||||
credits = t_credits;
|
||||
profile = await getProfileData();
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -70,7 +61,7 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
||||
<div className="flex items-center gap-4">
|
||||
{isLoggedIn ? (
|
||||
<div className="flex items-center gap-4">
|
||||
{profile && <CreditsCard credits={credits.credits} />}
|
||||
{profile && <CreditsCard />}
|
||||
<ProfilePopoutMenu
|
||||
menuItemGroups={menuItemGroups}
|
||||
userName={profile?.username}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
||||
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
||||
<Link
|
||||
href="/marketplace/dashboard"
|
||||
href="/profile/dashboard"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconDashboardLayout className="h-6 w-6" />
|
||||
@@ -56,17 +56,17 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</Link>
|
||||
{stripeAvailable && (
|
||||
<Link
|
||||
href="/marketplace/credits"
|
||||
href="/profile/credits"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconCoin className="h-6 w-6" />
|
||||
<div className="p-ui-medium text-base font-medium leading-normal">
|
||||
Credits
|
||||
Billing
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/marketplace/integrations"
|
||||
href="/profile/integrations"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconIntegrations className="h-6 w-6" />
|
||||
@@ -75,7 +75,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace/api_keys"
|
||||
href="/profile/api_keys"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<KeyIcon className="h-6 w-6" />
|
||||
@@ -84,7 +84,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace/profile"
|
||||
href="/profile"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconProfile className="h-6 w-6" />
|
||||
@@ -93,7 +93,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace/settings"
|
||||
href="/profile/settings"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconSliders className="h-6 w-6" />
|
||||
@@ -110,7 +110,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
||||
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
||||
<Link
|
||||
href="/marketplace/dashboard"
|
||||
href="/profile/dashboard"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconDashboardLayout className="h-6 w-6" />
|
||||
@@ -120,17 +120,17 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</Link>
|
||||
{stripeAvailable && (
|
||||
<Link
|
||||
href="/marketplace/credits"
|
||||
href="/profile/credits"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconCoin className="h-6 w-6" />
|
||||
<div className="p-ui-medium text-base font-medium leading-normal">
|
||||
Credits
|
||||
Billing
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/marketplace/integrations"
|
||||
href="/profile/integrations"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconIntegrations className="h-6 w-6" />
|
||||
@@ -139,7 +139,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace/api_keys"
|
||||
href="/profile/api_keys"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<KeyIcon className="h-6 w-6" strokeWidth={1} />
|
||||
@@ -148,7 +148,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace/profile"
|
||||
href="/profile"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconProfile className="h-6 w-6" />
|
||||
@@ -157,7 +157,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace/settings"
|
||||
href="/profile/settings"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconSliders className="h-6 w-6" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { FeaturedAgent, FeaturedSection } from "./FeaturedSection";
|
||||
import { userEvent, within, expect } from "@storybook/test";
|
||||
import { FeaturedSection } from "./FeaturedSection";
|
||||
import { userEvent, within } from "@storybook/test";
|
||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||
|
||||
const meta = {
|
||||
title: "AGPT UI/Composite/Featured Agents",
|
||||
@@ -15,7 +16,6 @@ const meta = {
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
featuredAgents: { control: "object" },
|
||||
// onCardClick: { action: "clicked" },
|
||||
},
|
||||
} satisfies Meta<typeof FeaturedSection>;
|
||||
|
||||
@@ -93,7 +93,7 @@ const mockFeaturedAgents = [
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
slug: "quicktask",
|
||||
},
|
||||
] satisfies FeaturedAgent[];
|
||||
] satisfies StoreAgent[];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { FeaturedStoreCard } from "@/components/agptui/FeaturedStoreCard";
|
||||
import { FeaturedAgentCard } from "@/components/agptui/FeaturedAgentCard";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
CarouselIndicator,
|
||||
} from "@/components/ui/carousel";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||
import Link from "next/link";
|
||||
|
||||
const BACKGROUND_COLORS = [
|
||||
"bg-violet-200 dark:bg-violet-800", // #ddd6fe / #5b21b6
|
||||
@@ -19,33 +20,14 @@ const BACKGROUND_COLORS = [
|
||||
"bg-green-200 dark:bg-green-800", // #bbf7d0 / #065f46
|
||||
];
|
||||
|
||||
export interface FeaturedAgent {
|
||||
slug: string;
|
||||
agent_name: string;
|
||||
agent_image: string;
|
||||
creator: string;
|
||||
creator_avatar: string;
|
||||
sub_heading: string;
|
||||
description: string;
|
||||
runs: number;
|
||||
rating: number;
|
||||
}
|
||||
|
||||
interface FeaturedSectionProps {
|
||||
featuredAgents: FeaturedAgent[];
|
||||
featuredAgents: StoreAgent[];
|
||||
}
|
||||
|
||||
export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
||||
featuredAgents,
|
||||
}) => {
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const router = useRouter();
|
||||
|
||||
const handleCardClick = (creator: string, slug: string) => {
|
||||
router.push(
|
||||
`/marketplace/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||
);
|
||||
};
|
||||
|
||||
const handlePrevSlide = useCallback(() => {
|
||||
setCurrentSlide((prev) =>
|
||||
@@ -84,17 +66,14 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
||||
key={index}
|
||||
className="max-w-[460px] flex-[0_0_auto]"
|
||||
>
|
||||
<FeaturedStoreCard
|
||||
agentName={agent.agent_name}
|
||||
subHeading={agent.sub_heading}
|
||||
agentImage={agent.agent_image}
|
||||
creatorName={agent.creator}
|
||||
description={agent.description}
|
||||
runs={agent.runs}
|
||||
rating={agent.rating}
|
||||
backgroundColor={getBackgroundColor(index)}
|
||||
onClick={() => handleCardClick(agent.creator, agent.slug)}
|
||||
/>
|
||||
<Link
|
||||
href={`/marketplace/agent/${encodeURIComponent(agent.creator)}/${encodeURIComponent(agent.slug)}`}
|
||||
>
|
||||
<FeaturedAgentCard
|
||||
agent={agent}
|
||||
backgroundColor={getBackgroundColor(index)}
|
||||
/>
|
||||
</Link>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
|
||||
@@ -76,6 +76,7 @@ export const providerIcons: Record<
|
||||
open_router: fallbackIcon,
|
||||
pinecone: fallbackIcon,
|
||||
slant3d: fallbackIcon,
|
||||
screenshotone: fallbackIcon,
|
||||
smtp: fallbackIcon,
|
||||
replicate: fallbackIcon,
|
||||
reddit: fallbackIcon,
|
||||
|
||||
@@ -41,6 +41,7 @@ const providerDisplayNames: Record<CredentialsProviderName, string> = {
|
||||
open_router: "Open Router",
|
||||
pinecone: "Pinecone",
|
||||
slant3d: "Slant3D",
|
||||
screenshotone: "ScreenshotOne",
|
||||
smtp: "SMTP",
|
||||
reddit: "Reddit",
|
||||
replicate: "Replicate",
|
||||
|
||||
@@ -65,7 +65,7 @@ export const FlowInfo: React.FC<
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = useAgentGraph(flow.id, false);
|
||||
} = useAgentGraph(flow.id, flow.version, undefined, false);
|
||||
|
||||
const api = useBackendAPI();
|
||||
const { toast } = useToast();
|
||||
@@ -224,7 +224,7 @@ export const FlowInfo: React.FC<
|
||||
)}
|
||||
<Link
|
||||
className={buttonVariants({ variant: "default" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
href={`/build?flowID=${flow.id}&flowVersion=${flow.version}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" />
|
||||
Open in Builder
|
||||
|
||||
@@ -109,7 +109,7 @@ export const FlowRunInfo: React.FC<
|
||||
</Button>
|
||||
<Link
|
||||
className={buttonVariants({ variant: "default" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
href={`/build?flowID=${flow.id}&flowVersion=${execution.graph_version}&flowExecutionID=${execution.execution_id}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" /> Open in Builder
|
||||
</Link>
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { TextRenderer } from "../ui/render";
|
||||
import { Input } from "../ui/input";
|
||||
import { Label } from "../ui/label";
|
||||
|
||||
interface SchedulesTableProps {
|
||||
schedules: Schedule[];
|
||||
@@ -53,6 +55,8 @@ export const SchedulesTable = ({
|
||||
const router = useRouter();
|
||||
const cron_manager = new CronExpressionManager();
|
||||
const [selectedAgent, setSelectedAgent] = useState<string>("");
|
||||
const [selectedVersion, setSelectedVersion] = useState<number>(0);
|
||||
const [maxVersion, setMaxVersion] = useState<number>(0);
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedFilter, setSelectedFilter] = useState<string>("");
|
||||
@@ -86,13 +90,38 @@ export const SchedulesTable = ({
|
||||
|
||||
const handleAgentSelect = (agentId: string) => {
|
||||
setSelectedAgent(agentId);
|
||||
const agent = agents.find((a) => a.id === agentId);
|
||||
setMaxVersion(agent!.version);
|
||||
setSelectedVersion(agent!.version);
|
||||
};
|
||||
|
||||
const handleVersionSelect = (version: string) => {
|
||||
setSelectedVersion(parseInt(version));
|
||||
};
|
||||
|
||||
const handleSchedule = async () => {
|
||||
if (!selectedAgent || !selectedVersion) {
|
||||
toast({
|
||||
title: "Invalid Input",
|
||||
description: "Please select an agent and a version.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (selectedVersion < 1 || selectedVersion > maxVersion) {
|
||||
toast({
|
||||
title: "Invalid Version",
|
||||
description: `Please select a version between 1 and ${maxVersion}.`,
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
router.push(`/build?flowID=${selectedAgent}&open_scheduling=true`);
|
||||
router.push(
|
||||
`/build?flowID=${selectedAgent}&flowVersion=${selectedVersion}&open_scheduling=true`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Navigation error:", error);
|
||||
}
|
||||
@@ -117,6 +146,18 @@ export const SchedulesTable = ({
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Label className="mt-4">
|
||||
Select version between 1 and {maxVersion}
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={selectedAgent ? maxVersion : 0}
|
||||
value={selectedVersion}
|
||||
onChange={(e) => handleVersionSelect(e.target.value)}
|
||||
placeholder="Select version"
|
||||
className="w-full"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSchedule}
|
||||
disabled={isLoading || !selectedAgent}
|
||||
@@ -165,6 +206,7 @@ export const SchedulesTable = ({
|
||||
>
|
||||
Graph Name
|
||||
</TableHead>
|
||||
<TableHead className="cursor-pointer">Graph Version</TableHead>
|
||||
<TableHead
|
||||
onClick={() => onSort("next_run_time")}
|
||||
className="cursor-pointer"
|
||||
@@ -198,6 +240,7 @@ export const SchedulesTable = ({
|
||||
{agents.find((a) => a.id === schedule.graph_id)?.name ||
|
||||
schedule.graph_id}
|
||||
</TableCell>
|
||||
<TableCell>{schedule.graph_version}</TableCell>
|
||||
<TableCell>
|
||||
{new Date(schedule.next_run_time).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { IconRefresh } from "@/components/ui/icons";
|
||||
import useCredits from "@/hooks/useCredits";
|
||||
|
||||
export default function CreditButton() {
|
||||
const { credits, fetchCredits } = useCredits();
|
||||
|
||||
return (
|
||||
credits !== null && (
|
||||
<Button
|
||||
onClick={fetchCredits}
|
||||
variant="outline"
|
||||
className="flex items-center space-x-2 rounded-xl bg-gray-200"
|
||||
>
|
||||
<span className="mr-2 flex items-center text-foreground">
|
||||
{credits} <span className="ml-2 text-muted-foreground"> credits</span>
|
||||
</span>
|
||||
<IconRefresh />
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { ButtonHTMLAttributes } from "react";
|
||||
import React from "react";
|
||||
|
||||
interface MarketPopupProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
marketplaceUrl?: string;
|
||||
}
|
||||
|
||||
export default function MarketPopup({
|
||||
className = "",
|
||||
marketplaceUrl = (() => {
|
||||
if (process.env.NEXT_PUBLIC_APP_ENV === "prod") {
|
||||
return "https://production-marketplace-url.com";
|
||||
} else if (process.env.NEXT_PUBLIC_APP_ENV === "dev") {
|
||||
return "https://dev-builder.agpt.co/marketplace";
|
||||
} else {
|
||||
return "http://localhost:3000/marketplace";
|
||||
}
|
||||
})(),
|
||||
children,
|
||||
...props
|
||||
}: MarketPopupProps) {
|
||||
const openMarketplacePopup = () => {
|
||||
window.open(
|
||||
marketplaceUrl,
|
||||
"popupWindow",
|
||||
"width=600,height=400,toolbar=no,menubar=no,scrollbars=no",
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={openMarketplacePopup} className={className} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { BsBoxes } from "react-icons/bs";
|
||||
import { LuLaptop, LuShoppingCart } from "react-icons/lu";
|
||||
import { BehaveAs, cn } from "@/lib/utils";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { getBehaveAs } from "@/lib/utils";
|
||||
import { IconMarketplace } from "@/components/ui/icons";
|
||||
import MarketPopup from "./MarketPopup";
|
||||
|
||||
export function NavBarButtons({ className }: { className?: string }) {
|
||||
const pathname = usePathname();
|
||||
const buttons = [
|
||||
{
|
||||
href: "/",
|
||||
text: "Monitor",
|
||||
icon: <LuLaptop />,
|
||||
},
|
||||
{
|
||||
href: "/build",
|
||||
text: "Build",
|
||||
icon: <BsBoxes />,
|
||||
},
|
||||
{
|
||||
href: "/marketplace",
|
||||
text: "Marketplace",
|
||||
icon: <IconMarketplace />,
|
||||
},
|
||||
];
|
||||
|
||||
const isCloud = getBehaveAs() === BehaveAs.CLOUD;
|
||||
|
||||
return (
|
||||
<>
|
||||
{buttons.map((button) => {
|
||||
const isActive = button.href === pathname;
|
||||
return (
|
||||
<Link
|
||||
key={button.href}
|
||||
href={button.href}
|
||||
data-testid={`${button.text.toLowerCase()}-nav-link`}
|
||||
className={cn(
|
||||
className,
|
||||
"flex items-center gap-2 rounded-xl p-3",
|
||||
isActive
|
||||
? "bg-gray-950 text-white"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{button.icon} {button.text}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{isCloud ? (
|
||||
<Link
|
||||
href="/marketplace"
|
||||
data-testid="marketplace-nav-link"
|
||||
className={cn(
|
||||
className,
|
||||
"flex items-center gap-2 rounded-xl p-3",
|
||||
pathname === "/marketplace"
|
||||
? "bg-gray-950 text-white"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<LuShoppingCart /> Marketplace
|
||||
</Link>
|
||||
) : (
|
||||
<MarketPopup
|
||||
data-testid="marketplace-nav-link"
|
||||
className={cn(
|
||||
className,
|
||||
"flex items-center gap-2 rounded-xl p-3 text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<LuShoppingCart /> Marketplace
|
||||
</MarketPopup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import BackendAPI, {
|
||||
Block,
|
||||
BlockIOSubSchema,
|
||||
BlockUIType,
|
||||
formatEdgeID,
|
||||
Graph,
|
||||
Link,
|
||||
NodeExecutionResult,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import {
|
||||
@@ -14,18 +14,22 @@ import {
|
||||
removeEmptyStringsAndNulls,
|
||||
setNestedProperty,
|
||||
} from "@/lib/utils";
|
||||
import { Connection, MarkerType } from "@xyflow/react";
|
||||
import { MarkerType } from "@xyflow/react";
|
||||
import Ajv from "ajv";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { InputItem } from "@/components/RunnerUIWrapper";
|
||||
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import useCredits from "./useCredits";
|
||||
import { default as NextLink } from "next/link";
|
||||
|
||||
const ajv = new Ajv({ strict: false, allErrors: true });
|
||||
|
||||
export default function useAgentGraph(
|
||||
flowID?: string,
|
||||
flowVersion?: number,
|
||||
flowExecutionID?: string,
|
||||
passDataToBeads?: boolean,
|
||||
) {
|
||||
const { toast } = useToast();
|
||||
@@ -72,31 +76,13 @@ export default function useAgentGraph(
|
||||
useState(false);
|
||||
const [nodes, setNodes] = useState<CustomNode[]>([]);
|
||||
const [edges, setEdges] = useState<CustomEdge[]>([]);
|
||||
const { credits, fetchCredits } = useCredits();
|
||||
|
||||
const api = useMemo(
|
||||
() => new BackendAPI(process.env.NEXT_PUBLIC_AGPT_SERVER_URL!),
|
||||
[],
|
||||
);
|
||||
|
||||
// Connect to WebSocket
|
||||
useEffect(() => {
|
||||
api
|
||||
.connectWebSocket()
|
||||
.then(() => {
|
||||
console.debug("WebSocket connected");
|
||||
api.onWebSocketMessage("execution_event", (data) => {
|
||||
setUpdateQueue((prev) => [...prev, data]);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to connect WebSocket:", error);
|
||||
});
|
||||
|
||||
return () => {
|
||||
api.disconnectWebSocket();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
// Load available blocks & flows
|
||||
useEffect(() => {
|
||||
api
|
||||
@@ -108,16 +94,32 @@ export default function useAgentGraph(
|
||||
.listGraphs()
|
||||
.then((flows) => setAvailableFlows(flows))
|
||||
.catch();
|
||||
|
||||
api.connectWebSocket().catch((error) => {
|
||||
console.error("Failed to connect WebSocket:", error);
|
||||
});
|
||||
|
||||
return () => {
|
||||
api.disconnectWebSocket();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
//TODO to utils? repeated in Flow
|
||||
const formatEdgeID = useCallback((conn: Link | Connection): string => {
|
||||
if ("sink_id" in conn) {
|
||||
return `${conn.source_id}_${conn.source_name}_${conn.sink_id}_${conn.sink_name}`;
|
||||
} else {
|
||||
return `${conn.source}_${conn.sourceHandle}_${conn.target}_${conn.targetHandle}`;
|
||||
// Subscribe to execution events
|
||||
useEffect(() => {
|
||||
api.onWebSocketMessage("execution_event", (data) => {
|
||||
if (data.graph_exec_id != flowExecutionID) {
|
||||
return;
|
||||
}
|
||||
setUpdateQueue((prev) => [...prev, data]);
|
||||
});
|
||||
|
||||
if (flowID && flowVersion) {
|
||||
api.subscribeToExecution(flowID, flowVersion);
|
||||
console.debug(
|
||||
`Subscribed to execution events for ${flowID} v.${flowVersion}`,
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
}, [api, flowID, flowVersion, flowExecutionID]);
|
||||
|
||||
const getOutputType = useCallback(
|
||||
(nodes: CustomNode[], nodeId: string, handleId: string) => {
|
||||
@@ -141,80 +143,82 @@ export default function useAgentGraph(
|
||||
setAgentName(graph.name);
|
||||
setAgentDescription(graph.description);
|
||||
|
||||
setNodes(() => {
|
||||
const newNodes = graph.nodes
|
||||
.map((node) => {
|
||||
const block = availableNodes.find(
|
||||
(block) => block.id === node.block_id,
|
||||
)!;
|
||||
if (!block) return null;
|
||||
const flow =
|
||||
block.uiType == BlockUIType.AGENT
|
||||
? availableFlows.find(
|
||||
(flow) => flow.id === node.input_default.graph_id,
|
||||
)
|
||||
: null;
|
||||
const newNode: CustomNode = {
|
||||
id: node.id,
|
||||
type: "custom",
|
||||
position: {
|
||||
x: node?.metadata?.position?.x || 0,
|
||||
y: node?.metadata?.position?.y || 0,
|
||||
},
|
||||
data: {
|
||||
block_id: block.id,
|
||||
blockType: flow?.name || block.name,
|
||||
blockCosts: block.costs,
|
||||
categories: block.categories,
|
||||
description: block.description,
|
||||
title: `${block.name} ${node.id}`,
|
||||
inputSchema: block.inputSchema,
|
||||
outputSchema: block.outputSchema,
|
||||
hardcodedValues: node.input_default,
|
||||
webhook: node.webhook,
|
||||
uiType: block.uiType,
|
||||
connections: graph.links
|
||||
.filter((l) => [l.source_id, l.sink_id].includes(node.id))
|
||||
.map((link) => ({
|
||||
edge_id: formatEdgeID(link),
|
||||
source: link.source_id,
|
||||
sourceHandle: link.source_name,
|
||||
target: link.sink_id,
|
||||
targetHandle: link.sink_name,
|
||||
})),
|
||||
isOutputOpen: false,
|
||||
},
|
||||
};
|
||||
return newNode;
|
||||
})
|
||||
.filter((node) => node !== null);
|
||||
setEdges((_) =>
|
||||
graph.links.map((link) => ({
|
||||
id: formatEdgeID(link),
|
||||
setNodes((prevNodes) => {
|
||||
const newNodes = graph.nodes.map((node) => {
|
||||
const block = availableNodes.find(
|
||||
(block) => block.id === node.block_id,
|
||||
)!;
|
||||
const prevNode = prevNodes.find((n) => n.id === node.id);
|
||||
const flow =
|
||||
block.uiType == BlockUIType.AGENT
|
||||
? availableFlows.find(
|
||||
(flow) => flow.id === node.input_default.graph_id,
|
||||
)
|
||||
: null;
|
||||
const newNode: CustomNode = {
|
||||
id: node.id,
|
||||
type: "custom",
|
||||
position: {
|
||||
x: node?.metadata?.position?.x || 0,
|
||||
y: node?.metadata?.position?.y || 0,
|
||||
},
|
||||
data: {
|
||||
edgeColor: getTypeColor(
|
||||
getOutputType(newNodes, link.source_id, link.source_name!),
|
||||
),
|
||||
sourcePos: newNodes.find((node) => node.id === link.source_id)
|
||||
?.position,
|
||||
isStatic: link.is_static,
|
||||
beadUp: 0,
|
||||
beadDown: 0,
|
||||
beadData: [],
|
||||
isOutputOpen: false,
|
||||
...prevNode?.data,
|
||||
block_id: block.id,
|
||||
blockType: flow?.name || block.name,
|
||||
blockCosts: block.costs,
|
||||
categories: block.categories,
|
||||
description: block.description,
|
||||
title: `${block.name} ${node.id}`,
|
||||
inputSchema: block.inputSchema,
|
||||
outputSchema: block.outputSchema,
|
||||
hardcodedValues: node.input_default,
|
||||
webhook: node.webhook,
|
||||
uiType: block.uiType,
|
||||
connections: graph.links
|
||||
.filter((l) => [l.source_id, l.sink_id].includes(node.id))
|
||||
.map((link) => ({
|
||||
edge_id: formatEdgeID(link),
|
||||
source: link.source_id,
|
||||
sourceHandle: link.source_name,
|
||||
target: link.sink_id,
|
||||
targetHandle: link.sink_name,
|
||||
})),
|
||||
backend_id: node.id,
|
||||
},
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
strokeWidth: 2,
|
||||
color: getTypeColor(
|
||||
getOutputType(newNodes, link.source_id, link.source_name!),
|
||||
),
|
||||
},
|
||||
source: link.source_id,
|
||||
target: link.sink_id,
|
||||
sourceHandle: link.source_name || undefined,
|
||||
targetHandle: link.sink_name || undefined,
|
||||
})),
|
||||
};
|
||||
return newNode;
|
||||
});
|
||||
setEdges(() =>
|
||||
graph.links.map((link) => {
|
||||
return {
|
||||
id: formatEdgeID(link),
|
||||
type: "custom",
|
||||
data: {
|
||||
edgeColor: getTypeColor(
|
||||
getOutputType(newNodes, link.source_id, link.source_name!),
|
||||
),
|
||||
sourcePos: newNodes.find((node) => node.id === link.source_id)
|
||||
?.position,
|
||||
isStatic: link.is_static,
|
||||
beadUp: 0,
|
||||
beadDown: 0,
|
||||
beadData: [],
|
||||
},
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
strokeWidth: 2,
|
||||
color: getTypeColor(
|
||||
getOutputType(newNodes, link.source_id, link.source_name!),
|
||||
),
|
||||
},
|
||||
source: link.source_id,
|
||||
target: link.sink_id,
|
||||
sourceHandle: link.source_name || undefined,
|
||||
targetHandle: link.sink_name || undefined,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return newNodes;
|
||||
});
|
||||
@@ -245,7 +249,8 @@ export default function useAgentGraph(
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
edge.data!.beadUp = (edge.data!.beadUp ?? 0) + 1;
|
||||
const count = executionData.output_data[key].length;
|
||||
edge.data!.beadUp = (edge.data!.beadUp ?? 0) + count;
|
||||
// For static edges beadDown is always one less than beadUp
|
||||
// Because there's no queueing and one bead is always at the connection point
|
||||
if (edge.data?.isStatic) {
|
||||
@@ -253,9 +258,8 @@ export default function useAgentGraph(
|
||||
edge.data!.beadData = edge.data!.beadData!.slice(0, -1);
|
||||
continue;
|
||||
}
|
||||
//todo kcze this assumes output at key is always array with one element
|
||||
edge.data!.beadData = [
|
||||
executionData.output_data[key][0],
|
||||
...executionData.output_data[key].toReversed(),
|
||||
...edge.data!.beadData!,
|
||||
];
|
||||
}
|
||||
@@ -332,14 +336,15 @@ export default function useAgentGraph(
|
||||
[passDataToBeads, updateEdgeBeads],
|
||||
);
|
||||
|
||||
// Load graph
|
||||
useEffect(() => {
|
||||
if (!flowID || availableNodes.length == 0) return;
|
||||
|
||||
api.getGraph(flowID).then((graph) => {
|
||||
api.getGraph(flowID, flowVersion).then((graph) => {
|
||||
console.debug("Loading graph");
|
||||
loadGraph(graph);
|
||||
});
|
||||
}, [flowID, availableNodes, api, loadGraph]);
|
||||
}, [flowID, flowVersion, availableNodes, api, loadGraph]);
|
||||
|
||||
// Update nodes with execution data
|
||||
useEffect(() => {
|
||||
@@ -540,44 +545,22 @@ export default function useAgentGraph(
|
||||
});
|
||||
return;
|
||||
}
|
||||
api.subscribeToExecution(savedAgent.id);
|
||||
setSaveRunRequest({ request: "run", state: "running" });
|
||||
api
|
||||
.executeGraph(savedAgent.id)
|
||||
.executeGraph(savedAgent.id, savedAgent.version)
|
||||
.then((graphExecution) => {
|
||||
setSaveRunRequest({
|
||||
request: "run",
|
||||
state: "running",
|
||||
activeExecutionID: graphExecution.id,
|
||||
activeExecutionID: graphExecution.graph_exec_id,
|
||||
});
|
||||
|
||||
// Track execution until completed
|
||||
const pendingNodeExecutions: Set<string> = new Set();
|
||||
const cancelExecListener = api.onWebSocketMessage(
|
||||
"execution_event",
|
||||
(nodeResult) => {
|
||||
// We are racing the server here, since we need the ID to filter events
|
||||
if (nodeResult.graph_exec_id != graphExecution.id) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!["COMPLETED", "TERMINATED", "FAILED"].includes(
|
||||
nodeResult.status,
|
||||
)
|
||||
) {
|
||||
pendingNodeExecutions.add(nodeResult.node_exec_id);
|
||||
} else {
|
||||
pendingNodeExecutions.delete(nodeResult.node_exec_id);
|
||||
}
|
||||
if (pendingNodeExecutions.size == 0) {
|
||||
// Assuming the first event is always a QUEUED node, and
|
||||
// following nodes are QUEUED before all preceding nodes are COMPLETED,
|
||||
// an empty set means the graph has finished running.
|
||||
cancelExecListener();
|
||||
setSaveRunRequest({ request: "none", state: "none" });
|
||||
}
|
||||
},
|
||||
);
|
||||
// Update URL params
|
||||
const path = new URLSearchParams(searchParams);
|
||||
path.set("flowID", savedAgent.id);
|
||||
path.set("flowVersion", savedAgent.version.toString());
|
||||
path.set("flowExecutionID", graphExecution.graph_exec_id);
|
||||
router.push(`${pathname}?${path.toString()}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorMessage =
|
||||
@@ -618,6 +601,72 @@ export default function useAgentGraph(
|
||||
validateNodes,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!flowID || !flowExecutionID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchExecutions = async () => {
|
||||
const results = await api.getGraphExecutionInfo(flowID, flowExecutionID);
|
||||
setUpdateQueue((prev) => [...prev, ...results]);
|
||||
|
||||
// Track execution until completed
|
||||
const pendingNodeExecutions: Set<string> = new Set();
|
||||
const cancelExecListener = api.onWebSocketMessage(
|
||||
"execution_event",
|
||||
(nodeResult) => {
|
||||
// We are racing the server here, since we need the ID to filter events
|
||||
if (nodeResult.graph_exec_id != flowExecutionID) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
nodeResult.status === "FAILED" &&
|
||||
nodeResult.output_data?.error?.[0]
|
||||
.toLowerCase()
|
||||
.includes("insufficient balance")
|
||||
) {
|
||||
// Show no credits toast if user has low credits
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Credits low",
|
||||
description: (
|
||||
<div>
|
||||
Agent execution failed due to insufficient credits.
|
||||
<br />
|
||||
Go to the{" "}
|
||||
<NextLink
|
||||
className="text-purple-300"
|
||||
href="/marketplace/credits"
|
||||
>
|
||||
Credits
|
||||
</NextLink>{" "}
|
||||
page to top up.
|
||||
</div>
|
||||
),
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
if (
|
||||
!["COMPLETED", "TERMINATED", "FAILED"].includes(nodeResult.status)
|
||||
) {
|
||||
pendingNodeExecutions.add(nodeResult.node_exec_id);
|
||||
} else {
|
||||
pendingNodeExecutions.delete(nodeResult.node_exec_id);
|
||||
}
|
||||
if (pendingNodeExecutions.size == 0) {
|
||||
// Assuming the first event is always a QUEUED node, and
|
||||
// following nodes are QUEUED before all preceding nodes are COMPLETED,
|
||||
// an empty set means the graph has finished running.
|
||||
cancelExecListener();
|
||||
setSaveRunRequest({ request: "none", state: "none" });
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
fetchExecutions();
|
||||
}, [flowID, flowExecutionID]);
|
||||
|
||||
// Check if node ids are synced with saved agent
|
||||
useEffect(() => {
|
||||
// Check if all node ids are synced with saved agent (frontend and backend)
|
||||
@@ -795,6 +844,7 @@ export default function useAgentGraph(
|
||||
if (!savedAgent) {
|
||||
const path = new URLSearchParams(searchParams);
|
||||
path.set("flowID", newSavedAgent.id);
|
||||
path.set("flowVersion", newSavedAgent.version.toString());
|
||||
router.push(`${pathname}?${path.toString()}`);
|
||||
return;
|
||||
}
|
||||
@@ -913,6 +963,8 @@ export default function useAgentGraph(
|
||||
if (flowID) {
|
||||
await api.createSchedule({
|
||||
graph_id: flowID,
|
||||
// flowVersion is always defined here because scheduling is opened for a specific version
|
||||
graph_version: flowVersion!,
|
||||
cron: cronExpression,
|
||||
input_data: inputs.reduce(
|
||||
(acc, input) => ({
|
||||
@@ -101,28 +101,26 @@ export function useCopyPaste(getNextNodeId: () => string) {
|
||||
|
||||
setNodes((nodes) => {
|
||||
return nodes.map((node) => {
|
||||
if (oldToNewIdMap[node.id]) {
|
||||
const nodeConnections = pastedEdges
|
||||
.filter(
|
||||
(edge: Edge) =>
|
||||
edge.source === node.id || edge.target === node.id,
|
||||
)
|
||||
.map((edge: Edge) => ({
|
||||
edge_id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
sourceHandle: edge.sourceHandle,
|
||||
targetHandle: edge.targetHandle,
|
||||
}));
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
connections: nodeConnections,
|
||||
},
|
||||
};
|
||||
}
|
||||
return node;
|
||||
const nodeConnections = getEdges()
|
||||
.filter(
|
||||
(edge: Edge) =>
|
||||
edge.source === node.id || edge.target === node.id,
|
||||
)
|
||||
.map((edge: Edge) => ({
|
||||
edge_id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
sourceHandle: edge.sourceHandle,
|
||||
targetHandle: edge.targetHandle,
|
||||
}));
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
connections: nodeConnections,
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
import { TransactionHistory } from "@/lib/autogpt-server-api/types";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { loadStripe } from "@stripe/stripe-js";
|
||||
import { loadStripe, Stripe } from "@stripe/stripe-js";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const stripePromise = loadStripe(
|
||||
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
|
||||
);
|
||||
|
||||
export default function useCredits(): {
|
||||
credits: number | null;
|
||||
fetchCredits: () => void;
|
||||
@@ -17,12 +13,14 @@ export default function useCredits(): {
|
||||
updateAutoTopUpConfig: (amount: number, threshold: number) => Promise<void>;
|
||||
transactionHistory: TransactionHistory;
|
||||
fetchTransactionHistory: () => void;
|
||||
formatCredits: (credit: number | null) => string;
|
||||
} {
|
||||
const [credits, setCredits] = useState<number | null>(null);
|
||||
const [autoTopUpConfig, setAutoTopUpConfig] = useState<{
|
||||
amount: number;
|
||||
threshold: number;
|
||||
} | null>(null);
|
||||
const [stripe, setStripe] = useState<Stripe | null>(null);
|
||||
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
const router = useRouter();
|
||||
@@ -36,6 +34,20 @@ export default function useCredits(): {
|
||||
fetchCredits();
|
||||
}, [fetchCredits]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStripe = async () => {
|
||||
if (!process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY?.trim()) {
|
||||
console.debug("Stripe publishable key is not set.");
|
||||
return;
|
||||
}
|
||||
const stripe = await loadStripe(
|
||||
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
|
||||
);
|
||||
setStripe(stripe);
|
||||
};
|
||||
fetchStripe();
|
||||
}, []);
|
||||
|
||||
const fetchAutoTopUpConfig = useCallback(async () => {
|
||||
const response = await api.getAutoTopUpConfig();
|
||||
setAutoTopUpConfig(response);
|
||||
@@ -55,16 +67,18 @@ export default function useCredits(): {
|
||||
|
||||
const requestTopUp = useCallback(
|
||||
async (credit_amount: number) => {
|
||||
const stripe = await stripePromise;
|
||||
|
||||
if (!stripe) {
|
||||
console.error(
|
||||
"Trying to top-up failed because Stripe is not loaded." +
|
||||
"Did you set NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY?",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await api.requestTopUp(credit_amount);
|
||||
router.push(response.checkout_url);
|
||||
},
|
||||
[api, router],
|
||||
[api, router, stripe],
|
||||
);
|
||||
|
||||
const [transactionHistory, setTransactionHistory] =
|
||||
@@ -89,6 +103,20 @@ export default function useCredits(): {
|
||||
|
||||
useEffect(() => {
|
||||
fetchTransactionHistory();
|
||||
// Note: We only need to fetch transaction history once.
|
||||
// Hence, we should avoid `fetchTransactionHistory` to the dependency array.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const formatCredits = useCallback((credit: number | null) => {
|
||||
if (credit === null) {
|
||||
return "-";
|
||||
}
|
||||
const value = Math.abs(credit);
|
||||
const sign = credit < 0 ? "-" : "";
|
||||
const precision =
|
||||
2 - (value % 100 === 0 ? 1 : 0) - (value % 10 === 0 ? 1 : 0);
|
||||
return `${sign}$${(value / 100).toFixed(precision)}`;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
@@ -100,5 +128,6 @@ export default function useCredits(): {
|
||||
updateAutoTopUpConfig,
|
||||
transactionHistory,
|
||||
fetchTransactionHistory,
|
||||
formatCredits,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
GraphExecution,
|
||||
Graph,
|
||||
GraphCreatable,
|
||||
GraphExecuteResponse,
|
||||
GraphMeta,
|
||||
GraphUpdateable,
|
||||
MyAgentsResponse,
|
||||
@@ -191,9 +190,10 @@ export default class BackendAPI {
|
||||
|
||||
executeGraph(
|
||||
id: string,
|
||||
version: number,
|
||||
inputData: { [key: string]: any } = {},
|
||||
): Promise<GraphExecuteResponse> {
|
||||
return this._request("POST", `/graphs/${id}/execute`, inputData);
|
||||
): Promise<{ graph_exec_id: string }> {
|
||||
return this._request("POST", `/graphs/${id}/execute/${version}`, inputData);
|
||||
}
|
||||
|
||||
async getGraphExecutionInfo(
|
||||
@@ -816,8 +816,11 @@ export default class BackendAPI {
|
||||
return () => this.wsMessageHandlers[method].delete(handler);
|
||||
}
|
||||
|
||||
subscribeToExecution(graphId: string) {
|
||||
this.sendWebSocketMessage("subscribe", { graph_id: graphId });
|
||||
subscribeToExecution(graphId: string, graphVersion: number) {
|
||||
this.sendWebSocketMessage("subscribe", {
|
||||
graph_id: graphId,
|
||||
graph_version: graphVersion,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,7 +831,7 @@ type GraphCreateRequestBody = {
|
||||
};
|
||||
|
||||
type WebsocketMessageTypeMap = {
|
||||
subscribe: { graph_id: string };
|
||||
subscribe: { graph_id: string; graph_version: number };
|
||||
execution_event: NodeExecutionResult;
|
||||
heartbeat: "ping" | "pong";
|
||||
};
|
||||
|
||||
@@ -135,6 +135,7 @@ export const PROVIDER_NAMES = {
|
||||
OPEN_ROUTER: "open_router",
|
||||
PINECONE: "pinecone",
|
||||
SLANT3D: "slant3d",
|
||||
SCREENSHOTONE: "screenshotone",
|
||||
SMTP: "smtp",
|
||||
TWITTER: "twitter",
|
||||
REPLICATE: "replicate",
|
||||
@@ -256,14 +257,6 @@ export type GraphUpdateable = Omit<
|
||||
|
||||
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string };
|
||||
|
||||
/* Derived from backend/executor/manager.py:ExecutionManager.add_execution */
|
||||
export type GraphExecuteResponse = {
|
||||
/** ID of the initiated run */
|
||||
id: string;
|
||||
/** List of node executions */
|
||||
executions: Array<{ id: string; node_id: string }>;
|
||||
};
|
||||
|
||||
/* Mirror of backend/data/execution.py:ExecutionResult */
|
||||
export type NodeExecutionResult = {
|
||||
graph_id: string;
|
||||
@@ -519,6 +512,7 @@ export type Schedule = {
|
||||
export type ScheduleCreatable = {
|
||||
cron: string;
|
||||
graph_id: string;
|
||||
graph_version: number;
|
||||
input_data: { [key: string]: any };
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Graph, Block, Node, BlockUIType } from "./types";
|
||||
import { Connection } from "@xyflow/react";
|
||||
import { Graph, Block, Node, BlockUIType, Link } from "./types";
|
||||
|
||||
/** Creates a copy of the graph with all secrets removed */
|
||||
export function safeCopyGraph(graph: Graph, block_defs: Block[]): Graph {
|
||||
@@ -44,3 +45,11 @@ export function removeAgentInputBlockValues(graph: Graph, blocks: Block[]) {
|
||||
nodes: modifiedNodes,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatEdgeID(conn: Link | Connection): string {
|
||||
if ("sink_id" in conn) {
|
||||
return `${conn.source_id}_${conn.source_name}_${conn.sink_id}_${conn.sink_name}`;
|
||||
} else {
|
||||
return `${conn.source}_${conn.sourceHandle}_${conn.target}_${conn.targetHandle}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ test.describe("Profile", () => {
|
||||
|
||||
// Verify email matches test worker's email
|
||||
const displayedHandle = await profilePage.getDisplayedName();
|
||||
test.expect(displayedHandle).toBe("No Profile Data");
|
||||
test.expect(displayedHandle).not.toBeNull();
|
||||
test.expect(displayedHandle).not.toBe("");
|
||||
test.expect(displayedHandle).toBeDefined();
|
||||
});
|
||||
|
||||
test("profile navigation is accessible from navbar", async ({ page }) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user