mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Compare commits
276 Commits
bently/ope
...
aarushikan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aff0cd768 | ||
|
|
0afbe26df2 | ||
|
|
a98677b79d | ||
|
|
87e2a8efc1 | ||
|
|
0d050c8b12 | ||
|
|
ddbacf35e4 | ||
|
|
056eb46c0f | ||
|
|
6fde030c37 | ||
|
|
bf1e01d423 | ||
|
|
52c731abd6 | ||
|
|
1e3208a3c6 | ||
|
|
1b14a97fa1 | ||
|
|
b770a7b044 | ||
|
|
e608491b70 | ||
|
|
cc8b932bb4 | ||
|
|
b8b6e1ccb2 | ||
|
|
c8fbce643e | ||
|
|
6c001bd595 | ||
|
|
f5b89672f8 | ||
|
|
76480ffa03 | ||
|
|
ab60a57379 | ||
|
|
1d9b01fc77 | ||
|
|
e81d9f9f0b | ||
|
|
e604264f82 | ||
|
|
ae95bad6fb | ||
|
|
612ceeea62 | ||
|
|
0b3ca7bad5 | ||
|
|
8cb3dd712d | ||
|
|
212b04c1f4 | ||
|
|
e27f5bb2e5 | ||
|
|
7154f80927 | ||
|
|
943e3241c9 | ||
|
|
0d5d0270ea | ||
|
|
f55a7c2475 | ||
|
|
bd25f9223c | ||
|
|
07305b55ff | ||
|
|
cdfe3e5fbc | ||
|
|
e992cdf8c2 | ||
|
|
ebd2ecd84c | ||
|
|
0b919522ae | ||
|
|
2bcf373f0f | ||
|
|
ef691359b7 | ||
|
|
f8815c3053 | ||
|
|
a60ed21404 | ||
|
|
2618d1d87c | ||
|
|
e17ea22a0a | ||
|
|
60669903a0 | ||
|
|
b1b31390a4 | ||
|
|
3c12a398ae | ||
|
|
126d070396 | ||
|
|
090f22b05c | ||
|
|
1b9adf5434 | ||
|
|
3bd8040d6a | ||
|
|
b12dba13f4 | ||
|
|
2cae9ba8da | ||
|
|
3753906482 | ||
|
|
fd54ad8666 | ||
|
|
e645cc4b33 | ||
|
|
010a8ffaaf | ||
|
|
2df325d033 | ||
|
|
79ebc4c13b | ||
|
|
e5eb42d84a | ||
|
|
d62b940baf | ||
|
|
8fd22bcfd7 | ||
|
|
11827835a0 | ||
|
|
70fab8711a | ||
|
|
8ec015ba72 | ||
|
|
bc7d2f0f37 | ||
|
|
54694709bb | ||
|
|
b4b5a09b6b | ||
|
|
82239dd129 | ||
|
|
078ad29356 | ||
|
|
5000aa7ee0 | ||
|
|
dc1077f893 | ||
|
|
80df44a978 | ||
|
|
c2a79d2f10 | ||
|
|
7db85a8990 | ||
|
|
0454a9a7be | ||
|
|
09951fed4b | ||
|
|
6204d82d84 | ||
|
|
8c9fe5c167 | ||
|
|
71de1a6a5e | ||
|
|
956165adf3 | ||
|
|
e4dc16a867 | ||
|
|
cfa0b6610c | ||
|
|
933baa0e8d | ||
|
|
370b2dabe8 | ||
|
|
baa00a5b03 | ||
|
|
60a8e00578 | ||
|
|
85e7d678ce | ||
|
|
476b307d69 | ||
|
|
5dbfb4e3f1 | ||
|
|
f6d09c74f5 | ||
|
|
6d17e627e8 | ||
|
|
5cfa807f00 | ||
|
|
6fff06f0f6 | ||
|
|
cbe553a547 | ||
|
|
96ef35536c | ||
|
|
087d3a3760 | ||
|
|
5da58aa284 | ||
|
|
7de12a2200 | ||
|
|
8f1c63a7ea | ||
|
|
6ec200f912 | ||
|
|
b5db7f575e | ||
|
|
98c909f99f | ||
|
|
c5615aa862 | ||
|
|
e725305e15 | ||
|
|
9551f54c35 | ||
|
|
777f7d25bf | ||
|
|
ea6f37bf98 | ||
|
|
299530cf95 | ||
|
|
1df7d527dd | ||
|
|
407cf858e7 | ||
|
|
a670b384f6 | ||
|
|
f9b8b0a41a | ||
|
|
e59e138352 | ||
|
|
a95ee693dd | ||
|
|
26f56114d1 | ||
|
|
45ace8ccab | ||
|
|
95af63b5ad | ||
|
|
012bad72e8 | ||
|
|
efcd0f93ed | ||
|
|
4c32b46d40 | ||
|
|
41fbfe35fb | ||
|
|
c719e4f177 | ||
|
|
3d62cec553 | ||
|
|
fa12564954 | ||
|
|
f6d8e597e1 | ||
|
|
a1cbc101a5 | ||
|
|
afc8338145 | ||
|
|
7fe4e455fd | ||
|
|
52d40d0f8b | ||
|
|
9e35f8c5cb | ||
|
|
c0afb133a7 | ||
|
|
526364297c | ||
|
|
aed067e61c | ||
|
|
653eb4964f | ||
|
|
406206f5d0 | ||
|
|
1e05d6a8e9 | ||
|
|
848637bfeb | ||
|
|
cea81bfe4e | ||
|
|
1e92c284d9 | ||
|
|
98c1cb8ff9 | ||
|
|
58dc8296db | ||
|
|
4782f4383c | ||
|
|
2b60a392fb | ||
|
|
f30b2cdf25 | ||
|
|
9084c31662 | ||
|
|
183c72b2d0 | ||
|
|
55e100ee1e | ||
|
|
82c5cd2d79 | ||
|
|
f0ab795248 | ||
|
|
5b9caa4345 | ||
|
|
1e054064f6 | ||
|
|
646d98470f | ||
|
|
5a68be5419 | ||
|
|
2ff8a0743a | ||
|
|
582571631e | ||
|
|
bf10df612e | ||
|
|
c577d04692 | ||
|
|
85d895ef77 | ||
|
|
deacc2bd8f | ||
|
|
3eb0d73461 | ||
|
|
be0f6498ed | ||
|
|
0bab2714e9 | ||
|
|
9c74d76a3a | ||
|
|
78e96f8a1a | ||
|
|
904b444b13 | ||
|
|
3cad0f89ee | ||
|
|
8131fc385b | ||
|
|
335fea8605 | ||
|
|
55d32f0324 | ||
|
|
56ce7ac628 | ||
|
|
81adf84032 | ||
|
|
f8d07a27af | ||
|
|
1bad26657c | ||
|
|
31dbb543a2 | ||
|
|
60d25135e6 | ||
|
|
4678ed2e57 | ||
|
|
98a07f1265 | ||
|
|
5e8ff5e3ed | ||
|
|
89adcefd63 | ||
|
|
d82e577196 | ||
|
|
e6cc8687a5 | ||
|
|
fbad0d01ee | ||
|
|
fe5c1968bc | ||
|
|
951abf6d5b | ||
|
|
9ae6389c6c | ||
|
|
4cf1dd30f1 | ||
|
|
c7fdfa0f77 | ||
|
|
6fa7d22c91 | ||
|
|
52bd033a02 | ||
|
|
bb5baadeb2 | ||
|
|
db97b24518 | ||
|
|
533d7b7da8 | ||
|
|
183c2a4845 | ||
|
|
6440a8e217 | ||
|
|
e0930ba39d | ||
|
|
a21fd30fce | ||
|
|
e2df2cd90d | ||
|
|
6bdb849150 | ||
|
|
8469fafc6f | ||
|
|
3c2c3e57a0 | ||
|
|
ec6bae0467 | ||
|
|
f5fe96260e | ||
|
|
49a18437ac | ||
|
|
3cee893314 | ||
|
|
5d1035aeb0 | ||
|
|
2e2c6fed52 | ||
|
|
ca9c52f76a | ||
|
|
973822d973 | ||
|
|
e773329391 | ||
|
|
c9d41e69bd | ||
|
|
08905d71f9 | ||
|
|
8becde370c | ||
|
|
dccc33152b | ||
|
|
b23bd9c479 | ||
|
|
ac45b7cae9 | ||
|
|
3d54a9103c | ||
|
|
ca7182403b | ||
|
|
53826ab360 | ||
|
|
eac5548023 | ||
|
|
122f544966 | ||
|
|
29ba4c2c73 | ||
|
|
76feead3b1 | ||
|
|
081df805df | ||
|
|
acc1d79146 | ||
|
|
07811b2133 | ||
|
|
01b6c2d4bf | ||
|
|
905b1df218 | ||
|
|
edf84fb9f8 | ||
|
|
b62c24dc77 | ||
|
|
dfa855f533 | ||
|
|
da2111bafb | ||
|
|
b2dba39810 | ||
|
|
d2a5bb286f | ||
|
|
36b9a0a930 | ||
|
|
f40db85b43 | ||
|
|
0767b17779 | ||
|
|
0f0c13bae8 | ||
|
|
3b0cd9518d | ||
|
|
22b6dbbf6a | ||
|
|
d9a1a1edc8 | ||
|
|
3c0d37d5d1 | ||
|
|
c98061bc3b | ||
|
|
a8c0cbef54 | ||
|
|
8ccd14c4bf | ||
|
|
3e384c9771 | ||
|
|
22f2a05f08 | ||
|
|
e94a7b08c9 | ||
|
|
7b8928f49b | ||
|
|
699087e289 | ||
|
|
aa8ca37f86 | ||
|
|
8bdb48cba4 | ||
|
|
03ea51b266 | ||
|
|
77034f2df0 | ||
|
|
ccf4397883 | ||
|
|
e7885f943b | ||
|
|
aca7165694 | ||
|
|
22b223037e | ||
|
|
6747ae1559 | ||
|
|
39afba6da8 | ||
|
|
a00df25092 | ||
|
|
ea698ab0fe | ||
|
|
902d2a8924 | ||
|
|
ab0df04bfe | ||
|
|
d407fd101e | ||
|
|
a911f9a5eb | ||
|
|
470c738732 | ||
|
|
7de49dfbe5 | ||
|
|
a02b017cea | ||
|
|
56b82369b6 | ||
|
|
a7926584ca | ||
|
|
6ffa644fb6 | ||
|
|
a82317e2ac | ||
|
|
fd000a4173 |
@@ -23,6 +23,18 @@
|
||||
# Frontend
|
||||
!frontend/build/web/
|
||||
|
||||
# rnd
|
||||
!rnd/
|
||||
|
||||
# Explicitly re-ignore some folders
|
||||
.*
|
||||
**/__pycache__
|
||||
# rnd
|
||||
rnd/autogpt_builder/.next/
|
||||
rnd/autogpt_builder/node_modules
|
||||
rnd/autogpt_builder/.env.example
|
||||
rnd/autogpt_builder/.env.local
|
||||
rnd/autogpt_server/.env
|
||||
rnd/autogpt_server/.venv/
|
||||
|
||||
rnd/market/.env
|
||||
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -6,3 +6,5 @@ docs/_javascript/** linguist-vendored
|
||||
|
||||
# Exclude VCR cassettes from stats
|
||||
forge/tests/vcr_cassettes/**/**.y*ml linguist-generated
|
||||
|
||||
* text=auto
|
||||
12
.github/CODEOWNERS
vendored
12
.github/CODEOWNERS
vendored
@@ -1,5 +1,7 @@
|
||||
.github/workflows/ @Significant-Gravitas/devops
|
||||
autogpt/ @Significant-Gravitas/maintainers
|
||||
forge/ @Significant-Gravitas/forge-maintainers
|
||||
benchmark/ @Significant-Gravitas/benchmark-maintainers
|
||||
frontend/ @Significant-Gravitas/frontend-maintainers
|
||||
* @Significant-Gravitas/maintainers
|
||||
.github/workflows/ @Significant-Gravitas/devops
|
||||
forge/ @Significant-Gravitas/forge-maintainers
|
||||
benchmark/ @Significant-Gravitas/benchmark-maintainers
|
||||
frontend/ @Significant-Gravitas/frontend-maintainers
|
||||
rnd/infra @Significant-Gravitas/devops
|
||||
.github/CODEOWNERS @Significant-Gravitas/admins
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/1.bug.yml
vendored
17
.github/ISSUE_TEMPLATE/1.bug.yml
vendored
@@ -88,14 +88,16 @@ body:
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Do you use OpenAI GPT-3 or GPT-4?
|
||||
label: What LLM Provider do you use?
|
||||
description: >
|
||||
If you are using AutoGPT with `SMART_LLM=gpt-3.5-turbo`, your problems may be caused by
|
||||
the [limitations](https://github.com/Significant-Gravitas/AutoGPT/issues?q=is%3Aissue+label%3A%22AI+model+limitation%22) of GPT-3.5.
|
||||
options:
|
||||
- GPT-3.5
|
||||
- GPT-4
|
||||
- GPT-4(32k)
|
||||
- Azure
|
||||
- Groq
|
||||
- Anthropic
|
||||
- Llamafile
|
||||
- Other (detail in issue)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -126,6 +128,13 @@ body:
|
||||
label: Specify the area
|
||||
description: Please specify the area you think is best related to the issue.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: What commit or version are you using?
|
||||
description: It is helpful for us to reproduce to know what version of the software you were using when this happened. Please run `git log -n 1 --pretty=format:"%H"` to output the full commit hash.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe your issue.
|
||||
|
||||
5
.github/workflows/autogpt-builder-ci.yml
vendored
5
.github/workflows/autogpt-builder-ci.yml
vendored
@@ -17,6 +17,7 @@ defaults:
|
||||
working-directory: rnd/autogpt_builder
|
||||
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -31,6 +32,10 @@ jobs:
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Check formatting with Prettier
|
||||
run: |
|
||||
npx prettier --check .
|
||||
|
||||
- name: Run lint
|
||||
run: |
|
||||
npm run lint
|
||||
|
||||
56
.github/workflows/autogpt-infra-ci.yml
vendored
Normal file
56
.github/workflows/autogpt-infra-ci.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: AutoGPT Builder Infra
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '.github/workflows/autogpt-infra-ci.yml'
|
||||
- 'rnd/infra/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/autogpt-infra-ci.yml'
|
||||
- 'rnd/infra/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: rnd/infra
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: TFLint
|
||||
uses: pauloconnor/tflint-action@v0.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tflint_path: terraform/
|
||||
tflint_recurse: true
|
||||
tflint_changed_only: false
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
with:
|
||||
version: v3.14.4
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.6.0
|
||||
|
||||
- name: Run chart-testing (list-changed)
|
||||
id: list-changed
|
||||
run: |
|
||||
changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }})
|
||||
if [[ -n "$changed" ]]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
if: steps.list-changed.outputs.changed == 'true'
|
||||
run: ct lint --target-branch ${{ github.event.repository.default_branch }}
|
||||
183
.github/workflows/autogpt-server-ci.yml
vendored
183
.github/workflows/autogpt-server-ci.yml
vendored
@@ -34,6 +34,15 @@ jobs:
|
||||
runs-on: ${{ matrix.platform-os != 'macos-arm64' && format('{0}-latest', matrix.platform-os) || 'macos-14' }}
|
||||
|
||||
steps:
|
||||
- name: Setup PostgreSQL
|
||||
uses: ikalnytskyi/action-setup-postgres@v6
|
||||
with:
|
||||
username: ${{ secrets.DB_USER || 'postgres' }}
|
||||
password: ${{ secrets.DB_PASS || 'postgres' }}
|
||||
database: postgres
|
||||
port: 5432
|
||||
id: postgres
|
||||
|
||||
# Quite slow on macOS (2~4 minutes to set up Docker)
|
||||
# - name: Set up Docker (macOS)
|
||||
# if: runner.os == 'macOS'
|
||||
@@ -110,167 +119,37 @@ jobs:
|
||||
|
||||
- name: Run Database Migrations
|
||||
run: poetry run prisma migrate dev --name updates
|
||||
env:
|
||||
CONNECTION_STR: ${{ steps.postgres.outputs.connection-uri }}
|
||||
|
||||
- name: Run Linter
|
||||
- id: lint
|
||||
name: Run Linter
|
||||
run: poetry run lint
|
||||
|
||||
- name: Run pytest with coverage
|
||||
run: |
|
||||
poetry run pytest -vv \
|
||||
test
|
||||
if [[ "${{ runner.debug }}" == "1" ]]; then
|
||||
poetry run pytest -vv -o log_cli=true -o log_cli_level=DEBUG test
|
||||
else
|
||||
poetry run pytest -vv test
|
||||
fi
|
||||
if: success() || (failure() && steps.lint.outcome == 'failure')
|
||||
env:
|
||||
CI: true
|
||||
PLAIN_OUTPUT: True
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
LOG_LEVEL: ${{ runner.debug && 'DEBUG' || 'INFO' }}
|
||||
env:
|
||||
CI: true
|
||||
PLAIN_OUTPUT: True
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
DB_USER: ${{ secrets.DB_USER || 'postgres' }}
|
||||
DB_PASS: ${{ secrets.DB_PASS || 'postgres' }}
|
||||
DB_NAME: postgres
|
||||
DB_PORT: 5432
|
||||
RUN_ENV: local
|
||||
PORT: 8080
|
||||
DATABASE_URL: postgresql://${{ secrets.DB_USER || 'postgres' }}:${{ secrets.DB_PASS || 'postgres' }}@localhost:5432/${{ secrets.DB_NAME || 'postgres'}}
|
||||
|
||||
# - name: Upload coverage reports to Codecov
|
||||
# uses: codecov/codecov-action@v4
|
||||
# with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
# flags: autogpt-server,${{ runner.os }}
|
||||
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10"]
|
||||
platform-os: [ubuntu, macos, macos-arm64, windows]
|
||||
runs-on: ${{ matrix.platform-os != 'macos-arm64' && format('{0}-latest', matrix.platform-os) || 'macos-14' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- id: get_date
|
||||
name: Get date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Python dependency cache
|
||||
# On Windows, unpacking cached dependencies takes longer than just installing them
|
||||
if: runner.os != 'Windows'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ runner.os == 'macOS' && '~/Library/Caches/pypoetry' || '~/.cache/pypoetry' }}
|
||||
key: poetry-${{ runner.os }}-${{ hashFiles('rnd/autogpt_server/poetry.lock') }}
|
||||
|
||||
- name: Install Poetry (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
if [ "${{ runner.os }}" = "macOS" ]; then
|
||||
PATH="$HOME/.local/bin:$PATH"
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
fi
|
||||
|
||||
- name: Install Poetry (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -
|
||||
|
||||
$env:PATH += ";$env:APPDATA\Python\Scripts"
|
||||
echo "$env:APPDATA\Python\Scripts" >> $env:GITHUB_PATH
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: poetry install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: poetry run prisma generate
|
||||
|
||||
- name: Run Database Migrations
|
||||
run: poetry run prisma migrate dev --name updates
|
||||
|
||||
- name: install rpm
|
||||
if: matrix.platform-os == 'ubuntu'
|
||||
run: sudo apt-get install -y alien fakeroot rpm
|
||||
|
||||
- name: Build distribution
|
||||
run: |
|
||||
case "${{ matrix.platform-os }}" in
|
||||
"macos" | "macos-arm64")
|
||||
${MAC_COMMAND}
|
||||
;;
|
||||
"windows")
|
||||
${WINDOWS_COMMAND}
|
||||
;;
|
||||
*)
|
||||
${LINUX_COMMAND}
|
||||
;;
|
||||
esac
|
||||
env:
|
||||
MAC_COMMAND: "poetry run poe dist_dmg"
|
||||
WINDOWS_COMMAND: "poetry run poe dist_msi"
|
||||
LINUX_COMMAND: "poetry run poe dist_appimage"
|
||||
|
||||
- name: Zip the .app directory
|
||||
if: runner.os == 'macOS'
|
||||
working-directory: ${{ runner.temp }}
|
||||
run: |
|
||||
zip -r autogptserver-app-${{ matrix.platform-os }}.app.zip /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/build/*.app
|
||||
|
||||
# break this into seperate steps each with their own name that matches the file
|
||||
- name: Upload App artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-app-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/_temp/autogptserver-app-${{ matrix.platform-os }}.app.zip
|
||||
|
||||
- name: Upload dmg artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-dmg-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/build/AutoGPTServer.dmg
|
||||
|
||||
- name: Upload msi artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-msi-${{ matrix.platform-os }}
|
||||
path: D:\a\AutoGPT\AutoGPT\rnd\autogpt_server\dist\*.msi
|
||||
|
||||
- name: Upload deb artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-deb-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/build/*.deb
|
||||
|
||||
- name: Upload rpm artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-rpm-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/build/*.rpm
|
||||
|
||||
- name: Upload tar.gz artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-tar.gz-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/build/*.tar.gz
|
||||
|
||||
- name: Upload zip artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-zip-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/build/*.zip
|
||||
|
||||
- name: Upload pkg artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-pkg-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/build/*.pkg
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogptserver-AppImage-${{ matrix.platform-os }}
|
||||
path: /Users/runner/work/AutoGPT/AutoGPT/rnd/autogpt_server/dist/*.AppImage
|
||||
|
||||
31
.github/workflows/workflow-checker.yml
vendored
Normal file
31
.github/workflows/workflow-checker.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: PR Status Checker
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
status-check:
|
||||
name: Check PR Status
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# - name: Wait some time for all actions to start
|
||||
# run: sleep 30
|
||||
- uses: actions/checkout@v4
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
- name: Check PR Status
|
||||
run: |
|
||||
echo "Current directory before running Python script:"
|
||||
pwd
|
||||
echo "Attempting to run Python script:"
|
||||
python check_actions_status.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
16
.vscode/all-projects.code-workspace
vendored
16
.vscode/all-projects.code-workspace
vendored
@@ -28,12 +28,26 @@
|
||||
"name": "autogpt_builder",
|
||||
"path": "../rnd/autogpt_builder"
|
||||
},
|
||||
{
|
||||
"name": "market",
|
||||
"path": "../rnd/market"
|
||||
},
|
||||
{
|
||||
"name": "lib",
|
||||
"path": "../rnd/autogpt_libs"
|
||||
},
|
||||
{
|
||||
"name": "infra",
|
||||
"path": "../rnd/infra"
|
||||
},
|
||||
{
|
||||
"name": "[root]",
|
||||
"path": ".."
|
||||
}
|
||||
],
|
||||
"settings": {},
|
||||
"settings": {
|
||||
"python.analysis.typeCheckingMode": "basic"
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"charliermarsh.ruff",
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
## HUGGINGFACE_API_TOKEN - HuggingFace API token (Default: None)
|
||||
# HUGGINGFACE_API_TOKEN=
|
||||
|
||||
|
||||
### Stable Diffusion (IMAGE_PROVIDER=sdwebui)
|
||||
|
||||
## SD_WEBUI_AUTH - Stable Diffusion Web UI username:password pair (Default: None)
|
||||
|
||||
3
autogpt/.vscode/settings.json
vendored
Normal file
3
autogpt/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
}
|
||||
@@ -51,8 +51,9 @@ async def apply_overrides_to_config(
|
||||
raise click.UsageError("--continuous-limit can only be used with --continuous")
|
||||
|
||||
# Check availability of configured LLMs; fallback to other LLM if unavailable
|
||||
config.fast_llm = await check_model(config.fast_llm, "fast_llm")
|
||||
config.smart_llm = await check_model(config.smart_llm, "smart_llm")
|
||||
config.fast_llm, config.smart_llm = await check_models(
|
||||
(config.fast_llm, "fast_llm"), (config.smart_llm, "smart_llm")
|
||||
)
|
||||
|
||||
if skip_reprompt:
|
||||
config.skip_reprompt = True
|
||||
@@ -61,17 +62,22 @@ async def apply_overrides_to_config(
|
||||
config.skip_news = True
|
||||
|
||||
|
||||
async def check_model(
|
||||
model_name: ModelName, model_type: Literal["smart_llm", "fast_llm"]
|
||||
) -> ModelName:
|
||||
async def check_models(
|
||||
*models: tuple[ModelName, Literal["smart_llm", "fast_llm"]]
|
||||
) -> tuple[ModelName, ...]:
|
||||
"""Check if model is available for use. If not, return gpt-3.5-turbo."""
|
||||
multi_provider = MultiProvider()
|
||||
models = await multi_provider.get_available_chat_models()
|
||||
available_models = await multi_provider.get_available_chat_models()
|
||||
|
||||
if any(model_name == m.name for m in models):
|
||||
return model_name
|
||||
checked_models: list[ModelName] = []
|
||||
for model, model_type in models:
|
||||
if any(model == m.name for m in available_models):
|
||||
checked_models.append(model)
|
||||
else:
|
||||
logger.warning(
|
||||
f"You don't have access to {model}. "
|
||||
f"Setting {model_type} to {GPT_3_MODEL}."
|
||||
)
|
||||
checked_models.append(GPT_3_MODEL)
|
||||
|
||||
logger.warning(
|
||||
f"You don't have access to {model_name}. Setting {model_type} to {GPT_3_MODEL}."
|
||||
)
|
||||
return GPT_3_MODEL
|
||||
return tuple(checked_models)
|
||||
|
||||
2
benchmark/.vscode/settings.json
vendored
2
benchmark/.vscode/settings.json
vendored
@@ -2,5 +2,5 @@
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"python.formatting.provider": "none"
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
}
|
||||
|
||||
109
check_actions_status.py
Normal file
109
check_actions_status.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import sys
|
||||
import time
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
def get_environment_variables() -> Tuple[str, str, str, str, str]:
|
||||
"""Retrieve and return necessary environment variables."""
|
||||
try:
|
||||
with open(os.environ["GITHUB_EVENT_PATH"]) as f:
|
||||
event = json.load(f)
|
||||
|
||||
sha = event["pull_request"]["head"]["sha"]
|
||||
|
||||
return (
|
||||
os.environ["GITHUB_API_URL"],
|
||||
os.environ["GITHUB_REPOSITORY"],
|
||||
sha,
|
||||
os.environ["GITHUB_TOKEN"],
|
||||
os.environ["GITHUB_RUN_ID"],
|
||||
)
|
||||
except KeyError as e:
|
||||
print(f"Error: Missing required environment variable or event data: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def make_api_request(url: str, headers: Dict[str, str]) -> Dict:
|
||||
"""Make an API request and return the JSON response."""
|
||||
try:
|
||||
print("Making API request to:", url)
|
||||
response = requests.get(url, headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.RequestException as e:
|
||||
print(f"Error: API request failed. {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def process_check_runs(check_runs: List[Dict]) -> Tuple[bool, bool]:
|
||||
"""Process check runs and return their status."""
|
||||
runs_in_progress = False
|
||||
all_others_passed = True
|
||||
|
||||
for run in check_runs:
|
||||
if str(run["name"]) != "Check PR Status":
|
||||
status = run["status"]
|
||||
conclusion = run["conclusion"]
|
||||
|
||||
if status == "completed":
|
||||
if conclusion not in ["success", "skipped", "neutral"]:
|
||||
all_others_passed = False
|
||||
print(
|
||||
f"Check run {run['name']} (ID: {run['id']}) has conclusion: {conclusion}"
|
||||
)
|
||||
else:
|
||||
runs_in_progress = True
|
||||
print(f"Check run {run['name']} (ID: {run['id']}) is still {status}.")
|
||||
all_others_passed = False
|
||||
else:
|
||||
print(
|
||||
f"Skipping check run {run['name']} (ID: {run['id']}) as it is the current run."
|
||||
)
|
||||
|
||||
return runs_in_progress, all_others_passed
|
||||
|
||||
|
||||
def main():
|
||||
api_url, repo, sha, github_token, current_run_id = get_environment_variables()
|
||||
|
||||
endpoint = f"{api_url}/repos/{repo}/commits/{sha}/check-runs"
|
||||
headers = {
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
if github_token:
|
||||
headers["Authorization"] = f"token {github_token}"
|
||||
|
||||
print(f"Current run ID: {current_run_id}")
|
||||
|
||||
while True:
|
||||
data = make_api_request(endpoint, headers)
|
||||
|
||||
check_runs = data["check_runs"]
|
||||
|
||||
print("Processing check runs...")
|
||||
|
||||
print(check_runs)
|
||||
|
||||
runs_in_progress, all_others_passed = process_check_runs(check_runs)
|
||||
|
||||
if not runs_in_progress:
|
||||
break
|
||||
|
||||
print(
|
||||
"Some check runs are still in progress. Waiting 3 minutes before checking again..."
|
||||
)
|
||||
time.sleep(180)
|
||||
|
||||
if all_others_passed:
|
||||
print("All other completed check runs have passed. This check passes.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("Some check runs have failed or have not completed. This check fails.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
cli.py
2
cli.py
@@ -69,6 +69,8 @@ d88P 888 "Y88888 "Y888 "Y88P" "Y8888P88 888 888
|
||||
bold=True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
click.echo(click.style("🎉 Setup completed!\n", fg="green"))
|
||||
|
||||
|
||||
@cli.group()
|
||||
|
||||
@@ -69,6 +69,8 @@ Lets the agent execute non-interactive Shell commands and Python code. Python ex
|
||||
| `shell_denylist` | List of prohibited shell commands | `List[str]` | `[]` |
|
||||
| `docker_container_name` | Name of the Docker container used for code execution | `str` | `"agent_sandbox"` |
|
||||
|
||||
All shell command configurations are expected to be for convience only. This component is not secure and should not be used in production environments. It is recommended to use more appropriate sandboxing.
|
||||
|
||||
### CommandProvider
|
||||
|
||||
- `execute_shell` execute shell command
|
||||
@@ -155,11 +157,12 @@ Allows agent to search the web. Google credentials aren't required for DuckDuckG
|
||||
|
||||
### `WebSearchConfiguration`
|
||||
|
||||
| Config variable | Details | Type | Default |
|
||||
| -------------------------------- | ----------------------------------------------------------------------- | ----- | ------- |
|
||||
| `google_api_key` | Google API key, *ENV:* `GOOGLE_API_KEY` | `str` | `None` |
|
||||
| `google_custom_search_engine_id` | Google Custom Search Engine ID, *ENV:* `GOOGLE_CUSTOM_SEARCH_ENGINE_ID` | `str` | `None` |
|
||||
| `duckduckgo_max_attempts` | Maximum number of attempts to search using DuckDuckGo | `int` | `3` |
|
||||
| Config variable | Details | Type | Default |
|
||||
| -------------------------------- | ----------------------------------------------------------------------- | --------------------------- | ------- |
|
||||
| `google_api_key` | Google API key, *ENV:* `GOOGLE_API_KEY` | `str` | `None` |
|
||||
| `google_custom_search_engine_id` | Google Custom Search Engine ID, *ENV:* `GOOGLE_CUSTOM_SEARCH_ENGINE_ID` | `str` | `None` |
|
||||
| `duckduckgo_max_attempts` | Maximum number of attempts to search using DuckDuckGo | `int` | `3` |
|
||||
| `duckduckgo_backend` | Backend to be used for DDG sdk | `"api" \| "html" \| "lite"` | `"api"` |
|
||||
|
||||
### DirectiveProvider
|
||||
|
||||
@@ -183,6 +186,7 @@ Allows agent to read websites using Selenium.
|
||||
| `headless` | Run browser in headless mode | `bool` | `True` |
|
||||
| `user_agent` | User agent used by the browser | `str` | `"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"` |
|
||||
| `browse_spacy_language_model` | Spacy language model used for chunking text | `str` | `"en_core_web_sm"` |
|
||||
| `selenium_proxy` | Http proxy to use with Selenium | `str` | `None` |
|
||||
|
||||
### DirectiveProvider
|
||||
|
||||
|
||||
@@ -4,13 +4,26 @@ Welcome to the AutoGPT Documentation.
|
||||
|
||||
The AutoGPT project consists of four main components:
|
||||
|
||||
* The [Agent](#agent) – also known as just "AutoGPT"
|
||||
* The [Benchmark](#benchmark) – AKA `agbenchmark`
|
||||
* The [Forge](#forge)
|
||||
* The [Frontend](#frontend)
|
||||
- The [Server](#server) – known as the "AutoGPT Platform"
|
||||
- The [Agent](#agent) – also known as just "AutoGPT"
|
||||
- The [Benchmark](#benchmark) – AKA `agbenchmark`
|
||||
- The [Forge](#forge)
|
||||
- The [Frontend](#frontend)
|
||||
|
||||
To tie these together, we also have a [CLI] at the root of the project.
|
||||
|
||||
## 🌐 Server
|
||||
|
||||
<!-- Setup, then Advanced, then New Blocks -->
|
||||
|
||||
**[📖 Setup](server/setup.md)**
|
||||
 | 
|
||||
**[📖 Advanced Setup](server/advanced_setup.md)**
|
||||
 | 
|
||||
**[📖 Making New Blocks](server/new_blocks.md)**
|
||||
|
||||
The server is the backbone of the New AutoGPT project. It provides the infrastructure for the agents to run, and the UI for you to interact with them. It integrates with the Forge, Agent, and a bespoke UI to provide a seamless experience.
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Agent
|
||||
|
||||
69
docs/content/server/advanced_setup.md
Normal file
69
docs/content/server/advanced_setup.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Advanced Setup
|
||||
|
||||
The advanced steps below are intended for people with sysadmin experience. If you are not comfortable with these steps, please refer to the [basic setup guide](setup.md).
|
||||
|
||||
## Introduction
|
||||
|
||||
For the advanced setup, first follow the [basic setup guide](setup.md) to get the server up and running. Once you have the server running, you can follow the steps below to configure the server for your specific needs.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Setting config via environment variables
|
||||
|
||||
The server uses environment variables to store configs. You can set these environment variables in a `.env` file in the root of the project. The `.env` file should look like this:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
KEY1=value1
|
||||
KEY2=value2
|
||||
```
|
||||
|
||||
The server will automatically load the `.env` file when it starts. You can also set the environment variables directly in your shell. Refer to your operating system's documentation on how to set environment variables in the current session.
|
||||
|
||||
The valid options are listed in `.env.example` in the root of the builder and server directories. You can copy the `.env.example` file to `.env` and modify the values as needed.
|
||||
|
||||
```bash
|
||||
# Copy the .env.example file to .env
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### Secrets directory
|
||||
|
||||
The secret directory is located at `./secrets`. You can store any secrets you need in this directory. The server will automatically load the secrets when it starts.
|
||||
|
||||
An example for a secret called `my_secret` would look like this:
|
||||
|
||||
```bash
|
||||
# ./secrets/my_secret
|
||||
my_secret_value
|
||||
```
|
||||
|
||||
This is useful when running on docker so you can copy the secrets into the container without exposing them in the Dockerfile.
|
||||
|
||||
## Database selection
|
||||
|
||||
### SQLite
|
||||
|
||||
By default, the server uses SQLite as the database. SQLite is a file-based database that is easy to set up and use. However, it is not recommended for production usecases where auth is required because that subsystem requires Postgres.
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
For production use, it is recommended to use PostgreSQL as the database. You will swap the commands you use to generate and run prisma to the following
|
||||
|
||||
```bash
|
||||
poetry run prisma generate --schema postgres/schema.prisma
|
||||
```
|
||||
|
||||
This will generate the Prisma client for PostgreSQL. You will also need to run the PostgreSQL database in a separate container. You can use the `docker-compose.yml` file in the `rnd` directory to run the PostgreSQL database.
|
||||
|
||||
```bash
|
||||
cd rnd/
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
You can then run the migrations from the `autogpt_server` directory.
|
||||
|
||||
```bash
|
||||
cd ../autogpt_server
|
||||
prisma migrate dev --schema postgres/schema.prisma
|
||||
```
|
||||
17
docs/content/server/d_id.md
Normal file
17
docs/content/server/d_id.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Find available voices for D-ID
|
||||
|
||||
1. **ElevenLabs**
|
||||
- Select any voice from the voice list: https://api.elevenlabs.io/v1/voices
|
||||
- Copy the voice_id
|
||||
- Use it as a string in the voice_id field in the CreateTalkingAvatarClip Block
|
||||
|
||||
2. **Microsoft Azure Voices**
|
||||
- Select any voice from the voice gallery: https://speech.microsoft.com/portal/voicegallery
|
||||
- Click on the "Sample code" tab on the right
|
||||
- Copy the voice name, for example: config.SpeechSynthesisVoiceName ="en-GB-AbbiNeural"
|
||||
- Use this string en-GB-AbbiNeural in the voice_id field in the CreateTalkingAvatarClip Block
|
||||
|
||||
3. **Amazon Polly Voices**
|
||||
- Select any voice from the voice list: https://docs.aws.amazon.com/polly/latest/dg/available-voices.html
|
||||
- Copy the voice name / ID
|
||||
- Use it as string in the voice_id field in the CreateTalkingAvatarClip Block
|
||||
@@ -52,7 +52,7 @@ Follow these steps to create and test a new block:
|
||||
```python
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
# Unique ID for the block
|
||||
# Unique ID for the block, used across users for templates
|
||||
# you can generate this with this python one liner
|
||||
# print(__import__('uuid').uuid4())
|
||||
id="h5e7f8g9-1b2c-3d4e-5f6g-7h8i9j0k1l2m",
|
||||
@@ -147,3 +147,78 @@ This approach allows us to test the block's logic comprehensively without relyin
|
||||
6. **Update tests when changing block behavior**: If you modify your block, ensure the tests are updated accordingly.
|
||||
|
||||
By following these steps, you can create new blocks that extend the functionality of the AutoGPT Agent Server.
|
||||
|
||||
## Blocks we want to see
|
||||
|
||||
Below is a list of blocks that we would like to see implemented in the AutoGPT Agent Server. If you're interested in contributing, feel free to pick one of these blocks or suggest your own by editing [docs/content/server/new_blocks.md](https://github.com/Significant-Gravitas/AutoGPT/edit/master/docs/content/server/new_blocks.md) and opening a pull request.
|
||||
|
||||
If you would like to implement one of these blocks, open a pull request and we will start the review process.
|
||||
|
||||
### Consumer Services/Platforms
|
||||
|
||||
- Google sheets - Read/Append [Read in Progress](https://github.com/Significant-Gravitas/AutoGPT/pull/7521)
|
||||
- Email - Read/Send with Gmail, Outlook, Yahoo, Proton, etc
|
||||
- Calendar - Read/Write with Google Calendar, Outlook Calendar, etc
|
||||
- Home Assistant - Call Service, Get Status
|
||||
- Dominos - Order Pizza, Track Order
|
||||
- Uber - Book Ride, Track Ride
|
||||
- Notion - Create/Read Page, Create/Append/Read DB
|
||||
- Google drive - read/write/overwrite file/folder
|
||||
|
||||
### Social Media
|
||||
|
||||
- Twitter - Post, Reply, Get Replies, Get Comments, Get Followers, Get Following, Get Tweets, Get Mentions
|
||||
- Instagram - Post, Reply, Get Comments, Get Followers, Get Following, Get Posts, Get Mentions, Get Trending Posts
|
||||
- TikTok - Post, Reply, Get Comments, Get Followers, Get Following, Get Videos, Get Mentions, Get Trending Videos
|
||||
- LinkedIn - Post, Reply, Get Comments, Get Followers, Get Following, Get Posts, Get Mentions, Get Trending Posts
|
||||
- YouTube - Transcribe Videos/Shorts, Post Videos/Shorts, Read/Reply/React to Comments, Update Thumbnails, Update Description, Update Tags, Update Titles, Get Views, Get Likes, Get Dislikes, Get Subscribers, Get Comments, Get Shares, Get Watch Time, Get Revenue, Get Trending Videos, Get Top Videos, Get Top Channels
|
||||
- Reddit - Post, Reply, Get Comments, Get Followers, Get Following, Get Posts, Get Mentions, Get Trending Posts
|
||||
- Treatwell (and related Platforms) - Book, Cancel, Review, Get Recommendations
|
||||
- Substack - Read/Subscribe/Unsubscribe, Post/Reply, Get Recommendations
|
||||
- Discord - Read/Post/Reply, Moderation actions
|
||||
- GoodReads - Read/Post/Reply, Get Recommendations
|
||||
|
||||
### E-commerce
|
||||
|
||||
- Airbnb - Book, Cancel, Review, Get Recommendations
|
||||
- Amazon - Order, Track Order, Return, Review, Get Recommendations
|
||||
- eBay - Order, Track Order, Return, Review, Get Recommendations
|
||||
- Upwork - Post Jobs, Hire Freelancer, Review Freelancer, Fire Freelancer
|
||||
|
||||
### Business Tools
|
||||
|
||||
- External Agents - Call other agents similar to AutoGPT
|
||||
- Trello - Create/Read/Update/Delete Cards, Lists, Boards
|
||||
- Jira - Create/Read/Update/Delete Issues, Projects, Boards
|
||||
- Linear - Create/Read/Update/Delete Issues, Projects, Boards
|
||||
- Excel - Read/Write/Update/Delete Rows, Columns, Sheets
|
||||
- Slack - Read/Post/Reply to Messages, Create Channels, Invite Users
|
||||
- ERPNext - Create/Read/Update/Delete Invoices, Orders, Customers, Products
|
||||
- Salesforce - Create/Read/Update/Delete Leads, Opportunities, Accounts
|
||||
- HubSpot - Create/Read/Update/Delete Contacts, Deals, Companies
|
||||
- Zendesk - Create/Read/Update/Delete Tickets, Users, Organizations
|
||||
- Odoo - Create/Read/Update/Delete Sales Orders, Invoices, Customers
|
||||
- Shopify - Create/Read/Update/Delete Products, Orders, Customers
|
||||
- WooCommerce - Create/Read/Update/Delete Products, Orders, Customers
|
||||
- Squarespace - Create/Read/Update/Delete Pages, Products, Orders
|
||||
|
||||
## Agent Templates we want to see
|
||||
|
||||
|
||||
### Data/Information
|
||||
|
||||
- Summarize top news of today, of this week, this month via Apple News or other large media outlets BBC, TechCrunch, hackernews, etc
|
||||
- Create, read, and summarize substack newsletters or any newsletters (blog writer vs blog reader)
|
||||
- Get/read/summarize the most viral Twitter, Instagram, TikTok (general social media accounts) of the day, week, month
|
||||
- Get/Read any LinkedIn posts or profile that mention AI Agents
|
||||
- Read/Summarize discord (might not be able to do this because you need access)
|
||||
- Read / Get most read books in a given month, year, etc from GoodReads or Amazon Books, etc
|
||||
- Get dates for specific shows across all streaming services
|
||||
- Suggest/Recommend/Get most watched shows in a given month, year, etc across all streaming platforms
|
||||
- Data analysis from xlsx data set
|
||||
- Gather via Excel or Google Sheets data > Sample the data randomly (sample block takes top X, bottom X, randomly, etc) > pass that to LLM Block to generate a script for analysis of the full data > Python block to run the script> making a loop back through LLM Fix Block on error > create chart/visualization (potentially in the code block?) > show the image as output (this may require frontend changes to show)
|
||||
- Tiktok video search and download
|
||||
|
||||
### Marketing
|
||||
|
||||
- Portfolio site design and enhancements
|
||||
|
||||
37
docs/content/server/ollama.md
Normal file
37
docs/content/server/ollama.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Running Ollama with AutoGPT
|
||||
|
||||
Follow these steps to set up and run Ollama and your AutoGPT project:
|
||||
|
||||
1. **Run Ollama**
|
||||
- Open a terminal
|
||||
- Execute the following command:
|
||||
```
|
||||
ollama run llama3
|
||||
```
|
||||
- Leave this terminal running
|
||||
|
||||
2. **Run the Backend**
|
||||
- Open a new terminal
|
||||
- Navigate to the backend directory in the AutoGPT project:
|
||||
```
|
||||
cd rnd/autogpt_server/
|
||||
```
|
||||
- Start the backend using Poetry:
|
||||
```
|
||||
poetry run app
|
||||
```
|
||||
|
||||
3. **Run the Frontend**
|
||||
- Open another terminal
|
||||
- Navigate to the frontend directory in the AutoGPT project:
|
||||
```
|
||||
cd rnd/autogpt_builder/
|
||||
```
|
||||
- Start the frontend development server:
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
4. **Choose the Ollama Model**
|
||||
- Add LLMBlock in the UI
|
||||
- Choose the last option in the model selection dropdown
|
||||
137
docs/content/server/setup.md
Normal file
137
docs/content/server/setup.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Setting up the server
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Prerequisites](#prerequisites)
|
||||
|
||||
## Introduction
|
||||
|
||||
This guide will help you setup the server and builder for the project.
|
||||
|
||||
<!-- The video is listed in the root Readme.md of the repo -->
|
||||
|
||||
We also offer this in video format. You can check it out [here](https://github.com/Significant-Gravitas/AutoGPT#how-to-get-started).
|
||||
|
||||
!!! warning
|
||||
**DO NOT FOLLOW ANY OUTSIDE TUTORIALS AS THEY WILL LIKELY BE OUT OF DATE**
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To setup the server, you need to have the following installed:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/)
|
||||
- [Python 3.10](https://www.python.org/downloads/)
|
||||
|
||||
### Checking if you have Node.js and Python installed
|
||||
|
||||
You can check if you have Node.js installed by running the following command:
|
||||
|
||||
```bash
|
||||
node -v
|
||||
```
|
||||
|
||||
You can check if you have Python installed by running the following command:
|
||||
|
||||
```bash
|
||||
python --version
|
||||
```
|
||||
|
||||
Once you have node and python installed, you can proceed to the next step.
|
||||
|
||||
### Installing the package managers
|
||||
|
||||
In order to install the dependencies, you need to have the appropriate package managers installed.
|
||||
|
||||
- Installing Yarn
|
||||
|
||||
Yarn is a package manager for Node.js. You can install it by running the following command:
|
||||
|
||||
```bash
|
||||
npm install -g yarn
|
||||
```
|
||||
|
||||
- Installing Poetry
|
||||
|
||||
Poetry is a package manager for Python. You can install it by running the following command:
|
||||
|
||||
```bash
|
||||
pip install poetry
|
||||
```
|
||||
- Installing Docker and Docker Compose
|
||||
|
||||
Docker containerizes applications, while Docker Compose orchestrates multi-container Docker applications.
|
||||
|
||||
You can follow the steps here:
|
||||
|
||||
If you need assistance installing docker:
|
||||
https://docs.docker.com/desktop/
|
||||
If you need assistance installing docker compose:
|
||||
https://docs.docker.com/compose/install/
|
||||
|
||||
### Installing the dependencies
|
||||
|
||||
Once you have installed Yarn and Poetry, you can run the following command to install the dependencies:
|
||||
|
||||
```bash
|
||||
cd rnd/autogpt_server
|
||||
cp .env.example .env
|
||||
poetry install
|
||||
```
|
||||
|
||||
**In another terminal**, run the following command to install the dependencies for the frontend:
|
||||
|
||||
```bash
|
||||
cd rnd/autogpt_builder
|
||||
yarn install
|
||||
```
|
||||
|
||||
Once you have installed the dependencies, you can proceed to the next step.
|
||||
|
||||
### Setting up the database
|
||||
|
||||
In order to setup the database, you need to run the following commands, in the same terminal you ran the `poetry install` command:
|
||||
|
||||
```sh
|
||||
docker compose up postgres redis -d
|
||||
poetry run prisma migrate dev
|
||||
```
|
||||
After deploying the migration, to ensure that the database schema is correctly mapped to your codebase, allowing the application to interact with the database properly, you need to generate the Prisma database model:
|
||||
|
||||
```bash
|
||||
poetry run prisma generate
|
||||
```
|
||||
|
||||
Without running this command, the necessary Python modules (prisma.models) won't be available, leading to a `ModuleNotFoundError`.
|
||||
|
||||
### Running the server without Docker
|
||||
|
||||
To run the server, you can run the following commands in the same terminal you ran the `poetry install` command:
|
||||
|
||||
```bash
|
||||
poetry run app
|
||||
```
|
||||
|
||||
### Running the server within Docker
|
||||
|
||||
To run the server, you can run the following commands in the same terminal you ran the `poetry install` command:
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose up
|
||||
```
|
||||
|
||||
In the other terminal from autogpt_builder, you can run the following command to start the frontend:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### Checking if the server is running
|
||||
|
||||
You can check if the server is running by visiting [http://localhost:3000](http://localhost:3000) in your browser.
|
||||
|
||||
### Notes:
|
||||
By default the daemons for different services run on the following ports:
|
||||
|
||||
Execution Manager Daemon: 8002
|
||||
Execution Scheduler Daemon: 8003
|
||||
Rest Server Daemon: 8004
|
||||
@@ -7,6 +7,10 @@ nav:
|
||||
|
||||
- The AutoGPT Server 🆕:
|
||||
- Build your own Blocks: server/new_blocks.md
|
||||
- Setup: server/setup.md
|
||||
- Advanced Setup: server/advanced_setup.md
|
||||
- Using Ollama: server/ollama.md
|
||||
- Using D-ID: serveer/d_id.md
|
||||
|
||||
- AutoGPT Agent:
|
||||
- Introduction: AutoGPT/index.md
|
||||
|
||||
3
forge/.vscode/settings.json
vendored
Normal file
3
forge/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Iterator, Optional
|
||||
from typing import Iterator, Literal, Optional
|
||||
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel, SecretStr
|
||||
@@ -24,6 +24,7 @@ class WebSearchConfiguration(BaseModel):
|
||||
None, from_env="GOOGLE_CUSTOM_SEARCH_ENGINE_ID", exclude=True
|
||||
)
|
||||
duckduckgo_max_attempts: int = 3
|
||||
duckduckgo_backend: Literal["api", "html", "lite"] = "api"
|
||||
|
||||
|
||||
class WebSearchComponent(
|
||||
@@ -89,7 +90,9 @@ class WebSearchComponent(
|
||||
if not query:
|
||||
return json.dumps(search_results)
|
||||
|
||||
search_results = DDGS().text(query, max_results=num_results)
|
||||
search_results = DDGS().text(
|
||||
query, max_results=num_results, backend=self.config.duckduckgo_backend
|
||||
)
|
||||
|
||||
if search_results:
|
||||
break
|
||||
|
||||
@@ -68,6 +68,8 @@ class WebSeleniumConfiguration(BaseModel):
|
||||
"""User agent used by the browser"""
|
||||
browse_spacy_language_model: str = "en_core_web_sm"
|
||||
"""Spacy language model used for chunking text"""
|
||||
selenium_proxy: Optional[str] = None
|
||||
"""Http proxy to use with Selenium"""
|
||||
|
||||
|
||||
class WebSeleniumComponent(
|
||||
@@ -301,6 +303,9 @@ class WebSeleniumComponent(
|
||||
options.add_argument("--headless=new")
|
||||
options.add_argument("--disable-gpu")
|
||||
|
||||
if self.config.selenium_proxy:
|
||||
options.add_argument(f"--proxy-server={self.config.selenium_proxy}")
|
||||
|
||||
self._sideload_chrome_extensions(options, self.data_dir / "assets" / "crx")
|
||||
|
||||
if (chromium_driver_path := Path("/usr/bin/chromedriver")).exists():
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Callable, Iterator, Optional, Sequence, TypeVar, get_args
|
||||
from typing import Any, AsyncIterator, Callable, Optional, Sequence, TypeVar, get_args
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
@@ -68,7 +68,7 @@ class MultiProvider(BaseChatModelProvider[ModelName, ModelProviderSettings]):
|
||||
|
||||
async def get_available_chat_models(self) -> Sequence[ChatModelInfo[ModelName]]:
|
||||
models = []
|
||||
for provider in self.get_available_providers():
|
||||
async for provider in self.get_available_providers():
|
||||
models.extend(await provider.get_available_chat_models())
|
||||
return models
|
||||
|
||||
@@ -120,14 +120,18 @@ class MultiProvider(BaseChatModelProvider[ModelName, ModelProviderSettings]):
|
||||
model_info = CHAT_MODELS[model]
|
||||
return self._get_provider(model_info.provider_name)
|
||||
|
||||
def get_available_providers(self) -> Iterator[ChatModelProvider]:
|
||||
async def get_available_providers(self) -> AsyncIterator[ChatModelProvider]:
|
||||
for provider_name in ModelProviderName:
|
||||
self._logger.debug(f"Checking if {provider_name} is available...")
|
||||
self._logger.debug(f"Checking if provider {provider_name} is available...")
|
||||
try:
|
||||
yield self._get_provider(provider_name)
|
||||
self._logger.debug(f"{provider_name} is available!")
|
||||
provider = self._get_provider(provider_name)
|
||||
await provider.get_available_models() # check connection
|
||||
yield provider
|
||||
self._logger.debug(f"Provider '{provider_name}' is available!")
|
||||
except ValueError:
|
||||
pass
|
||||
except Exception as e:
|
||||
self._logger.debug(f"Provider '{provider_name}' is failing: {e}")
|
||||
|
||||
def _get_provider(self, provider_name: ModelProviderName) -> ChatModelProvider:
|
||||
_provider = self._provider_instances.get(provider_name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## [AutoGPT Forge Part 1: A Comprehensive Guide to Your First Steps](https://aiedge.medium.com/autogpt-forge-a-comprehensive-guide-to-your-first-steps-a1dfdf46e3b4)
|
||||
|
||||

|
||||

|
||||
|
||||
**Written by Craig Swift & [Ryan Brandt](https://github.com/paperMoose)**
|
||||
|
||||
@@ -15,22 +15,22 @@ The Forge serves as a comprehensive template for building your own AutoGPT agent
|
||||
|
||||
To begin, you need to fork the [repository](https://github.com/Significant-Gravitas/AutoGPT) by navigating to the main page of the repository and clicking **Fork** in the top-right corner.
|
||||
|
||||

|
||||

|
||||
|
||||
Follow the on-screen instructions to complete the process.
|
||||
|
||||

|
||||

|
||||
|
||||
### Cloning the Repository
|
||||
Next, clone your newly forked repository to your local system. Ensure you have Git installed to proceed with this step. You can download Git from [here](https://git-scm.com/downloads). Then clone the repo using the following command and the url for your repo. You can find the correct url by clicking on the green Code button on your repos main page.
|
||||

|
||||

|
||||
|
||||
```bash
|
||||
# replace the url with the one for your forked repo
|
||||
git clone https://github.com/<YOUR REPO PATH HERE>
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
### Setting up the Project
|
||||
|
||||
@@ -41,8 +41,8 @@ cd AutoGPT
|
||||
```
|
||||
To set up the project, utilize the `./run setup` command in the terminal. Follow the instructions to install necessary dependencies and set up your GitHub access token.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Section 3: Creating Your Agent
|
||||
|
||||
@@ -55,7 +55,7 @@ Create your agent template using the command:
|
||||
```
|
||||
Replacing YOUR_AGENT_NAME with the name you chose in the previous step.
|
||||
|
||||

|
||||

|
||||
|
||||
## Section 4: Running Your Agent
|
||||
|
||||
@@ -66,13 +66,13 @@ Begin by starting your agent using the command:
|
||||
```
|
||||
This will initiate the agent on `http://localhost:8000/`.
|
||||
|
||||

|
||||

|
||||
|
||||
### Logging in and Sending Tasks to Your Agent
|
||||
Access the frontend at `http://localhost:8000/` and log in using a Google or GitHub account. Once you're logged you'll see the agent tasking interface! However... the agent won't do anything yet. We'll implement the logic for our agent to run tasks in the upcoming tutorial chapters.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### Stopping and Restarting Your Agent
|
||||
When needed, use Ctrl+C to end the session or use the stop command:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
---
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ Large Language Models (LLMs) are state-of-the-art machine learning models that h
|
||||
|
||||
Traditional autonomous agents operated with limited knowledge, often confined to specific tasks or environments. They were like calculators — efficient but limited to predefined functions. LLM-based agents, on the other hand don’t just compute; they understand, reason, and then act, drawing from a vast reservoir of information.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## The Anatomy of an LLM-Based AI Agent
|
||||
|
||||
Diving deep into the core of an LLM-based AI agent, we find it’s structured much like a human, with distinct components akin to personality, memory, thought process, and abilities. Let’s break these down:
|
||||
|
||||

|
||||

|
||||
Anatomy of an Agent from the Agent Landscape Survey
|
||||
|
||||
### **Profile**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AutoGPT Forge: Crafting Intelligent Agent Logic
|
||||
|
||||

|
||||

|
||||
**By Craig Swift & [Ryan Brandt](https://github.com/paperMoose)**
|
||||
|
||||
Hey there! Ready for part 3 of our AutoGPT Forge tutorial series? If you missed the earlier parts, catch up here:
|
||||
@@ -17,7 +17,7 @@ Make sure you've set up your project and created an agent as described in our in
|
||||
|
||||
In the image below, you'll see my "SmartAgent" and the agent.py file inside the 'forge' folder. That's where we'll be adding our LLM-based logic. If you're unsure about the project structure or agent functions from our last guide, don't worry. We'll cover the basics as we go!
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -125,7 +125,7 @@ Now that we've set this up, let's move to the next exciting part: The PromptEngi
|
||||
|
||||
**The Art of Prompting**
|
||||
|
||||

|
||||

|
||||
|
||||
Prompting is like shaping messages for powerful language models like ChatGPT. Since these models respond to input details, creating the right prompt can be a challenge. That's where the **PromptEngine** comes in.
|
||||
|
||||
@@ -479,7 +479,7 @@ d88P 888 "Y88888 "Y888 "Y88P" "Y8888P88 888 888
|
||||
3. **Navigate to Benchmarking**
|
||||
- Look to the left, and you'll spot a trophy icon. Click it to enter the benchmarking arena.
|
||||
|
||||

|
||||

|
||||
|
||||
4. **Select the 'WriteFile' Test**
|
||||
- Choose the 'WriteFile' test from the available options.
|
||||
|
||||
138
rnd/README.md
138
rnd/README.md
@@ -1,36 +1,114 @@
|
||||
This is a guide to setting up and running the AutoGPT Server and Builder. This tutorial will cover downloading the necessary files, setting up the server, and testing the system.
|
||||
# AutoGPT Platform
|
||||
|
||||
https://github.com/user-attachments/assets/fd0d0f35-3155-4263-b575-ba3efb126cb4
|
||||
Welcome to the AutoGPT Platform - a powerful system for creating and running AI agents to solve business problems. This platform enables you to harness the power of artificial intelligence to automate tasks, analyze data, and generate insights for your organization.
|
||||
|
||||
1. Navigate to the AutoGPT GitHub repository.
|
||||
2. Click the "Code" button, then select "Download ZIP".
|
||||
3. Once downloaded, extract the ZIP file to a folder of your choice.
|
||||
## Getting Started
|
||||
|
||||
4. Open the extracted folder and navigate to the "rnd" directory.
|
||||
5. Enter the "AutoGPT server" folder.
|
||||
6. Open a terminal window in this directory.
|
||||
7. Locate and open the README file in the AutoGPT server folder: [doc](./autogpt_server/README.md#setup).
|
||||
8. Copy and paste each command from the setup section in the README into your terminal.
|
||||
- Important: Wait for each command to finish before running the next one.
|
||||
9. If all commands run without errors, enter the final command: `poetry run app`
|
||||
10. You should now see the server running in your terminal.
|
||||
### Prerequisites
|
||||
|
||||
- Docker
|
||||
- Docker Compose V2 (comes with Docker Desktop, or can be installed separately)
|
||||
|
||||
### Running the System
|
||||
|
||||
To run the AutoGPT Platform, follow these steps:
|
||||
|
||||
1. Clone this repository to your local machine.
|
||||
2. Navigate to the project directory.
|
||||
3. Run the following command:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This command will start all the necessary services defined in the `docker-compose.yml` file in detached mode.
|
||||
|
||||
### Docker Compose Commands
|
||||
|
||||
Here are some useful Docker Compose commands for managing your AutoGPT Platform:
|
||||
|
||||
- `docker compose up -d`: Start the services in detached mode.
|
||||
- `docker compose stop`: Stop the running services without removing them.
|
||||
- `docker compose rm`: Remove stopped service containers.
|
||||
- `docker compose build`: Build or rebuild services.
|
||||
- `docker compose down`: Stop and remove containers, networks, and volumes.
|
||||
- `docker compose watch`: Watch for changes in your services and automatically update them.
|
||||
|
||||
|
||||
### Sample Scenarios
|
||||
|
||||
Here are some common scenarios where you might use multiple Docker Compose commands:
|
||||
|
||||
1. Updating and restarting a specific service:
|
||||
```
|
||||
docker compose build api_srv
|
||||
docker compose up -d --no-deps api_srv
|
||||
```
|
||||
This rebuilds the `api_srv` service and restarts it without affecting other services.
|
||||
|
||||
2. Viewing logs for troubleshooting:
|
||||
```
|
||||
docker compose logs -f api_srv ws_srv
|
||||
```
|
||||
This shows and follows the logs for both `api_srv` and `ws_srv` services.
|
||||
|
||||
3. Scaling a service for increased load:
|
||||
```
|
||||
docker compose up -d --scale executor=3
|
||||
```
|
||||
This scales the `executor` service to 3 instances to handle increased load.
|
||||
|
||||
4. Stopping the entire system for maintenance:
|
||||
```
|
||||
docker compose stop
|
||||
docker compose rm -f
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
This stops all services, removes containers, pulls the latest images, and restarts the system.
|
||||
|
||||
5. Developing with live updates:
|
||||
```
|
||||
docker compose watch
|
||||
```
|
||||
This watches for changes in your code and automatically updates the relevant services.
|
||||
|
||||
6. Checking the status of services:
|
||||
```
|
||||
docker compose ps
|
||||
```
|
||||
This shows the current status of all services defined in your docker-compose.yml file.
|
||||
|
||||
These scenarios demonstrate how to use Docker Compose commands in combination to manage your AutoGPT Platform effectively.
|
||||
|
||||
|
||||
### Persisting Data
|
||||
|
||||
To persist data for PostgreSQL and Redis, you can modify the `docker-compose.yml` file to add volumes. Here's how:
|
||||
|
||||
1. Open the `docker-compose.yml` file in a text editor.
|
||||
2. Add volume configurations for PostgreSQL and Redis services:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
# ... other configurations ...
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
# ... other configurations ...
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
```
|
||||
|
||||
3. Save the file and run `docker compose up -d` to apply the changes.
|
||||
|
||||
This configuration will create named volumes for PostgreSQL and Redis, ensuring that your data persists across container restarts.
|
||||
|
||||
11. Navigate back to the "rnd" folder.
|
||||
12. Open the "AutoGPT builder" folder.
|
||||
13. Open the README file in this folder: [doc](./autogpt_builder/README.md#getting-started).
|
||||
14. In your terminal, run the following commands:
|
||||
```
|
||||
npm install
|
||||
```
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
15. Once the front-end is running, click the link to navigate to `localhost:3000`.
|
||||
16. Click on the "Build" option.
|
||||
17. Add a few blocks to test the functionality.
|
||||
18. Connect the blocks together.
|
||||
19. Click "Run".
|
||||
20. Check your terminal window - you should see that the server has received the request, is processing it, and has executed it.
|
||||
|
||||
And there you have it! You've successfully set up and tested AutoGPT.
|
||||
|
||||
0
rnd/__init__.py
Normal file
0
rnd/__init__.py
Normal file
@@ -1 +1,15 @@
|
||||
AGPT_SERVER_URL=http://localhost:8000/api
|
||||
NEXT_PUBLIC_AGPT_SERVER_URL=http://localhost:8000/api
|
||||
NEXT_PUBLIC_AGPT_WS_SERVER_URL=ws://localhost:8001/ws
|
||||
NEXT_PUBLIC_AGPT_MARKETPLACE_URL=http://localhost:8015/api/v1/market
|
||||
|
||||
## Supabase credentials
|
||||
## YOU ONLY NEED THEM IF YOU WANT TO USE SUPABASE USER AUTHENTICATION
|
||||
## If you're using self-hosted version then you most likely don't need to set this
|
||||
# NEXT_PUBLIC_SUPABASE_URL=your-project-url
|
||||
# NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
|
||||
## OAuth Callback URL
|
||||
## This should be {domain}/auth/callback
|
||||
## Only used if you're using Supabase and OAuth
|
||||
AUTH_CALLBACK_URL=http://localhost:3000/auth/callback
|
||||
GA_MEASUREMENT_ID=G-FH2XK2W4GN
|
||||
|
||||
4
rnd/autogpt_builder/.prettierignore
Normal file
4
rnd/autogpt_builder/.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
.next
|
||||
build
|
||||
public
|
||||
3
rnd/autogpt_builder/.prettierrc
Normal file
3
rnd/autogpt_builder/.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
4
rnd/autogpt_builder/.vscode/launch.json
vendored
4
rnd/autogpt_builder/.vscode/launch.json
vendored
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Next.js: debug server-side",
|
||||
"type": "node-terminal",
|
||||
@@ -24,7 +23,6 @@
|
||||
"uriFormat": "%s",
|
||||
"action": "debugWithEdge"
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
32
rnd/autogpt_builder/Dockerfile
Normal file
32
rnd/autogpt_builder/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
# Base stage for both dev and prod
|
||||
FROM node:21-alpine AS base
|
||||
WORKDIR /app
|
||||
COPY rnd/autogpt_builder/package.json rnd/autogpt_builder/yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
# Dev stage
|
||||
FROM base AS dev
|
||||
ENV NODE_ENV=development
|
||||
COPY rnd/autogpt_builder/ .
|
||||
EXPOSE 3000
|
||||
CMD ["yarn", "run", "dev"]
|
||||
|
||||
# Build stage for prod
|
||||
FROM base AS build
|
||||
COPY rnd/autogpt_builder/ .
|
||||
RUN npm run build
|
||||
|
||||
# Prod stage
|
||||
FROM node:21-alpine AS prod
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/package.json /app/yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY --from=build /app/.next ./.next
|
||||
COPY --from=build /app/public ./public
|
||||
COPY --from=build /app/next.config.mjs ./next.config.mjs
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
@@ -2,7 +2,19 @@ This is the frontend for AutoGPT's next generation
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
Run the following installation once.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# or
|
||||
yarn install
|
||||
# or
|
||||
pnpm install
|
||||
# or
|
||||
bun install
|
||||
```
|
||||
|
||||
Next, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
@@ -18,8 +30,12 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
For subsequent runs, you do not have to `npm install` again. Simply do `npm run dev`.
|
||||
|
||||
If the project is updated via git, you will need to `npm install` after each update.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Deploy
|
||||
## Deploy
|
||||
|
||||
TODO
|
||||
TODO
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
import dotenv from 'dotenv';
|
||||
import dotenv from "dotenv";
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
env: {
|
||||
AGPT_SERVER_URL: process.env.AGPT_SERVER_URL,
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/',
|
||||
destination: '/build',
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
env: {
|
||||
NEXT_PUBLIC_AGPT_SERVER_URL: process.env.NEXT_PUBLIC_AGPT_SERVER_URL,
|
||||
NEXT_PUBLIC_AGPT_MARKETPLACE_URL:
|
||||
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL,
|
||||
},
|
||||
images: {
|
||||
domains: ["images.unsplash.com"],
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/monitor", // FIXME: Remove after 2024-09-01
|
||||
destination: "/",
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
// TODO: Re-enable TypeScript checks once current issues are resolved
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -6,21 +6,35 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@next/third-parties": "^14.2.5",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@supabase/ssr": "^0.4.0",
|
||||
"@supabase/supabase-js": "^2.45.0",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
"@xyflow/react": "^12.1.0",
|
||||
"ajv": "^8.17.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"lucide-react": "^0.407.0",
|
||||
@@ -31,12 +45,14 @@
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"reactflow": "^11.11.4",
|
||||
"react-shepherd": "^6.1.1",
|
||||
"recharts": "^2.12.7",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^10.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -47,6 +63,8 @@
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.4",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
||||
18
rnd/autogpt_builder/src/app/admin/dashboard/page.tsx
Normal file
18
rnd/autogpt_builder/src/app/admin/dashboard/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { withRoleAccess } from "@/lib/withRoleAccess";
|
||||
import React from "react";
|
||||
|
||||
function AdminDashboard() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Admin Dashboard</h1>
|
||||
{/* Add your admin-only content here */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function AdminDashboardPage() {
|
||||
"use server";
|
||||
const withAdminAccess = await withRoleAccess(["admin"]);
|
||||
const ProtectedAdminDashboard = await withAdminAccess(AdminDashboard);
|
||||
return <ProtectedAdminDashboard />;
|
||||
}
|
||||
100
rnd/autogpt_builder/src/app/admin/layout.tsx
Normal file
100
rnd/autogpt_builder/src/app/admin/layout.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { BinaryIcon, XIcon } from "lucide-react";
|
||||
import { usePathname } from "next/navigation"; // Add this import
|
||||
|
||||
const tabs = [
|
||||
{ name: "Dashboard", href: "/admin/dashboard" },
|
||||
{ name: "Marketplace", href: "/admin/marketplace" },
|
||||
{ name: "Users", href: "/admin/users" },
|
||||
{ name: "Settings", href: "/admin/settings" },
|
||||
];
|
||||
|
||||
export default function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const pathname = usePathname(); // Get the current pathname
|
||||
const [activeTab, setActiveTab] = useState(() => {
|
||||
// Set active tab based on the current route
|
||||
return tabs.find((tab) => tab.href === pathname)?.name || tabs[0].name;
|
||||
});
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<nav className="bg-white shadow-sm">
|
||||
<div className="max-w-10xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex h-16 items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<h1 className="text-xl font-bold">Admin Panel</h1>
|
||||
</div>
|
||||
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
{tabs.map((tab) => (
|
||||
<Link
|
||||
key={tab.name}
|
||||
href={tab.href}
|
||||
className={`${
|
||||
activeTab === tab.name
|
||||
? "border-indigo-500 text-indigo-600"
|
||||
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
|
||||
} inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium`}
|
||||
onClick={() => setActiveTab(tab.name)}
|
||||
>
|
||||
{tab.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
>
|
||||
<span className="sr-only">Open main menu</span>
|
||||
{mobileMenuOpen ? (
|
||||
<XIcon className="block h-6 w-6" aria-hidden="true" />
|
||||
) : (
|
||||
<BinaryIcon className="block h-6 w-6" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mobileMenuOpen && (
|
||||
<div className="sm:hidden">
|
||||
<div className="space-y-1 pb-3 pt-2">
|
||||
{tabs.map((tab) => (
|
||||
<Link
|
||||
key={tab.name}
|
||||
href={tab.href}
|
||||
className={`${
|
||||
activeTab === tab.name
|
||||
? "border-indigo-500 bg-indigo-50 text-indigo-700"
|
||||
: "border-transparent text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800"
|
||||
} block border-l-4 py-2 pl-3 pr-4 text-base font-medium`}
|
||||
onClick={() => {
|
||||
setActiveTab(tab.name);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
>
|
||||
{tab.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<main className="py-10">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">{children}</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
rnd/autogpt_builder/src/app/admin/marketplace/page.tsx
Normal file
25
rnd/autogpt_builder/src/app/admin/marketplace/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { withRoleAccess } from "@/lib/withRoleAccess";
|
||||
|
||||
import React from "react";
|
||||
import { getReviewableAgents } from "@/components/admin/marketplace/actions";
|
||||
import AdminMarketplaceAgentList from "@/components/admin/marketplace/AdminMarketplaceAgentList";
|
||||
import AdminFeaturedAgentsControl from "@/components/admin/marketplace/AdminFeaturedAgentsControl";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
async function AdminMarketplace() {
|
||||
const reviewableAgents = await getReviewableAgents();
|
||||
|
||||
return (
|
||||
<>
|
||||
<AdminMarketplaceAgentList agents={reviewableAgents.agents} />
|
||||
<Separator className="my-4" />
|
||||
<AdminFeaturedAgentsControl className="mt-4" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function AdminDashboardPage() {
|
||||
"use server";
|
||||
const withAdminAccess = await withRoleAccess(["admin"]);
|
||||
const ProtectedAdminMarketplace = await withAdminAccess(AdminMarketplace);
|
||||
return <ProtectedAdminMarketplace />;
|
||||
}
|
||||
18
rnd/autogpt_builder/src/app/admin/settings/page.tsx
Normal file
18
rnd/autogpt_builder/src/app/admin/settings/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { withRoleAccess } from "@/lib/withRoleAccess";
|
||||
import React from "react";
|
||||
|
||||
function AdminSettings() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Admin Settings</h1>
|
||||
{/* Add your admin-only settings content here */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function AdminSettingsPage() {
|
||||
"use server";
|
||||
const withAdminAccess = await withRoleAccess(["admin"]);
|
||||
const ProtectedAdminSettings = await withAdminAccess(AdminSettings);
|
||||
return <ProtectedAdminSettings />;
|
||||
}
|
||||
18
rnd/autogpt_builder/src/app/admin/users/page.tsx
Normal file
18
rnd/autogpt_builder/src/app/admin/users/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { withRoleAccess } from "@/lib/withRoleAccess";
|
||||
import React from "react";
|
||||
|
||||
function AdminUsers() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Users Dashboard</h1>
|
||||
{/* Add your admin-only content here */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function AdminUsersPage() {
|
||||
"use server";
|
||||
const withAdminAccess = await withRoleAccess(["admin"]);
|
||||
const ProtectedAdminUsers = await withAdminAccess(AdminUsers);
|
||||
return <ProtectedAdminUsers />;
|
||||
}
|
||||
36
rnd/autogpt_builder/src/app/auth/auth-code-error/page.tsx
Normal file
36
rnd/autogpt_builder/src/app/auth/auth-code-error/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function AuthErrorPage() {
|
||||
const [errorType, setErrorType] = useState<string | null>(null);
|
||||
const [errorCode, setErrorCode] = useState<string | null>(null);
|
||||
const [errorDescription, setErrorDescription] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// This code only runs on the client side
|
||||
if (typeof window !== "undefined") {
|
||||
const hash = window.location.hash.substring(1); // Remove the leading '#'
|
||||
const params = new URLSearchParams(hash);
|
||||
|
||||
setErrorType(params.get("error"));
|
||||
setErrorCode(params.get("error_code"));
|
||||
setErrorDescription(
|
||||
params.get("error_description")?.replace(/\+/g, " ") ?? null,
|
||||
); // Replace '+' with space
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!errorType && !errorCode && !errorDescription) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Authentication Error</h1>
|
||||
{errorType && <p>Error Type: {errorType}</p>}
|
||||
{errorCode && <p>Error Code: {errorCode}</p>}
|
||||
{errorDescription && <p>Error Description: {errorDescription}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
rnd/autogpt_builder/src/app/auth/callback/route.ts
Normal file
36
rnd/autogpt_builder/src/app/auth/callback/route.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createServerClient } from "@/lib/supabase/server";
|
||||
|
||||
// Handle the callback to complete the user session login
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams, origin } = new URL(request.url);
|
||||
const code = searchParams.get("code");
|
||||
// if "next" is in param, use it as the redirect URL
|
||||
const next = searchParams.get("next") ?? "/profile";
|
||||
|
||||
if (code) {
|
||||
const supabase = createServerClient();
|
||||
|
||||
if (!supabase) {
|
||||
return NextResponse.redirect(`${origin}/error`);
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
|
||||
// data.session?.refresh_token is available if you need to store it for later use
|
||||
if (!error) {
|
||||
const forwardedHost = request.headers.get("x-forwarded-host"); // original origin before load balancer
|
||||
const isLocalEnv = process.env.NODE_ENV === "development";
|
||||
if (isLocalEnv) {
|
||||
// we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host
|
||||
return NextResponse.redirect(`${origin}${next}`);
|
||||
} else if (forwardedHost) {
|
||||
return NextResponse.redirect(`https://${forwardedHost}${next}`);
|
||||
} else {
|
||||
return NextResponse.redirect(`${origin}${next}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the user to an error page with instructions
|
||||
return NextResponse.redirect(`${origin}/auth/auth-code-error`);
|
||||
}
|
||||
33
rnd/autogpt_builder/src/app/auth/confirm/route.ts
Normal file
33
rnd/autogpt_builder/src/app/auth/confirm/route.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { type EmailOtpType } from "@supabase/supabase-js";
|
||||
import { type NextRequest } from "next/server";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
import { createServerClient } from "@/lib/supabase/server";
|
||||
|
||||
// Email confirmation route
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const token_hash = searchParams.get("token_hash");
|
||||
const type = searchParams.get("type") as EmailOtpType | null;
|
||||
const next = searchParams.get("next") ?? "/";
|
||||
|
||||
if (token_hash && type) {
|
||||
const supabase = createServerClient();
|
||||
|
||||
if (!supabase) {
|
||||
redirect("/error");
|
||||
}
|
||||
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
type,
|
||||
token_hash,
|
||||
});
|
||||
if (!error) {
|
||||
// redirect user to specified redirect URL or root of app
|
||||
redirect(next);
|
||||
}
|
||||
}
|
||||
|
||||
// redirect the user to an error page with some instructions
|
||||
redirect("/error");
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import FlowEditor from '@/components/Flow';
|
||||
|
||||
@@ -7,38 +7,10 @@ export default function Home() {
|
||||
const query = useSearchParams();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center min-h-screen">
|
||||
<div className="z-10 w-full flex items-center justify-between font-mono text-sm relative">
|
||||
<p className="border border-gray-600 rounded-xl pb-4 pt-4 p-4">
|
||||
Get started by adding a
|
||||
<code className="font-mono font-bold">block</code>
|
||||
</p>
|
||||
<div className="absolute top-0 right-0 p-4">
|
||||
<a
|
||||
className="pointer-events-auto flex place-items-center gap-2"
|
||||
href="https://news.agpt.co/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/AUTOgpt_Logo_dark.png"
|
||||
alt="AutoGPT Logo"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex justify-center mt-10">
|
||||
<FlowEditor
|
||||
className="flow-container w-full min-h-[75vh] border border-gray-300 dark:border-gray-700 rounded-lg"
|
||||
flowID={query.get("flowID") ?? query.get("templateID") ?? undefined}
|
||||
template={!!query.get("templateID")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FlowEditor
|
||||
className="flow-container w-full min-h-[86vh] border border-gray-300 dark:border-gray-700 rounded-lg"
|
||||
flowID={query.get("flowID") ?? query.get("templateID") ?? undefined}
|
||||
template={!!query.get("templateID")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
43
rnd/autogpt_builder/src/app/error.tsx
Normal file
43
rnd/autogpt_builder/src/app/error.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { IconCircleAlert } from "@/components/ui/icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
console.error(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-background">
|
||||
<div className="w-full max-w-md px-4 text-center sm:px-6">
|
||||
<div className="mx-auto flex size-12 items-center justify-center rounded-full bg-muted">
|
||||
<IconCircleAlert className="size-10" />
|
||||
</div>
|
||||
<h1 className="mt-8 text-2xl font-bold tracking-tight text-foreground">
|
||||
Oops, something went wrong!
|
||||
</h1>
|
||||
<p className="mt-4 text-muted-foreground">
|
||||
We're sorry, but an unexpected error has occurred. Please try
|
||||
again later or contact support if the issue persists.
|
||||
</p>
|
||||
<div className="mt-6 flex flex-row justify-center gap-4">
|
||||
<Button onClick={reset} variant="outline">
|
||||
Retry
|
||||
</Button>
|
||||
<Button>
|
||||
<Link href="/">Go to Homepage</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,3 +7,69 @@
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--radius: 0.5rem;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { ThemeProvider as NextThemeProvider } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
import { Inter } from "next/font/google";
|
||||
import Link from "next/link";
|
||||
import { CubeIcon, Pencil1Icon, ReaderIcon, TimerIcon } from "@radix-ui/react-icons";
|
||||
import { Providers } from "@/app/providers";
|
||||
import { NavBar } from "@/components/NavBar";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import "./globals.css";
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import TallyPopupSimple from "@/components/TallyPopup";
|
||||
import { GoogleAnalytics } from "@next/third-parties/google";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
@@ -21,39 +17,6 @@ export const metadata: Metadata = {
|
||||
description: "Your one stop shop to creating AI Agents",
|
||||
};
|
||||
|
||||
function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemeProvider {...props}>{children}</NextThemeProvider>
|
||||
}
|
||||
|
||||
const NavBar = () => (
|
||||
<nav className="bg-white dark:bg-slate-800 p-4 flex justify-between items-center shadow">
|
||||
<div className="flex space-x-4">
|
||||
<Link href="/monitor" className={buttonVariants({ variant: "ghost" })}>
|
||||
<TimerIcon className="mr-1" /> Monitor
|
||||
</Link>
|
||||
<Link href="/build" className={buttonVariants({ variant: "ghost" })}>
|
||||
<Pencil1Icon className="mr-1" /> Build
|
||||
</Link>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 rounded-full">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>Profile</DropdownMenuItem>
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Switch Workspace</DropdownMenuItem>
|
||||
<DropdownMenuItem>Log out</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</nav>
|
||||
);
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -61,20 +24,26 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider
|
||||
<body className={cn("antialiased transition-colors", inter.className)}>
|
||||
<Providers
|
||||
attribute="class"
|
||||
defaultTheme="light"
|
||||
// Feel free to remove this line if you want to use the system theme by default
|
||||
// enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<div className="min-h-screen bg-gray-200 text-gray-900">
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<NavBar />
|
||||
<main className="mx-auto p-4">
|
||||
{children}
|
||||
</main>
|
||||
<main className="flex-1 overflow-hidden p-4">{children}</main>
|
||||
<TallyPopupSimple />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
<Toaster />
|
||||
</Providers>
|
||||
</body>
|
||||
|
||||
<GoogleAnalytics
|
||||
gaId={process.env.GA_MEASUREMENT_ID || "G-FH2XK2W4GN"} // This is the measurement Id for the Google Analytics dev project
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
21
rnd/autogpt_builder/src/app/loading.tsx
Normal file
21
rnd/autogpt_builder/src/app/loading.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import AgentFlowListSkeleton from "@/components/monitor/skeletons/AgentFlowListSkeleton";
|
||||
import React from "react";
|
||||
import FlowRunsListSkeleton from "@/components/monitor/skeletons/FlowRunsListSkeleton";
|
||||
import FlowRunsStatusSkeleton from "@/components/monitor/skeletons/FlowRunsStatusSkeleton";
|
||||
|
||||
export default function MonitorLoadingSkeleton() {
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
{/* Agents Section */}
|
||||
<AgentFlowListSkeleton />
|
||||
|
||||
{/* Runs Section */}
|
||||
<FlowRunsListSkeleton />
|
||||
|
||||
{/* Stats Section */}
|
||||
<FlowRunsStatusSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
rnd/autogpt_builder/src/app/login/actions.ts
Normal file
66
rnd/autogpt_builder/src/app/login/actions.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
"use server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { createServerClient } from "@/lib/supabase/server";
|
||||
import { z } from "zod";
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
|
||||
const loginFormSchema = z.object({
|
||||
email: z.string().email().min(2).max(64),
|
||||
password: z.string().min(6).max(64),
|
||||
});
|
||||
|
||||
export async function login(values: z.infer<typeof loginFormSchema>) {
|
||||
const supabase = createServerClient();
|
||||
|
||||
if (!supabase) {
|
||||
redirect("/error");
|
||||
}
|
||||
|
||||
// We are sure that the values are of the correct type because zod validates the form
|
||||
const { data, error } = await supabase.auth.signInWithPassword(values);
|
||||
|
||||
if (error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
if (data.session) {
|
||||
await supabase.auth.setSession(data.session);
|
||||
}
|
||||
|
||||
revalidatePath("/", "layout");
|
||||
redirect("/profile");
|
||||
}
|
||||
|
||||
export async function signup(values: z.infer<typeof loginFormSchema>) {
|
||||
"use server";
|
||||
const supabase = createServerClient();
|
||||
|
||||
if (!supabase) {
|
||||
redirect("/error");
|
||||
}
|
||||
|
||||
// We are sure that the values are of the correct type because zod validates the form
|
||||
const { data, error } = await supabase.auth.signUp(values);
|
||||
|
||||
if (error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
if (data.session) {
|
||||
await supabase.auth.setSession(data.session);
|
||||
}
|
||||
if (data.user) {
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
api.logCreateUser({
|
||||
email: values.email,
|
||||
user_id: data.user.id,
|
||||
name: values.email,
|
||||
username: values.email,
|
||||
});
|
||||
}
|
||||
|
||||
revalidatePath("/", "layout");
|
||||
redirect("/profile");
|
||||
}
|
||||
234
rnd/autogpt_builder/src/app/login/page.tsx
Normal file
234
rnd/autogpt_builder/src/app/login/page.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
"use client";
|
||||
import useUser from "@/hooks/useUser";
|
||||
import { login, signup } from "./actions";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PasswordInput } from "@/components/PasswordInput";
|
||||
import { FaGoogle, FaGithub, FaDiscord, FaSpinner } from "react-icons/fa";
|
||||
import { useState } from "react";
|
||||
import { useSupabase } from "@/components/SupabaseProvider";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
const loginFormSchema = z.object({
|
||||
email: z.string().email().min(2).max(64),
|
||||
password: z.string().min(6).max(64),
|
||||
agreeToTerms: z.boolean().refine((value) => value === true, {
|
||||
message: "You must agree to the Terms of Service and Privacy Policy",
|
||||
}),
|
||||
});
|
||||
|
||||
export default function LoginPage() {
|
||||
const { supabase, isLoading: isSupabaseLoading } = useSupabase();
|
||||
const { user, isLoading: isUserLoading } = useUser();
|
||||
const [feedback, setFeedback] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const form = useForm<z.infer<typeof loginFormSchema>>({
|
||||
resolver: zodResolver(loginFormSchema),
|
||||
defaultValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
agreeToTerms: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (user) {
|
||||
console.log("User exists, redirecting to profile");
|
||||
router.push("/profile");
|
||||
}
|
||||
|
||||
if (isUserLoading || isSupabaseLoading || user) {
|
||||
return (
|
||||
<div className="flex h-[80vh] items-center justify-center">
|
||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!supabase) {
|
||||
return (
|
||||
<div>
|
||||
User accounts are disabled because Supabase client is unavailable
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function handleSignInWithProvider(
|
||||
provider: "google" | "github" | "discord",
|
||||
) {
|
||||
const { data, error } = await supabase!.auth.signInWithOAuth({
|
||||
provider: provider,
|
||||
options: {
|
||||
redirectTo:
|
||||
process.env.AUTH_CALLBACK_URL ??
|
||||
`http://localhost:3000/auth/callback`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!error) {
|
||||
setFeedback(null);
|
||||
return;
|
||||
}
|
||||
setFeedback(error.message);
|
||||
}
|
||||
|
||||
const onLogin = async (data: z.infer<typeof loginFormSchema>) => {
|
||||
setIsLoading(true);
|
||||
const error = await login(data);
|
||||
setIsLoading(false);
|
||||
if (error) {
|
||||
setFeedback(error);
|
||||
return;
|
||||
}
|
||||
setFeedback(null);
|
||||
};
|
||||
|
||||
const onSignup = async (data: z.infer<typeof loginFormSchema>) => {
|
||||
if (await form.trigger()) {
|
||||
setIsLoading(true);
|
||||
const error = await signup(data);
|
||||
setIsLoading(false);
|
||||
if (error) {
|
||||
setFeedback(error);
|
||||
return;
|
||||
}
|
||||
setFeedback(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-[80vh] items-center justify-center">
|
||||
<div className="w-full max-w-md space-y-6 rounded-lg p-8 shadow-md">
|
||||
<div className="mb-6 space-y-2">
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => handleSignInWithProvider("google")}
|
||||
variant="outline"
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<FaGoogle className="mr-2 h-4 w-4" />
|
||||
Sign in with Google
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => handleSignInWithProvider("github")}
|
||||
variant="outline"
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<FaGithub className="mr-2 h-4 w-4" />
|
||||
Sign in with GitHub
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => handleSignInWithProvider("discord")}
|
||||
variant="outline"
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<FaDiscord className="mr-2 h-4 w-4" />
|
||||
Sign in with Discord
|
||||
</Button>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onLogin)}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mb-4">
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="user@email.com" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput placeholder="password" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Password needs to be at least 6 characters long
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="agreeToTerms"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4 flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>
|
||||
I agree to the{" "}
|
||||
<Link href="/terms-of-service" className="underline">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link
|
||||
href="https://www.notion.so/auto-gpt/Privacy-Policy-ab11c9c20dbd4de1a15dcffe84d77984"
|
||||
className="underline"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</FormLabel>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="mb-6 mt-6 flex w-full space-x-4">
|
||||
<Button
|
||||
className="flex w-1/2 justify-center"
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
<Button
|
||||
className="flex w-1/2 justify-center"
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={form.handleSubmit(onSignup)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<p className="text-sm text-red-500">{feedback}</p>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx
Normal file
41
rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Suspense } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
import MarketplaceAPI from "@/lib/marketplace-api";
|
||||
import { AgentDetailResponse } from "@/lib/marketplace-api";
|
||||
import AgentDetailContent from "@/components/marketplace/AgentDetailContent";
|
||||
|
||||
async function getAgentDetails(id: string): Promise<AgentDetailResponse> {
|
||||
const apiUrl =
|
||||
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL ||
|
||||
"http://localhost:8015/api/v1/market";
|
||||
const api = new MarketplaceAPI(apiUrl);
|
||||
try {
|
||||
console.log(`Fetching agent details for id: ${id}`);
|
||||
const agent = await api.getAgentDetails(id);
|
||||
console.log(`Agent details fetched:`, agent);
|
||||
return agent;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching agent details:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function AgentDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string };
|
||||
}) {
|
||||
let agent: AgentDetailResponse;
|
||||
|
||||
try {
|
||||
agent = await getAgentDetails(params.id);
|
||||
} catch (error) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<AgentDetailContent agent={agent} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
317
rnd/autogpt_builder/src/app/marketplace/page.tsx
Normal file
317
rnd/autogpt_builder/src/app/marketplace/page.tsx
Normal file
@@ -0,0 +1,317 @@
|
||||
"use client";
|
||||
import React, { useEffect, useMemo, useState, useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import MarketplaceAPI, {
|
||||
AgentResponse,
|
||||
AgentListResponse,
|
||||
AgentWithRank,
|
||||
} from "@/lib/marketplace-api";
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
PlusCircle,
|
||||
Search,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
|
||||
// Utility Functions
|
||||
function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number,
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Types
|
||||
type Agent = AgentResponse | AgentWithRank;
|
||||
|
||||
// Components
|
||||
const HeroSection: React.FC = () => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="relative bg-indigo-600 py-6">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1562408590-e32931084e23?auto=format&fit=crop&w=2070&q=80"
|
||||
alt="Marketplace background"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
quality={75}
|
||||
priority
|
||||
className="opacity-20"
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 bg-indigo-600 mix-blend-multiply"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</div>
|
||||
<div className="relative mx-auto flex max-w-7xl items-center justify-between px-4 py-4 sm:px-6 lg:px-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-extrabold tracking-tight text-white sm:text-3xl lg:text-4xl">
|
||||
AutoGPT Marketplace
|
||||
</h1>
|
||||
<p className="mt-2 max-w-3xl text-sm text-indigo-100 sm:text-base">
|
||||
Discover and share proven AI Agents to supercharge your business.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => router.push("/marketplace/submit")}
|
||||
className="flex items-center bg-white text-indigo-600 hover:bg-indigo-50"
|
||||
>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
Submit Agent
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchInput: React.FC<{
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}> = ({ value, onChange }) => (
|
||||
<div className="relative mb-8">
|
||||
<Input
|
||||
placeholder="Search agents..."
|
||||
type="text"
|
||||
className="w-full rounded-full border-gray-300 py-2 pl-10 pr-4 focus:border-indigo-500 focus:ring-indigo-500"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Search
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 transform text-gray-400"
|
||||
size={20}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AgentCard: React.FC<{ agent: Agent; featured?: boolean }> = ({
|
||||
agent,
|
||||
featured = false,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`/marketplace/${agent.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex cursor-pointer flex-col justify-between rounded-lg border p-6 transition-colors duration-200 hover:bg-gray-50 ${featured ? "border-indigo-500 shadow-md" : "border-gray-200"}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h3 className="truncate text-lg font-semibold text-gray-900">
|
||||
{agent.name}
|
||||
</h3>
|
||||
{featured && <Star className="text-indigo-500" size={20} />}
|
||||
</div>
|
||||
<p className="mb-4 line-clamp-2 text-sm text-gray-500">
|
||||
{agent.description}
|
||||
</p>
|
||||
<div className="mb-2 text-xs text-gray-400">
|
||||
Categories: {agent.categories.join(", ")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-end justify-between">
|
||||
<div className="text-xs text-gray-400">
|
||||
Updated {new Date(agent.updatedAt).toLocaleDateString()}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">Downloads {agent.downloads}</div>
|
||||
{"rank" in agent && (
|
||||
<div className="text-xs text-indigo-600">
|
||||
Rank: {agent.rank.toFixed(2)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AgentGrid: React.FC<{
|
||||
agents: Agent[];
|
||||
title: string;
|
||||
featured?: boolean;
|
||||
}> = ({ agents, title, featured = false }) => (
|
||||
<div className="mb-12">
|
||||
<h2 className="mb-4 text-2xl font-bold text-gray-900">{title}</h2>
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{agents.map((agent) => (
|
||||
<AgentCard agent={agent} key={agent.id} featured={featured} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Pagination: React.FC<{
|
||||
page: number;
|
||||
totalPages: number;
|
||||
onPrevPage: () => void;
|
||||
onNextPage: () => void;
|
||||
}> = ({ page, totalPages, onPrevPage, onNextPage }) => (
|
||||
<div className="mt-8 flex items-center justify-between">
|
||||
<Button
|
||||
onClick={onPrevPage}
|
||||
disabled={page === 1}
|
||||
className="flex items-center space-x-2 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
<span>Previous</span>
|
||||
</Button>
|
||||
<span className="text-sm text-gray-700">
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
onClick={onNextPage}
|
||||
disabled={page === totalPages}
|
||||
className="flex items-center space-x-2 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50"
|
||||
>
|
||||
<span>Next</span>
|
||||
<ChevronRight size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Main Component
|
||||
const Marketplace: React.FC = () => {
|
||||
const apiUrl =
|
||||
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL ||
|
||||
"http://localhost:8015/api/v1/market";
|
||||
const api = useMemo(() => new MarketplaceAPI(apiUrl), [apiUrl]);
|
||||
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [searchResults, setSearchResults] = useState<Agent[]>([]);
|
||||
const [featuredAgents, setFeaturedAgents] = useState<Agent[]>([]);
|
||||
const [topAgents, setTopAgents] = useState<Agent[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const fetchTopAgents = useCallback(
|
||||
async (currentPage: number) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await api.getTopDownloadedAgents(currentPage, 9);
|
||||
setTopAgents(response.agents);
|
||||
setTotalPages(response.total_pages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching top agents:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[api],
|
||||
);
|
||||
|
||||
const fetchFeaturedAgents = useCallback(async () => {
|
||||
try {
|
||||
const featured = await api.getFeaturedAgents();
|
||||
setFeaturedAgents(featured.agents);
|
||||
} catch (error) {
|
||||
console.error("Error fetching featured agents:", error);
|
||||
}
|
||||
}, [api]);
|
||||
|
||||
const searchAgents = useCallback(
|
||||
async (searchTerm: string) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await api.searchAgents(searchTerm, 1, 30);
|
||||
const filteredAgents = response.filter((agent) => agent.rank > 0);
|
||||
setSearchResults(filteredAgents);
|
||||
} catch (error) {
|
||||
console.error("Error searching agents:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[api],
|
||||
);
|
||||
|
||||
const debouncedSearch = useMemo(
|
||||
() => debounce(searchAgents, 300),
|
||||
[searchAgents],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchValue) {
|
||||
debouncedSearch(searchValue);
|
||||
} else {
|
||||
fetchTopAgents(page);
|
||||
}
|
||||
}, [searchValue, page, debouncedSearch, fetchTopAgents]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchFeaturedAgents();
|
||||
}, [fetchFeaturedAgents]);
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchValue(e.target.value);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (page < totalPages) {
|
||||
setPage(page + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevPage = () => {
|
||||
if (page > 1) {
|
||||
setPage(page - 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<HeroSection />
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
<SearchInput value={searchValue} onChange={handleInputChange} />
|
||||
{isLoading ? (
|
||||
<div className="py-12 text-center">
|
||||
<div className="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-gray-900"></div>
|
||||
<p className="mt-2 text-gray-600">Loading agents...</p>
|
||||
</div>
|
||||
) : searchValue ? (
|
||||
searchResults.length > 0 ? (
|
||||
<AgentGrid agents={searchResults} title="Search Results" />
|
||||
) : (
|
||||
<div className="py-12 text-center">
|
||||
<p className="text-gray-600">
|
||||
No agents found matching your search criteria.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{featuredAgents.length > 0 && (
|
||||
<AgentGrid
|
||||
agents={featuredAgents}
|
||||
title="Featured Agents"
|
||||
featured={true}
|
||||
/>
|
||||
)}
|
||||
<AgentGrid agents={topAgents} title="Top Downloaded Agents" />
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
onPrevPage={handlePrevPage}
|
||||
onNextPage={handleNextPage}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Marketplace;
|
||||
408
rnd/autogpt_builder/src/app/marketplace/submit/page.tsx
Normal file
408
rnd/autogpt_builder/src/app/marketplace/submit/page.tsx
Normal file
@@ -0,0 +1,408 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import MarketplaceAPI from "@/lib/marketplace-api";
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from "@/components/ui/multiselect";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
type FormData = {
|
||||
name: string;
|
||||
description: string;
|
||||
author: string;
|
||||
keywords: string[];
|
||||
categories: string[];
|
||||
agreeToTerms: boolean;
|
||||
selectedAgentId: string;
|
||||
};
|
||||
|
||||
const SubmitPage: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
selectedAgentId: "", // Initialize with an empty string
|
||||
name: "",
|
||||
description: "",
|
||||
author: "",
|
||||
keywords: [],
|
||||
categories: [],
|
||||
agreeToTerms: false,
|
||||
},
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||
const [userAgents, setUserAgents] = useState<
|
||||
Array<{ id: string; name: string; version: number }>
|
||||
>([]);
|
||||
const [selectedAgentGraph, setSelectedAgentGraph] = useState<any>(null);
|
||||
|
||||
const selectedAgentId = watch("selectedAgentId");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserAgents = async () => {
|
||||
const api = new AutoGPTServerAPI();
|
||||
const agents = await api.listGraphs();
|
||||
console.log(agents);
|
||||
setUserAgents(
|
||||
agents.map((agent) => ({
|
||||
id: agent.id,
|
||||
name: agent.name || `Agent (${agent.id})`,
|
||||
version: agent.version,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
fetchUserAgents();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAgentGraph = async () => {
|
||||
if (selectedAgentId) {
|
||||
const api = new AutoGPTServerAPI();
|
||||
const graph = await api.getGraph(selectedAgentId);
|
||||
setSelectedAgentGraph(graph);
|
||||
setValue("name", graph.name);
|
||||
setValue("description", graph.description);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAgentGraph();
|
||||
}, [selectedAgentId, setValue]);
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
setIsSubmitting(true);
|
||||
setSubmitError(null);
|
||||
|
||||
if (!data.agreeToTerms) {
|
||||
throw new Error("You must agree to the terms of service");
|
||||
}
|
||||
|
||||
try {
|
||||
if (!selectedAgentGraph) {
|
||||
throw new Error("Please select an agent");
|
||||
}
|
||||
|
||||
const api = new MarketplaceAPI();
|
||||
await api.submitAgent(
|
||||
{
|
||||
...selectedAgentGraph,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
},
|
||||
data.author,
|
||||
data.keywords,
|
||||
data.categories,
|
||||
);
|
||||
|
||||
router.push("/marketplace?submission=success");
|
||||
} catch (error) {
|
||||
console.error("Submission error:", error);
|
||||
setSubmitError(
|
||||
error instanceof Error ? error.message : "An unknown error occurred",
|
||||
);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="mb-6 text-3xl font-bold">Submit Your Agent</h1>
|
||||
<Card className="p-6">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="space-y-4">
|
||||
<Controller
|
||||
name="selectedAgentId"
|
||||
control={control}
|
||||
rules={{ required: "Please select an agent" }}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={field.name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Select Agent
|
||||
</label>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value || ""}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select an agent" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{userAgents.map((agent) => (
|
||||
<SelectItem key={agent.id} value={agent.id}>
|
||||
{agent.name} (v{agent.version})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.selectedAgentId && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.selectedAgentId.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* {selectedAgentGraph && (
|
||||
<div className="mt-4" style={{ height: "600px" }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
fitView
|
||||
attributionPosition="bottom-left"
|
||||
nodesConnectable={false}
|
||||
nodesDraggable={false}
|
||||
zoomOnScroll={false}
|
||||
panOnScroll={false}
|
||||
elementsSelectable={false}
|
||||
>
|
||||
<Controls showInteractive={false} />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
rules={{ required: "Name is required" }}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={field.name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Agent Name
|
||||
</label>
|
||||
<Input
|
||||
id={field.name}
|
||||
placeholder="Enter your agent's name"
|
||||
{...field}
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.name.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="description"
|
||||
control={control}
|
||||
rules={{ required: "Description is required" }}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={field.name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<Textarea
|
||||
id={field.name}
|
||||
placeholder="Describe your agent"
|
||||
{...field}
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="author"
|
||||
control={control}
|
||||
rules={{ required: "Author is required" }}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={field.name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Author
|
||||
</label>
|
||||
<Input
|
||||
id={field.name}
|
||||
placeholder="Your name or username"
|
||||
{...field}
|
||||
/>
|
||||
{errors.author && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.author.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="keywords"
|
||||
control={control}
|
||||
rules={{ required: "At least one keyword is required" }}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={field.name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Keywords
|
||||
</label>
|
||||
<MultiSelector
|
||||
values={field.value || []}
|
||||
onValuesChange={field.onChange}
|
||||
>
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Add keywords" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="keyword1">
|
||||
Keyword 1
|
||||
</MultiSelectorItem>
|
||||
<MultiSelectorItem value="keyword2">
|
||||
Keyword 2
|
||||
</MultiSelectorItem>
|
||||
{/* Add more predefined keywords as needed */}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
{errors.keywords && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.keywords.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="categories"
|
||||
control={control}
|
||||
rules={{ required: "At least one category is required" }}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={field.name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Categories
|
||||
</label>
|
||||
<MultiSelector
|
||||
values={field.value || []}
|
||||
onValuesChange={field.onChange}
|
||||
>
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select categories" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="productivity">
|
||||
Productivity
|
||||
</MultiSelectorItem>
|
||||
<MultiSelectorItem value="entertainment">
|
||||
Entertainment
|
||||
</MultiSelectorItem>
|
||||
<MultiSelectorItem value="education">
|
||||
Education
|
||||
</MultiSelectorItem>
|
||||
<MultiSelectorItem value="business">
|
||||
Business
|
||||
</MultiSelectorItem>
|
||||
<MultiSelectorItem value="other">
|
||||
Other
|
||||
</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
{errors.categories && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.categories.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="agreeToTerms"
|
||||
control={control}
|
||||
rules={{ required: "You must agree to the terms of service" }}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="agreeToTerms"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<label
|
||||
htmlFor="agreeToTerms"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
I agree to the{" "}
|
||||
<a href="/terms" className="text-blue-500 hover:underline">
|
||||
terms of service
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{errors.agreeToTerms && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.agreeToTerms.message}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{submitError && (
|
||||
<Alert variant="destructive">
|
||||
<AlertTitle>Submission Failed</AlertTitle>
|
||||
<AlertDescription>{submitError}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
||||
{isSubmitting ? "Submitting..." : "Submit Agent"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubmitPage;
|
||||
@@ -1,702 +0,0 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
ComposedChart,
|
||||
DefaultLegendContentProps,
|
||||
Legend,
|
||||
Line,
|
||||
ResponsiveContainer,
|
||||
Scatter,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { ChevronDownIcon, ClockIcon, EnterIcon, ExitIcon, Pencil2Icon } from '@radix-ui/react-icons';
|
||||
import AutoGPTServerAPI, { GraphMeta, NodeExecutionResult } from '@/lib/autogpt_server_api';
|
||||
import { cn, exportAsJSONFile, hashString } from '@/lib/utils';
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { AgentImportForm } from '@/components/agent-import-form';
|
||||
|
||||
const Monitor = () => {
|
||||
const [flows, setFlows] = useState<GraphMeta[]>([]);
|
||||
const [flowRuns, setFlowRuns] = useState<FlowRun[]>([]);
|
||||
const [selectedFlow, setSelectedFlow] = useState<GraphMeta | null>(null);
|
||||
const [selectedRun, setSelectedRun] = useState<FlowRun | null>(null);
|
||||
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
useEffect(() => fetchFlowsAndRuns(), []);
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => flows.map(f => refreshFlowRuns(f.id)), 5000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
function fetchFlowsAndRuns() {
|
||||
api.listGraphs()
|
||||
.then(flows => {
|
||||
setFlows(flows);
|
||||
flows.map(flow => refreshFlowRuns(flow.id));
|
||||
});
|
||||
}
|
||||
|
||||
function refreshFlowRuns(flowID: string) {
|
||||
// Fetch flow run IDs
|
||||
api.listGraphRunIDs(flowID)
|
||||
.then(runIDs => runIDs.map(runID => {
|
||||
let run;
|
||||
if (
|
||||
(run = flowRuns.find(fr => fr.id == runID))
|
||||
&& !["waiting", "running"].includes(run.status)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch flow run
|
||||
api.getGraphExecutionInfo(flowID, runID)
|
||||
.then(execInfo => setFlowRuns(flowRuns => {
|
||||
if (execInfo.length == 0) return flowRuns;
|
||||
|
||||
const flowRunIndex = flowRuns.findIndex(fr => fr.id == runID);
|
||||
const flowRun = flowRunFromNodeExecutionResults(execInfo);
|
||||
if (flowRunIndex > -1) {
|
||||
flowRuns.splice(flowRunIndex, 1, flowRun)
|
||||
}
|
||||
else {
|
||||
flowRuns.push(flowRun)
|
||||
}
|
||||
return flowRuns
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
const column1 = "md:col-span-2 xl:col-span-3 xxl:col-span-2";
|
||||
const column2 = "md:col-span-3 lg:col-span-2 xl:col-span-3 space-y-4";
|
||||
const column3 = "col-span-full xl:col-span-4 xxl:col-span-5";
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 lg:grid-cols-4 xl:grid-cols-10 gap-4">
|
||||
<AgentFlowList
|
||||
className={column1}
|
||||
flows={flows}
|
||||
flowRuns={flowRuns}
|
||||
selectedFlow={selectedFlow}
|
||||
onSelectFlow={f => {
|
||||
setSelectedRun(null);
|
||||
setSelectedFlow(f.id == selectedFlow?.id ? null : f);
|
||||
}}
|
||||
/>
|
||||
<FlowRunsList
|
||||
className={column2}
|
||||
flows={flows}
|
||||
runs={
|
||||
(
|
||||
selectedFlow
|
||||
? flowRuns.filter(v => v.graphID == selectedFlow.id)
|
||||
: flowRuns
|
||||
)
|
||||
.toSorted((a, b) => Number(a.startTime) - Number(b.startTime))
|
||||
}
|
||||
selectedRun={selectedRun}
|
||||
onSelectRun={r => setSelectedRun(r.id == selectedRun?.id ? null : r)}
|
||||
/>
|
||||
{selectedRun && (
|
||||
<FlowRunInfo
|
||||
flow={selectedFlow || flows.find(f => f.id == selectedRun.graphID)!}
|
||||
flowRun={selectedRun}
|
||||
className={column3}
|
||||
/>
|
||||
) || selectedFlow && (
|
||||
<FlowInfo
|
||||
flow={selectedFlow}
|
||||
flowRuns={flowRuns.filter(r => r.graphID == selectedFlow.id)}
|
||||
className={column3}
|
||||
/>
|
||||
) || (
|
||||
<Card className={`p-6 ${column3}`}>
|
||||
<FlowRunsStats flows={flows} flowRuns={flowRuns} />
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type FlowRun = {
|
||||
id: string
|
||||
graphID: string
|
||||
graphVersion: number
|
||||
status: 'running' | 'waiting' | 'success' | 'failed'
|
||||
startTime: number // unix timestamp (ms)
|
||||
endTime: number // unix timestamp (ms)
|
||||
duration: number // seconds
|
||||
totalRunTime: number // seconds
|
||||
|
||||
nodeExecutionResults: NodeExecutionResult[]
|
||||
};
|
||||
|
||||
function flowRunFromNodeExecutionResults(
|
||||
nodeExecutionResults: NodeExecutionResult[]
|
||||
): FlowRun {
|
||||
// Determine overall status
|
||||
let status: 'running' | 'waiting' | 'success' | 'failed' = 'success';
|
||||
for (const execution of nodeExecutionResults) {
|
||||
if (execution.status === 'FAILED') {
|
||||
status = 'failed';
|
||||
break;
|
||||
} else if (['QUEUED', 'RUNNING'].includes(execution.status)) {
|
||||
status = 'running';
|
||||
break;
|
||||
} else if (execution.status === 'INCOMPLETE') {
|
||||
status = 'waiting';
|
||||
}
|
||||
}
|
||||
|
||||
// Determine aggregate startTime, endTime, and totalRunTime
|
||||
const now = Date.now();
|
||||
const startTime = Math.min(
|
||||
...nodeExecutionResults.map(ner => ner.add_time.getTime()), now
|
||||
);
|
||||
const endTime = (
|
||||
['success', 'failed'].includes(status)
|
||||
? Math.max(
|
||||
...nodeExecutionResults.map(ner => ner.end_time?.getTime() || 0), startTime
|
||||
)
|
||||
: now
|
||||
);
|
||||
const duration = (endTime - startTime) / 1000; // Convert to seconds
|
||||
const totalRunTime = nodeExecutionResults.reduce((cum, node) => (
|
||||
cum + ((node.end_time?.getTime() ?? now) - (node.start_time?.getTime() ?? now))
|
||||
), 0) / 1000;
|
||||
|
||||
return {
|
||||
id: nodeExecutionResults[0].graph_exec_id,
|
||||
graphID: nodeExecutionResults[0].graph_id,
|
||||
graphVersion: nodeExecutionResults[0].graph_version,
|
||||
status,
|
||||
startTime,
|
||||
endTime,
|
||||
duration,
|
||||
totalRunTime,
|
||||
nodeExecutionResults: nodeExecutionResults,
|
||||
};
|
||||
}
|
||||
|
||||
const AgentFlowList = (
|
||||
{ flows, flowRuns, selectedFlow, onSelectFlow, className }: {
|
||||
flows: GraphMeta[],
|
||||
flowRuns?: FlowRun[],
|
||||
selectedFlow: GraphMeta | null,
|
||||
onSelectFlow: (f: GraphMeta) => void,
|
||||
className?: string,
|
||||
}
|
||||
) => {
|
||||
const [templates, setTemplates] = useState<GraphMeta[]>([]);
|
||||
const api = new AutoGPTServerAPI();
|
||||
useEffect(() => {
|
||||
api.listTemplates().then(templates => setTemplates(templates))
|
||||
}, []);
|
||||
|
||||
return <Card className={className}>
|
||||
<CardHeader className="flex-row justify-between items-center space-x-3 space-y-0">
|
||||
<CardTitle>Agents</CardTitle>
|
||||
|
||||
<div className="flex items-center">{/* Split "Create" button */}
|
||||
<Button variant="outline" className="rounded-r-none" asChild>
|
||||
<Link href="/build">Create</Link>
|
||||
</Button>
|
||||
<Dialog>{/* https://ui.shadcn.com/docs/components/dialog#notes */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className={"rounded-l-none border-l-0 px-2"}>
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem>
|
||||
<EnterIcon className="mr-2" /> Import from file
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
{templates.length > 0 && <>{/* List of templates */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Use a template</DropdownMenuLabel>
|
||||
{templates.map(template => (
|
||||
<DropdownMenuItem
|
||||
key={template.id}
|
||||
onClick={() => {
|
||||
api.createGraph(template.id, template.version)
|
||||
.then(newGraph => {
|
||||
window.location.href = `/build?flowID=${newGraph.id}`;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{template.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader className="text-lg">
|
||||
Import an Agent (template) from a file
|
||||
</DialogHeader>
|
||||
<AgentImportForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
{/* <TableHead>Status</TableHead> */}
|
||||
{/* <TableHead>Last updated</TableHead> */}
|
||||
{flowRuns && <TableHead className="md:hidden lg:table-cell"># of runs</TableHead>}
|
||||
{flowRuns && <TableHead>Last run</TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{flows
|
||||
.map((flow) => {
|
||||
let runCount = 0, lastRun: FlowRun | null = null;
|
||||
if (flowRuns) {
|
||||
const _flowRuns = flowRuns.filter(r => r.graphID == flow.id);
|
||||
runCount = _flowRuns.length;
|
||||
lastRun = runCount == 0 ? null : _flowRuns.reduce(
|
||||
(a, c) => a.startTime > c.startTime ? a : c
|
||||
);
|
||||
}
|
||||
return { flow, runCount, lastRun };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (!a.lastRun && !b.lastRun) return 0;
|
||||
if (!a.lastRun) return 1;
|
||||
if (!b.lastRun) return -1;
|
||||
return b.lastRun.startTime - a.lastRun.startTime;
|
||||
})
|
||||
.map(({ flow, runCount, lastRun }) => (
|
||||
<TableRow
|
||||
key={flow.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSelectFlow(flow)}
|
||||
data-state={selectedFlow?.id == flow.id ? "selected" : null}
|
||||
>
|
||||
<TableCell>{flow.name}</TableCell>
|
||||
{/* <TableCell><FlowStatusBadge status={flow.status ?? "active"} /></TableCell> */}
|
||||
{/* <TableCell>
|
||||
{flow.updatedAt ?? "???"}
|
||||
</TableCell> */}
|
||||
{flowRuns && <TableCell className="md:hidden lg:table-cell">{runCount}</TableCell>}
|
||||
{flowRuns && (!lastRun ? <TableCell /> :
|
||||
<TableCell title={moment(lastRun.startTime).toString()}>
|
||||
{moment(lastRun.startTime).fromNow()}
|
||||
</TableCell>)}
|
||||
</TableRow>
|
||||
))
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
};
|
||||
|
||||
const FlowStatusBadge = ({ status }: { status: "active" | "disabled" | "failing" }) => (
|
||||
<Badge
|
||||
variant="default"
|
||||
className={
|
||||
status === 'active' ? 'bg-green-500 dark:bg-green-600' :
|
||||
status === 'failing' ? 'bg-red-500 dark:bg-red-700' :
|
||||
'bg-gray-500 dark:bg-gray-600'
|
||||
}
|
||||
>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
const FlowRunsList: React.FC<{
|
||||
flows: GraphMeta[];
|
||||
runs: FlowRun[];
|
||||
className?: string;
|
||||
selectedRun?: FlowRun | null;
|
||||
onSelectRun: (r: FlowRun) => void;
|
||||
}> = ({ flows, runs, selectedRun, onSelectRun, className }) => (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>Runs</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Agent</TableHead>
|
||||
<TableHead>Started</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Duration</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{runs.map((run) => (
|
||||
<TableRow
|
||||
key={run.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSelectRun(run)}
|
||||
data-state={selectedRun?.id == run.id ? "selected" : null}
|
||||
>
|
||||
<TableCell>{flows.find(f => f.id == run.graphID)!.name}</TableCell>
|
||||
<TableCell>{moment(run.startTime).format("HH:mm")}</TableCell>
|
||||
<TableCell><FlowRunStatusBadge status={run.status} /></TableCell>
|
||||
<TableCell>{formatDuration(run.duration)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const FlowRunStatusBadge: React.FC<{
|
||||
status: FlowRun['status'];
|
||||
className?: string;
|
||||
}> = ({ status, className }) => (
|
||||
<Badge
|
||||
variant="default"
|
||||
className={cn(
|
||||
status === 'running' ? 'bg-blue-500 dark:bg-blue-700' :
|
||||
status === 'waiting' ? 'bg-yellow-500 dark:bg-yellow-600' :
|
||||
status === 'success' ? 'bg-green-500 dark:bg-green-600' :
|
||||
'bg-red-500 dark:bg-red-700',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
const FlowInfo: React.FC<React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRuns: FlowRun[];
|
||||
flowVersion?: number | "all";
|
||||
}> = ({ flow, flowRuns, flowVersion, ...props }) => {
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
const [flowVersions, setFlowVersions] = useState<GraphMeta[] | null>(null);
|
||||
const [selectedVersion, setSelectedFlowVersion] = useState(flowVersion ?? "all");
|
||||
|
||||
useEffect(() => {
|
||||
api.getGraphAllVersions(flow.id).then(result => setFlowVersions(result));
|
||||
}, [flow.id]);
|
||||
|
||||
return <Card {...props}>
|
||||
<CardHeader className="flex-row justify-between space-y-0 space-x-3">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">Agent ID: <code>{flow.id}</code></p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
{(flowVersions?.length ?? 0) > 1 &&
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<ClockIcon className="mr-2" />
|
||||
{selectedVersion == "all" ? "All versions" : `Version ${selectedVersion}`}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuLabel>Choose a version</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup
|
||||
value={String(selectedVersion)}
|
||||
onValueChange={choice => setSelectedFlowVersion(
|
||||
choice == "all" ? choice : Number(choice)
|
||||
)}
|
||||
>
|
||||
<DropdownMenuRadioItem value="all">All versions</DropdownMenuRadioItem>
|
||||
{flowVersions?.map(v =>
|
||||
<DropdownMenuRadioItem key={v.version} value={v.version.toString()}>
|
||||
Version {v.version}{v.is_active ? " (active)" : ""}
|
||||
</DropdownMenuRadioItem>
|
||||
)}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>}
|
||||
<Link className={buttonVariants({ variant: "outline" })} href={`/build?flowID=${flow.id}`}>
|
||||
<Pencil2Icon className="mr-2" /> Edit
|
||||
</Link>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="px-2.5"
|
||||
title="Export to a JSON-file"
|
||||
onClick={() => exportAsJSONFile(flow, `${flow.name}_v${flow.version}.json`)}
|
||||
>
|
||||
<ExitIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FlowRunsStats
|
||||
flows={[
|
||||
selectedVersion != "all"
|
||||
? flowVersions?.find(v => v.version == selectedVersion)!
|
||||
: flow
|
||||
]}
|
||||
flowRuns={flowRuns.filter(r =>
|
||||
r.graphID == flow.id
|
||||
&& (selectedVersion == "all" || r.graphVersion == selectedVersion)
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>;
|
||||
};
|
||||
|
||||
const FlowRunInfo: React.FC<React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRun: FlowRun;
|
||||
}> = ({ flow, flowRun, ...props }) => {
|
||||
if (flowRun.graphID != flow.id) {
|
||||
throw new Error(`FlowRunInfo can't be used with non-matching flowRun.flowID and flow.id`)
|
||||
}
|
||||
|
||||
return <Card {...props}>
|
||||
<CardHeader className="flex-row items-center justify-between space-y-0 space-x-3">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">Agent ID: <code>{flow.id}</code></p>
|
||||
<p className="mt-1">Run ID: <code>{flowRun.id}</code></p>
|
||||
</div>
|
||||
<Link className={buttonVariants({ variant: "outline" })} href={`/build?flowID=${flow.id}`}>
|
||||
<Pencil2Icon className="mr-2" /> Edit Agent
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p><strong>Status:</strong> <FlowRunStatusBadge status={flowRun.status} /></p>
|
||||
<p><strong>Started:</strong> {moment(flowRun.startTime).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
<p><strong>Finished:</strong> {moment(flowRun.endTime).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
<p><strong>Duration (run time):</strong> {flowRun.duration} ({flowRun.totalRunTime}) seconds</p>
|
||||
{/* <p><strong>Total cost:</strong> €1,23</p> */}
|
||||
</CardContent>
|
||||
</Card>;
|
||||
};
|
||||
|
||||
const FlowRunsStats: React.FC<{
|
||||
flows: GraphMeta[],
|
||||
flowRuns: FlowRun[],
|
||||
title?: string,
|
||||
className?: string,
|
||||
}> = ({ flows, flowRuns, title, className }) => {
|
||||
/* "dateMin": since the first flow in the dataset
|
||||
* number > 0: custom date (unix timestamp)
|
||||
* number < 0: offset relative to Date.now() (in seconds) */
|
||||
const [statsSince, setStatsSince] = useState<number | "dataMin">(-24*3600)
|
||||
const statsSinceTimestamp = ( // unix timestamp or null
|
||||
typeof(statsSince) == "string"
|
||||
? null
|
||||
: statsSince < 0
|
||||
? Date.now() + (statsSince*1000)
|
||||
: statsSince
|
||||
)
|
||||
const filteredFlowRuns = statsSinceTimestamp != null
|
||||
? flowRuns.filter(fr => fr.startTime > statsSinceTimestamp)
|
||||
: flowRuns;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<CardTitle>{ title || "Stats" }</CardTitle>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setStatsSince(-2*3600)}>2h</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setStatsSince(-8*3600)}>8h</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setStatsSince(-24*3600)}>24h</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setStatsSince(-7*24*3600)}>7d</Button>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant={"outline"} size="sm">Custom</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
onSelect={(_, selectedDay) => setStatsSince(selectedDay.getTime())}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button variant="outline" size="sm" onClick={() => setStatsSince("dataMin")}>All</Button>
|
||||
</div>
|
||||
</div>
|
||||
<FlowRunsTimeline flows={flows} flowRuns={flowRuns} dataMin={statsSince} className="mt-3" />
|
||||
<hr className="my-4" />
|
||||
<div>
|
||||
<p><strong>Total runs:</strong> {filteredFlowRuns.length}</p>
|
||||
<p>
|
||||
<strong>Total run time:</strong> {
|
||||
filteredFlowRuns.reduce((total, run) => total + run.totalRunTime, 0)
|
||||
} seconds
|
||||
</p>
|
||||
{/* <p><strong>Total cost:</strong> €1,23</p> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FlowRunsTimeline = (
|
||||
{ flows, flowRuns, dataMin, className }: {
|
||||
flows: GraphMeta[],
|
||||
flowRuns: FlowRun[],
|
||||
dataMin: "dataMin" | number,
|
||||
className?: string,
|
||||
}
|
||||
) => (
|
||||
/* TODO: make logarithmic? */
|
||||
<ResponsiveContainer width="100%" height={120} className={className}>
|
||||
<ComposedChart>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
domain={[
|
||||
typeof(dataMin) == "string"
|
||||
? dataMin
|
||||
: dataMin < 0
|
||||
? Date.now() + (dataMin*1000)
|
||||
: dataMin,
|
||||
Date.now()
|
||||
]}
|
||||
allowDataOverflow={true}
|
||||
tickFormatter={(unixTime) => {
|
||||
const now = moment();
|
||||
const time = moment(unixTime);
|
||||
return now.diff(time, 'hours') < 24
|
||||
? time.format('HH:mm')
|
||||
: time.format('YYYY-MM-DD HH:mm');
|
||||
}}
|
||||
name="Time"
|
||||
scale="time"
|
||||
/>
|
||||
<YAxis
|
||||
dataKey="_duration"
|
||||
name="Duration (s)"
|
||||
tickFormatter={s => s > 90 ? `${Math.round(s / 60)}m` : `${s}s`}
|
||||
/>
|
||||
<Tooltip
|
||||
content={({ payload, label }) => {
|
||||
if (payload && payload.length) {
|
||||
const data: FlowRun & { time: number, _duration: number } = payload[0].payload;
|
||||
const flow = flows.find(f => f.id === data.graphID);
|
||||
return (
|
||||
<Card className="p-2 text-xs leading-normal">
|
||||
<p><strong>Agent:</strong> {flow ? flow.name : 'Unknown'}</p>
|
||||
<p>
|
||||
<strong>Status:</strong>
|
||||
<FlowRunStatusBadge status={data.status} className="px-1.5 py-0" />
|
||||
</p>
|
||||
<p><strong>Started:</strong> {moment(data.startTime).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||
<p><strong>Duration / run time:</strong> {
|
||||
formatDuration(data.duration)} / {formatDuration(data.totalRunTime)
|
||||
}</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
{flows.map((flow) => (
|
||||
<Scatter
|
||||
key={flow.id}
|
||||
data={flowRuns.filter(fr => fr.graphID == flow.id).map(fr => ({
|
||||
...fr,
|
||||
time: fr.startTime + (fr.totalRunTime * 1000),
|
||||
_duration: fr.totalRunTime,
|
||||
}))}
|
||||
name={flow.name}
|
||||
fill={`hsl(${hashString(flow.id) * 137.5 % 360}, 70%, 50%)`}
|
||||
/>
|
||||
))}
|
||||
{flowRuns.map((run) => (
|
||||
<Line
|
||||
key={run.id}
|
||||
type="linear"
|
||||
dataKey="_duration"
|
||||
data={[
|
||||
{ ...run, time: run.startTime, _duration: 0 },
|
||||
{ ...run, time: run.endTime, _duration: run.totalRunTime }
|
||||
]}
|
||||
stroke={`hsl(${hashString(run.graphID) * 137.5 % 360}, 70%, 50%)`}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
legendType="none"
|
||||
/>
|
||||
))}
|
||||
<Legend
|
||||
content={<ScrollableLegend />}
|
||||
wrapperStyle={{
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
const ScrollableLegend: React.FC<DefaultLegendContentProps & { className?: string }> = (
|
||||
{ payload, className }
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"whitespace-nowrap px-4 text-sm overflow-x-auto space-x-3",
|
||||
className,
|
||||
)}
|
||||
style={{ scrollbarWidth: "none" }}
|
||||
>
|
||||
{payload.map((entry, index) => {
|
||||
if (entry.type == "none") return;
|
||||
return (
|
||||
<span key={`item-${index}`} className="inline-flex items-center">
|
||||
<span
|
||||
className="size-2.5 inline-block mr-1 rounded-full"
|
||||
style={{backgroundColor: entry.color}}
|
||||
/>
|
||||
<span>{entry.value}</span>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
return (
|
||||
seconds < 100
|
||||
? seconds.toPrecision(2)
|
||||
: Math.round(seconds)
|
||||
).toString() + "s";
|
||||
}
|
||||
|
||||
export default Monitor;
|
||||
178
rnd/autogpt_builder/src/app/page.tsx
Normal file
178
rnd/autogpt_builder/src/app/page.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
"use client";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import AutoGPTServerAPI, {
|
||||
GraphMeta,
|
||||
NodeExecutionResult,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import {
|
||||
AgentFlowList,
|
||||
FlowInfo,
|
||||
FlowRunInfo,
|
||||
FlowRunsList,
|
||||
FlowRunsStats,
|
||||
} from "@/components/monitor";
|
||||
|
||||
const Monitor = () => {
|
||||
const [flows, setFlows] = useState<GraphMeta[]>([]);
|
||||
const [flowRuns, setFlowRuns] = useState<FlowRun[]>([]);
|
||||
const [selectedFlow, setSelectedFlow] = useState<GraphMeta | null>(null);
|
||||
const [selectedRun, setSelectedRun] = useState<FlowRun | null>(null);
|
||||
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
|
||||
const refreshFlowRuns = useCallback(
|
||||
(flowID: string) => {
|
||||
// Fetch flow run IDs
|
||||
api.listGraphRunIDs(flowID).then((runIDs) =>
|
||||
runIDs.map((runID) => {
|
||||
let run;
|
||||
if (
|
||||
(run = flowRuns.find((fr) => fr.id == runID)) &&
|
||||
!["waiting", "running"].includes(run.status)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch flow run
|
||||
api.getGraphExecutionInfo(flowID, runID).then((execInfo) =>
|
||||
setFlowRuns((flowRuns) => {
|
||||
if (execInfo.length == 0) return flowRuns;
|
||||
|
||||
const flowRunIndex = flowRuns.findIndex((fr) => fr.id == runID);
|
||||
const flowRun = flowRunFromNodeExecutionResults(execInfo);
|
||||
if (flowRunIndex > -1) {
|
||||
flowRuns.splice(flowRunIndex, 1, flowRun);
|
||||
} else {
|
||||
flowRuns.push(flowRun);
|
||||
}
|
||||
return [...flowRuns];
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
[api, flowRuns],
|
||||
);
|
||||
|
||||
const fetchFlowsAndRuns = useCallback(() => {
|
||||
api.listGraphs().then((flows) => {
|
||||
setFlows(flows);
|
||||
flows.map((flow) => refreshFlowRuns(flow.id));
|
||||
});
|
||||
}, [api, refreshFlowRuns]);
|
||||
|
||||
useEffect(() => fetchFlowsAndRuns(), [fetchFlowsAndRuns]);
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(
|
||||
() => flows.map((f) => refreshFlowRuns(f.id)),
|
||||
5000,
|
||||
);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [flows, refreshFlowRuns]);
|
||||
|
||||
const column1 = "md:col-span-2 xl:col-span-3 xxl:col-span-2";
|
||||
const column2 = "md:col-span-3 lg:col-span-2 xl:col-span-3 space-y-4";
|
||||
const column3 = "col-span-full xl:col-span-4 xxl:col-span-5";
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-5 lg:grid-cols-4 xl:grid-cols-10">
|
||||
<AgentFlowList
|
||||
className={column1}
|
||||
flows={flows}
|
||||
flowRuns={flowRuns}
|
||||
selectedFlow={selectedFlow}
|
||||
onSelectFlow={(f) => {
|
||||
setSelectedRun(null);
|
||||
setSelectedFlow(f.id == selectedFlow?.id ? null : f);
|
||||
}}
|
||||
/>
|
||||
<FlowRunsList
|
||||
className={column2}
|
||||
flows={flows}
|
||||
runs={[
|
||||
...(selectedFlow
|
||||
? flowRuns.filter((v) => v.graphID == selectedFlow.id)
|
||||
: flowRuns),
|
||||
].sort((a, b) => Number(a.startTime) - Number(b.startTime))}
|
||||
selectedRun={selectedRun}
|
||||
onSelectRun={(r) => setSelectedRun(r.id == selectedRun?.id ? null : r)}
|
||||
/>
|
||||
{(selectedRun && (
|
||||
<FlowRunInfo
|
||||
flow={selectedFlow || flows.find((f) => f.id == selectedRun.graphID)!}
|
||||
flowRun={selectedRun}
|
||||
className={column3}
|
||||
/>
|
||||
)) ||
|
||||
(selectedFlow && (
|
||||
<FlowInfo
|
||||
flow={selectedFlow}
|
||||
flowRuns={flowRuns.filter((r) => r.graphID == selectedFlow.id)}
|
||||
className={column3}
|
||||
/>
|
||||
)) || (
|
||||
<Card className={`p-6 ${column3}`}>
|
||||
<FlowRunsStats flows={flows} flowRuns={flowRuns} />
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function flowRunFromNodeExecutionResults(
|
||||
nodeExecutionResults: NodeExecutionResult[],
|
||||
): FlowRun {
|
||||
// Determine overall status
|
||||
let status: "running" | "waiting" | "success" | "failed" = "success";
|
||||
for (const execution of nodeExecutionResults) {
|
||||
if (execution.status === "FAILED") {
|
||||
status = "failed";
|
||||
break;
|
||||
} else if (["QUEUED", "RUNNING"].includes(execution.status)) {
|
||||
status = "running";
|
||||
break;
|
||||
} else if (execution.status === "INCOMPLETE") {
|
||||
status = "waiting";
|
||||
}
|
||||
}
|
||||
|
||||
// Determine aggregate startTime, endTime, and totalRunTime
|
||||
const now = Date.now();
|
||||
const startTime = Math.min(
|
||||
...nodeExecutionResults.map((ner) => ner.add_time.getTime()),
|
||||
now,
|
||||
);
|
||||
const endTime = ["success", "failed"].includes(status)
|
||||
? Math.max(
|
||||
...nodeExecutionResults.map((ner) => ner.end_time?.getTime() || 0),
|
||||
startTime,
|
||||
)
|
||||
: now;
|
||||
const duration = (endTime - startTime) / 1000; // Convert to seconds
|
||||
const totalRunTime =
|
||||
nodeExecutionResults.reduce(
|
||||
(cum, node) =>
|
||||
cum +
|
||||
((node.end_time?.getTime() ?? now) -
|
||||
(node.start_time?.getTime() ?? now)),
|
||||
0,
|
||||
) / 1000;
|
||||
|
||||
return {
|
||||
id: nodeExecutionResults[0].graph_exec_id,
|
||||
graphID: nodeExecutionResults[0].graph_id,
|
||||
graphVersion: nodeExecutionResults[0].graph_version,
|
||||
status,
|
||||
startTime,
|
||||
endTime,
|
||||
duration,
|
||||
totalRunTime,
|
||||
nodeExecutionResults: nodeExecutionResults,
|
||||
};
|
||||
}
|
||||
|
||||
export default Monitor;
|
||||
33
rnd/autogpt_builder/src/app/profile/page.tsx
Normal file
33
rnd/autogpt_builder/src/app/profile/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { useSupabase } from "@/components/SupabaseProvider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useUser from "@/hooks/useUser";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { FaSpinner } from "react-icons/fa";
|
||||
|
||||
export default function PrivatePage() {
|
||||
const { user, isLoading, error } = useUser();
|
||||
const { supabase } = useSupabase();
|
||||
const router = useRouter();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-[80vh] items-center justify-center">
|
||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !user || !supabase) {
|
||||
router.push("/login");
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Hello {user.email}</p>
|
||||
<Button onClick={() => supabase.auth.signOut()}>Log out</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
20
rnd/autogpt_builder/src/app/providers.tsx
Normal file
20
rnd/autogpt_builder/src/app/providers.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { ThemeProviderProps } from "next-themes/dist/types";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import SupabaseProvider from "@/components/SupabaseProvider";
|
||||
import { PageViewProvider } from "@/components/providers/PageViewProvider";
|
||||
|
||||
export function Providers({ children, ...props }: ThemeProviderProps) {
|
||||
return (
|
||||
<NextThemesProvider {...props}>
|
||||
<SupabaseProvider>
|
||||
<PageViewProvider>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</PageViewProvider>
|
||||
</SupabaseProvider>
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
9
rnd/autogpt_builder/src/app/unauthorized/page.tsx
Normal file
9
rnd/autogpt_builder/src/app/unauthorized/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
// app/unauthorized/page.tsx
|
||||
export default function Unauthorized() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Unauthorized Access</h1>
|
||||
<p>You do not have permission to view this page.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
rnd/autogpt_builder/src/components/ConnectionLine.tsx
Normal file
34
rnd/autogpt_builder/src/components/ConnectionLine.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
BaseEdge,
|
||||
ConnectionLineComponentProps,
|
||||
getBezierPath,
|
||||
Position,
|
||||
} from "@xyflow/react";
|
||||
|
||||
const ConnectionLine: React.FC<ConnectionLineComponentProps> = ({
|
||||
fromPosition,
|
||||
fromHandle,
|
||||
fromX,
|
||||
fromY,
|
||||
toPosition,
|
||||
toX,
|
||||
toY,
|
||||
}) => {
|
||||
const sourceX =
|
||||
fromPosition === Position.Right
|
||||
? fromX + (fromHandle?.width! / 2 - 5)
|
||||
: fromX - (fromHandle?.width! / 2 - 5);
|
||||
|
||||
const [path] = getBezierPath({
|
||||
sourceX: sourceX,
|
||||
sourceY: fromY,
|
||||
sourcePosition: fromPosition,
|
||||
targetX: toX,
|
||||
targetY: toY,
|
||||
targetPosition: toPosition,
|
||||
});
|
||||
|
||||
return <BaseEdge path={path} style={{ strokeWidth: 2, stroke: "#555" }} />;
|
||||
};
|
||||
|
||||
export default ConnectionLine;
|
||||
235
rnd/autogpt_builder/src/components/CustomEdge.tsx
Normal file
235
rnd/autogpt_builder/src/components/CustomEdge.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
useReactFlow,
|
||||
XYPosition,
|
||||
Edge,
|
||||
Node,
|
||||
} from "@xyflow/react";
|
||||
import "./customedge.css";
|
||||
import { X } from "lucide-react";
|
||||
import { useBezierPath } from "@/hooks/useBezierPath";
|
||||
import { FlowContext } from "./Flow";
|
||||
|
||||
export type CustomEdgeData = {
|
||||
edgeColor: string;
|
||||
sourcePos?: XYPosition;
|
||||
isStatic?: boolean;
|
||||
beadUp?: number;
|
||||
beadDown?: number;
|
||||
beadData?: any[];
|
||||
};
|
||||
|
||||
type Bead = {
|
||||
t: number;
|
||||
targetT: number;
|
||||
startTime: number;
|
||||
};
|
||||
|
||||
export type CustomEdge = Edge<CustomEdgeData, "custom">;
|
||||
|
||||
export function CustomEdge({
|
||||
id,
|
||||
data,
|
||||
selected,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
markerEnd,
|
||||
}: EdgeProps<CustomEdge>) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [beads, setBeads] = useState<{
|
||||
beads: Bead[];
|
||||
created: number;
|
||||
destroyed: number;
|
||||
}>({ beads: [], created: 0, destroyed: 0 });
|
||||
const { svgPath, length, getPointForT, getTForDistance } = useBezierPath(
|
||||
sourceX - 5,
|
||||
sourceY,
|
||||
targetX + 3,
|
||||
targetY,
|
||||
);
|
||||
const { deleteElements } = useReactFlow<Node, CustomEdge>();
|
||||
const { visualizeBeads } = useContext(FlowContext) ?? {
|
||||
visualizeBeads: "no",
|
||||
};
|
||||
|
||||
const onEdgeRemoveClick = () => {
|
||||
deleteElements({ edges: [{ id }] });
|
||||
};
|
||||
|
||||
const animationDuration = 500; // Duration in milliseconds for bead to travel the curve
|
||||
const beadDiameter = 12;
|
||||
const deltaTime = 16;
|
||||
|
||||
const setTargetPositions = useCallback(
|
||||
(beads: Bead[]) => {
|
||||
const distanceBetween = Math.min(
|
||||
(length - beadDiameter) / (beads.length + 1),
|
||||
beadDiameter,
|
||||
);
|
||||
|
||||
return beads.map((bead, index) => {
|
||||
const distanceFromEnd = beadDiameter * 1.35;
|
||||
const targetPosition = distanceBetween * index + distanceFromEnd;
|
||||
const t = getTForDistance(-targetPosition);
|
||||
|
||||
return {
|
||||
...bead,
|
||||
t: visualizeBeads === "animate" ? bead.t : t,
|
||||
targetT: t,
|
||||
} as Bead;
|
||||
});
|
||||
},
|
||||
[getTForDistance, length, visualizeBeads],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.beadUp === 0 && data?.beadDown === 0) {
|
||||
setBeads({ beads: [], created: 0, destroyed: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
const beadUp = data?.beadUp!;
|
||||
|
||||
// Add beads
|
||||
setBeads(({ beads, created, destroyed }) => {
|
||||
const newBeads = [];
|
||||
for (let i = 0; i < beadUp - created; i++) {
|
||||
newBeads.push({ t: 0, targetT: 0, startTime: Date.now() });
|
||||
}
|
||||
|
||||
const b = setTargetPositions([...beads, ...newBeads]);
|
||||
return { beads: b, created: beadUp, destroyed };
|
||||
});
|
||||
|
||||
// Remove beads if not animating
|
||||
if (visualizeBeads !== "animate") {
|
||||
setBeads(({ beads, created, destroyed }) => {
|
||||
let destroyedCount = 0;
|
||||
|
||||
const newBeads = beads
|
||||
.map((bead) => ({ ...bead }))
|
||||
.filter((bead, index) => {
|
||||
const beadDown = data?.beadDown!;
|
||||
|
||||
// Remove always one less bead in case of static edge, so it stays at the connection point
|
||||
const removeCount = beadDown - destroyed - (data?.isStatic ? 1 : 0);
|
||||
if (bead.t >= bead.targetT && index < removeCount) {
|
||||
destroyedCount++;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
beads: setTargetPositions(newBeads),
|
||||
created,
|
||||
destroyed: destroyed + destroyedCount,
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Animate and remove beads
|
||||
const interval = setInterval(() => {
|
||||
setBeads(({ beads, created, destroyed }) => {
|
||||
let destroyedCount = 0;
|
||||
|
||||
const newBeads = beads
|
||||
.map((bead) => {
|
||||
const progressIncrement = deltaTime / animationDuration;
|
||||
const t = Math.min(
|
||||
bead.t + bead.targetT * progressIncrement,
|
||||
bead.targetT,
|
||||
);
|
||||
|
||||
return {
|
||||
...bead,
|
||||
t,
|
||||
};
|
||||
})
|
||||
.filter((bead, index) => {
|
||||
const beadDown = data?.beadDown!;
|
||||
|
||||
// Remove always one less bead in case of static edge, so it stays at the connection point
|
||||
const removeCount = beadDown - destroyed - (data?.isStatic ? 1 : 0);
|
||||
if (bead.t >= bead.targetT && index < removeCount) {
|
||||
destroyedCount++;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
beads: setTargetPositions(newBeads),
|
||||
created,
|
||||
destroyed: destroyed + destroyedCount,
|
||||
};
|
||||
});
|
||||
}, deltaTime);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [data, setTargetPositions, visualizeBeads]);
|
||||
|
||||
const middle = getPointForT(0.5);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d={svgPath}
|
||||
fill="none"
|
||||
strokeOpacity={0}
|
||||
strokeWidth={20}
|
||||
className="react-flow__edge-interaction"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
/>
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
transform: `translate(-50%, -50%) translate(${middle.x}px,${middle.y}px)`,
|
||||
pointerEvents: "all",
|
||||
}}
|
||||
className="edge-label-renderer"
|
||||
>
|
||||
<button
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className={`edge-label-button ${isHovered ? "visible" : ""}`}
|
||||
onClick={onEdgeRemoveClick}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
{beads.beads.map((bead, index) => {
|
||||
const pos = getPointForT(bead.t);
|
||||
return (
|
||||
<circle
|
||||
key={index}
|
||||
cx={pos.x}
|
||||
cy={pos.y}
|
||||
r={beadDiameter / 2} // Bead radius
|
||||
fill={data?.edgeColor ?? "#555555"}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
92
rnd/autogpt_builder/src/components/DataTable.tsx
Normal file
92
rnd/autogpt_builder/src/components/DataTable.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { beautifyString } from "@/lib/utils";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "./ui/table";
|
||||
import { Clipboard } from "lucide-react";
|
||||
import { useToast } from "./ui/use-toast";
|
||||
|
||||
type DataTableProps = {
|
||||
title?: string;
|
||||
truncateLongData?: boolean;
|
||||
data: { [key: string]: Array<any> };
|
||||
};
|
||||
|
||||
export default function DataTable({
|
||||
title,
|
||||
truncateLongData,
|
||||
data,
|
||||
}: DataTableProps) {
|
||||
const { toast } = useToast();
|
||||
const maxChars = 100;
|
||||
|
||||
const copyData = (pin: string, data: string) => {
|
||||
navigator.clipboard.writeText(data).then(() => {
|
||||
toast({
|
||||
title: `"${pin}" data copied to clipboard!`,
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{title && <strong className="mt-2 flex justify-center">{title}</strong>}
|
||||
<Table className="cursor-default select-text">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Pin</TableHead>
|
||||
<TableHead>Data</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{Object.entries(data).map(([key, value]) => (
|
||||
<TableRow className="group" key={key}>
|
||||
<TableCell className="cursor-text">
|
||||
{beautifyString(key)}
|
||||
</TableCell>
|
||||
<TableCell className="cursor-text">
|
||||
<div className="flex min-h-9 items-center">
|
||||
<Button
|
||||
className="absolute right-1 top-auto m-1 hidden p-2 group-hover:block"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
copyData(
|
||||
beautifyString(key),
|
||||
value
|
||||
.map((i) =>
|
||||
typeof i === "object"
|
||||
? JSON.stringify(i)
|
||||
: String(i),
|
||||
)
|
||||
.join(", "),
|
||||
)
|
||||
}
|
||||
title="Copy Data"
|
||||
>
|
||||
<Clipboard size={18} />
|
||||
</Button>
|
||||
{value
|
||||
.map((i) => {
|
||||
const text =
|
||||
typeof i === "object" ? JSON.stringify(i) : String(i);
|
||||
return truncateLongData && text.length > maxChars
|
||||
? text.slice(0, maxChars) + "..."
|
||||
: text;
|
||||
})
|
||||
.join(", ")}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
107
rnd/autogpt_builder/src/components/InputModalComponent.tsx
Normal file
107
rnd/autogpt_builder/src/components/InputModalComponent.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Textarea } from "./ui/textarea";
|
||||
import { Maximize2, Minimize2, Clipboard } from "lucide-react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { toast } from "./ui/use-toast";
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (value: string) => void;
|
||||
title?: string;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
const InputModalComponent: FC<ModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
title,
|
||||
defaultValue,
|
||||
}) => {
|
||||
const [tempValue, setTempValue] = useState(defaultValue);
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setTempValue(defaultValue);
|
||||
setIsMaximized(false);
|
||||
}
|
||||
}, [isOpen, defaultValue]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(tempValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const toggleSize = () => {
|
||||
setIsMaximized(!isMaximized);
|
||||
};
|
||||
|
||||
const copyValue = () => {
|
||||
navigator.clipboard.writeText(tempValue).then(() => {
|
||||
toast({
|
||||
title: "Input value copied to clipboard!",
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const modalContent = (
|
||||
<div
|
||||
id="modal-content"
|
||||
className={`fixed rounded-lg border-[1.5px] bg-white p-5 ${
|
||||
isMaximized ? "inset-[128px] flex flex-col" : `w-[90%] max-w-[800px]`
|
||||
}`}
|
||||
>
|
||||
<h2 className="mb-4 text-center text-lg font-semibold">
|
||||
{title || "Enter input text"}
|
||||
</h2>
|
||||
<div className="nowheel relative flex-grow">
|
||||
<Textarea
|
||||
className="h-full min-h-[200px] w-full resize-none"
|
||||
value={tempValue}
|
||||
onChange={(e) => setTempValue(e.target.value)}
|
||||
/>
|
||||
<div className="absolute bottom-2 right-2 flex space-x-2">
|
||||
<Button onClick={copyValue} size="icon" variant="outline">
|
||||
<Clipboard size={18} />
|
||||
</Button>
|
||||
<Button onClick={toggleSize} size="icon" variant="outline">
|
||||
{isMaximized ? <Minimize2 size={18} /> : <Maximize2 size={18} />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end space-x-2">
|
||||
<Button onClick={onClose} variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMaximized ? (
|
||||
createPortal(
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-white bg-opacity-60">
|
||||
{modalContent}
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
) : (
|
||||
<div className="nodrag fixed inset-0 flex items-center justify-center bg-white bg-opacity-60">
|
||||
{modalContent}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputModalComponent;
|
||||
@@ -1,48 +0,0 @@
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { Textarea } from './ui/textarea';
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (value: string) => void;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const ModalComponent: FC<ModalProps> = ({ isOpen, onClose, onSave, value }) => {
|
||||
const [tempValue, setTempValue] = React.useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setTempValue(value);
|
||||
}
|
||||
}, [isOpen, value]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(tempValue);
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-white bg-opacity-60 flex justify-center items-center">
|
||||
<div className="bg-white p-5 rounded-lg w-[500px] max-w-[90%]">
|
||||
<center><h1>Enter input text</h1></center>
|
||||
<Textarea
|
||||
className="w-full h-[200px] p-2.5 rounded border border-[#dfdfdf] text-black bg-[#dfdfdf]"
|
||||
value={tempValue}
|
||||
onChange={(e) => setTempValue(e.target.value)}
|
||||
/>
|
||||
<div className="flex justify-end gap-2.5 mt-2.5">
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={handleSave}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalComponent;
|
||||
112
rnd/autogpt_builder/src/components/NavBar.tsx
Normal file
112
rnd/autogpt_builder/src/components/NavBar.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
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 "@/hooks/getServerUser";
|
||||
import ProfileDropdown from "./ProfileDropdown";
|
||||
import {
|
||||
IconCircleUser,
|
||||
IconMenu,
|
||||
IconPackage2,
|
||||
IconSquareActivity,
|
||||
IconWorkFlow,
|
||||
} from "@/components/ui/icons";
|
||||
|
||||
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 (
|
||||
<header className="sticky top-0 z-50 flex h-16 items-center gap-4 border-b bg-background px-4 md:px-6">
|
||||
<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">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex flex-row gap-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconSquareActivity /> Monitor
|
||||
</Link>
|
||||
<Link
|
||||
href="/build"
|
||||
className="flex flex-row gap-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconWorkFlow /> Build
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace"
|
||||
className="flex flex-row gap-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconPackage2 /> Marketplace
|
||||
</Link>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<nav className="hidden md:flex md:flex-row md:items-center md:gap-5 lg:gap-6">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex flex-row items-center gap-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconSquareActivity /> Monitor
|
||||
</Link>
|
||||
<Link
|
||||
href="/build"
|
||||
className="flex flex-row items-center gap-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconWorkFlow /> Build
|
||||
</Link>
|
||||
<Link
|
||||
href="/marketplace"
|
||||
className="flex flex-row items-center gap-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconPackage2 /> Marketplace
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="relative flex flex-1 justify-center">
|
||||
<a
|
||||
className="pointer-events-auto flex place-items-center gap-2"
|
||||
href="https://news.agpt.co/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/AUTOgpt_Logo_dark.png"
|
||||
alt="AutoGPT Logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-end gap-4">
|
||||
{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>
|
||||
);
|
||||
}
|
||||
85
rnd/autogpt_builder/src/components/NodeHandle.tsx
Normal file
85
rnd/autogpt_builder/src/components/NodeHandle.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { BlockIOSubSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { beautifyString, getTypeBgColor, getTypeTextColor } from "@/lib/utils";
|
||||
import { FC } from "react";
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
import SchemaTooltip from "./SchemaTooltip";
|
||||
|
||||
type HandleProps = {
|
||||
keyName: string;
|
||||
schema: BlockIOSubSchema;
|
||||
isConnected: boolean;
|
||||
isRequired?: boolean;
|
||||
side: "left" | "right";
|
||||
};
|
||||
|
||||
const NodeHandle: FC<HandleProps> = ({
|
||||
keyName,
|
||||
schema,
|
||||
isConnected,
|
||||
isRequired,
|
||||
side,
|
||||
}) => {
|
||||
const typeName: Record<string, string> = {
|
||||
string: "text",
|
||||
number: "number",
|
||||
boolean: "true/false",
|
||||
object: "object",
|
||||
array: "list",
|
||||
null: "null",
|
||||
};
|
||||
|
||||
const typeClass = `text-sm ${getTypeTextColor(schema.type || "any")} ${side === "left" ? "text-left" : "text-right"}`;
|
||||
|
||||
const label = (
|
||||
<div className="flex flex-grow flex-col">
|
||||
<span className="text-m green -mb-1 text-gray-900">
|
||||
{schema.title || beautifyString(keyName)}
|
||||
{isRequired ? "*" : ""}
|
||||
</span>
|
||||
<span className={typeClass}>{typeName[schema.type] || "any"}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const dot = (
|
||||
<div
|
||||
className={`m-1 h-4 w-4 border-2 bg-white ${isConnected ? getTypeBgColor(schema.type || "any") : "border-gray-300"} rounded-full transition-colors duration-100 group-hover:bg-gray-300`}
|
||||
/>
|
||||
);
|
||||
|
||||
if (side === "left") {
|
||||
return (
|
||||
<div key={keyName} className="handle-container">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={keyName}
|
||||
className="background-color: white; border: 2px solid black; width: 15px; height: 15px; border-radius: 50%; bottom: -7px; left: 20%; group -ml-[26px]"
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
{dot}
|
||||
{label}
|
||||
</div>
|
||||
</Handle>
|
||||
<SchemaTooltip description={schema.description} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div key={keyName} className="handle-container justify-end">
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={keyName}
|
||||
className="group -mr-[26px]"
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
{label}
|
||||
{dot}
|
||||
</div>
|
||||
</Handle>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default NodeHandle;
|
||||
45
rnd/autogpt_builder/src/components/OutputModalComponent.tsx
Normal file
45
rnd/autogpt_builder/src/components/OutputModalComponent.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { FC } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { NodeExecutionResult } from "@/lib/autogpt-server-api/types";
|
||||
import DataTable from "./DataTable";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
interface OutputModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
executionResults: {
|
||||
execId: string;
|
||||
data: NodeExecutionResult["output_data"];
|
||||
}[];
|
||||
}
|
||||
|
||||
const OutputModalComponent: FC<OutputModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
executionResults,
|
||||
}) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="nodrag nowheel fixed inset-0 flex items-center justify-center bg-white bg-opacity-60">
|
||||
<div className="w-[500px] max-w-[90%] rounded-lg border-[1.5px] bg-white p-5">
|
||||
<strong>Output Data History</strong>
|
||||
<div className="my-2 max-h-[384px] flex-grow overflow-y-auto rounded-md border-[1.5px] p-2">
|
||||
{executionResults.map((data, i) => (
|
||||
<>
|
||||
<DataTable key={i} title={data.execId} data={data.data} />
|
||||
<Separator />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-2.5 flex justify-end gap-2.5">
|
||||
<Button onClick={onClose}>Close</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutputModalComponent;
|
||||
54
rnd/autogpt_builder/src/components/PasswordInput.tsx
Normal file
54
rnd/autogpt_builder/src/components/PasswordInput.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { forwardRef, useState } from "react";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input, InputProps } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const PasswordInput = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const disabled =
|
||||
props.value === "" || props.value === undefined || props.disabled;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
className={cn("hide-password-toggle pr-10", className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
||||
onClick={() => setShowPassword((prev) => !prev)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{showPassword && !disabled ? (
|
||||
<EyeIcon className="h-4 w-4" aria-hidden="true" />
|
||||
) : (
|
||||
<EyeOffIcon className="h-4 w-4" aria-hidden="true" />
|
||||
)}
|
||||
<span className="sr-only">
|
||||
{showPassword ? "Hide password" : "Show password"}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{/* hides browsers password toggles */}
|
||||
<style>{`
|
||||
.hide-password-toggle::-ms-reveal,
|
||||
.hide-password-toggle::-ms-clear {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
PasswordInput.displayName = "PasswordInput";
|
||||
|
||||
export { PasswordInput };
|
||||
53
rnd/autogpt_builder/src/components/ProfileDropdown.tsx
Normal file
53
rnd/autogpt_builder/src/components/ProfileDropdown.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "./ui/button";
|
||||
import { useSupabase } from "./SupabaseProvider";
|
||||
import { useRouter } from "next/navigation";
|
||||
import useUser from "@/hooks/useUser";
|
||||
|
||||
const ProfileDropdown = () => {
|
||||
const { supabase } = useSupabase();
|
||||
const router = useRouter();
|
||||
const { user, role, isLoading } = useUser();
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 rounded-full">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={user?.user_metadata["avatar_url"]}
|
||||
alt="User Avatar"
|
||||
/>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => router.push("/profile")}>
|
||||
Profile
|
||||
</DropdownMenuItem>
|
||||
{role === "admin" && (
|
||||
<DropdownMenuItem onClick={() => router.push("/admin/dashboard")}>
|
||||
Admin Dashboard
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => supabase?.auth.signOut()}>
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileDropdown;
|
||||
27
rnd/autogpt_builder/src/components/RoleBasedAccess.tsx
Normal file
27
rnd/autogpt_builder/src/components/RoleBasedAccess.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
// components/RoleBasedAccess.tsx
|
||||
import React from "react";
|
||||
import useUser from "@/hooks/useUser";
|
||||
|
||||
interface RoleBasedAccessProps {
|
||||
allowedRoles: string[];
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const RoleBasedAccess: React.FC<RoleBasedAccessProps> = ({
|
||||
allowedRoles,
|
||||
children,
|
||||
}) => {
|
||||
const { role, isLoading } = useUser();
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!role || !allowedRoles.includes(role)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default RoleBasedAccess;
|
||||
@@ -3,28 +3,37 @@ import {
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import { BlockSchema } from "@/lib/types";
|
||||
import { Info } from 'lucide-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Info } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
const SchemaTooltip: React.FC<{ schema: BlockSchema }> = ({ schema }) => {
|
||||
if (!schema.description) return null;
|
||||
const SchemaTooltip: React.FC<{ description?: string }> = ({ description }) => {
|
||||
if (!description) return null;
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={400}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="flex items-center justify-center" asChild>
|
||||
<Info className="p-1 rounded-full hover:bg-gray-300" size={24} />
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="rounded-full p-1 hover:bg-gray-300" size={24} />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs tooltip-content">
|
||||
<ReactMarkdown components={{
|
||||
a: ({ node, ...props }) => <a className="text-blue-400 underline" {...props} />,
|
||||
}}>{schema.description}</ReactMarkdown>
|
||||
<TooltipContent className="tooltip-content max-w-xs">
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
a: ({ node, ...props }) => (
|
||||
<a
|
||||
target="_blank"
|
||||
className="text-blue-400 underline"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SchemaTooltip;
|
||||
|
||||
65
rnd/autogpt_builder/src/components/SupabaseProvider.tsx
Normal file
65
rnd/autogpt_builder/src/components/SupabaseProvider.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import { createClient } from "@/lib/supabase/client";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
|
||||
type SupabaseContextType = {
|
||||
supabase: SupabaseClient | null;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const Context = createContext<SupabaseContextType | undefined>(undefined);
|
||||
|
||||
export default function SupabaseProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [supabase, setSupabase] = useState<SupabaseClient | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const initializeSupabase = async () => {
|
||||
setIsLoading(true);
|
||||
const client = createClient();
|
||||
const api = new AutoGPTServerAPI();
|
||||
setSupabase(client);
|
||||
setIsLoading(false);
|
||||
|
||||
if (client) {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = client.auth.onAuthStateChange((event, session) => {
|
||||
if (event === "SIGNED_IN") {
|
||||
api.createUser();
|
||||
}
|
||||
router.refresh();
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
initializeSupabase();
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ supabase, isLoading }}>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useSupabase = () => {
|
||||
const context = useContext(Context);
|
||||
if (context === undefined) {
|
||||
throw new Error("useSupabase must be used inside SupabaseProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
67
rnd/autogpt_builder/src/components/TallyPopup.tsx
Normal file
67
rnd/autogpt_builder/src/components/TallyPopup.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { IconMegaphone } from "@/components/ui/icons";
|
||||
|
||||
const TallyPopupSimple = () => {
|
||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Load Tally script
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://tally.so/widgets/embed.js";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Setup event listeners for Tally events
|
||||
const handleTallyMessage = (event: MessageEvent) => {
|
||||
if (typeof event.data === "string") {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === "Tally.FormLoaded") {
|
||||
setIsFormVisible(true);
|
||||
} else if (data.event === "Tally.PopupClosed") {
|
||||
setIsFormVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing Tally message:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleTallyMessage);
|
||||
|
||||
return () => {
|
||||
document.head.removeChild(script);
|
||||
window.removeEventListener("message", handleTallyMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (isFormVisible) {
|
||||
return null; // Hide the button when the form is visible
|
||||
}
|
||||
|
||||
const resetTutorial = () => {
|
||||
const url = `${window.location.origin}/build?resetTutorial=true`;
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-6 right-6 z-50 flex items-center gap-4 p-3 transition-all duration-300 ease-in-out">
|
||||
<Button variant="default" onClick={resetTutorial} className="mb-0">
|
||||
Tutorial
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
data-tally-open="3yx2L0"
|
||||
data-tally-emoji-text="👋"
|
||||
data-tally-emoji-animation="wave"
|
||||
>
|
||||
<IconMegaphone size="lg" />
|
||||
<span className="sr-only">Reach Out</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TallyPopupSimple;
|
||||
@@ -0,0 +1,149 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogClose,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from "@/components/ui/multiselect";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useState } from "react";
|
||||
import { addFeaturedAgent } from "./actions";
|
||||
import { Agent } from "@/lib/marketplace-api/types";
|
||||
|
||||
type FormData = {
|
||||
agent: string;
|
||||
categories: string[];
|
||||
};
|
||||
|
||||
export const AdminAddFeaturedAgentDialog = ({
|
||||
categories,
|
||||
agents,
|
||||
}: {
|
||||
categories: string[];
|
||||
agents: Agent[];
|
||||
}) => {
|
||||
const [selectedAgent, setSelectedAgent] = useState<string>("");
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
agent: "",
|
||||
categories: [],
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
Add Featured Agent
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add Featured Agent</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Controller
|
||||
name="agent"
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<label htmlFor={field.name}>Agent</label>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
setSelectedAgent(value);
|
||||
}}
|
||||
value={field.value || ""}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an agent" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{/* Populate with agents */}
|
||||
{agents.map((agent) => (
|
||||
<SelectItem key={agent.id} value={agent.id}>
|
||||
{agent.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="categories"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<MultiSelector
|
||||
values={field.value || []}
|
||||
onValuesChange={(values) => {
|
||||
field.onChange(values);
|
||||
setSelectedCategories(values);
|
||||
}}
|
||||
>
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select categories" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
{categories.map((category) => (
|
||||
<MultiSelectorItem key={category} value={category}>
|
||||
{category}
|
||||
</MultiSelectorItem>
|
||||
))}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={async () => {
|
||||
// Handle adding the featured agent
|
||||
await addFeaturedAgent(selectedAgent, selectedCategories);
|
||||
// close the dialog
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
getFeaturedAgents,
|
||||
removeFeaturedAgent,
|
||||
getCategories,
|
||||
getNotFeaturedAgents,
|
||||
} from "./actions";
|
||||
|
||||
import FeaturedAgentsTable from "./FeaturedAgentsTable";
|
||||
import { AdminAddFeaturedAgentDialog } from "./AdminAddFeaturedAgentDialog";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export default async function AdminFeaturedAgentsControl({
|
||||
className,
|
||||
}: {
|
||||
className?: string;
|
||||
}) {
|
||||
// add featured agent button
|
||||
// modal to select agent?
|
||||
// modal to select categories?
|
||||
// table of featured agents
|
||||
// in table
|
||||
// remove featured agent button
|
||||
// edit featured agent categories button
|
||||
// table footer
|
||||
// Next page button
|
||||
// Previous page button
|
||||
// Page number input
|
||||
// Page size input
|
||||
// Total pages input
|
||||
// Go to page button
|
||||
|
||||
const page = 1;
|
||||
const pageSize = 10;
|
||||
|
||||
const agents = await getFeaturedAgents(page, pageSize);
|
||||
|
||||
const categories = await getCategories();
|
||||
|
||||
const notFeaturedAgents = await getNotFeaturedAgents();
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col gap-4 ${className}`}>
|
||||
<div className="mb-4 flex justify-between">
|
||||
<h3 className="text-lg font-semibold">Featured Agent Controls</h3>
|
||||
<AdminAddFeaturedAgentDialog
|
||||
categories={categories.unique_categories}
|
||||
agents={notFeaturedAgents.agents}
|
||||
/>
|
||||
</div>
|
||||
<FeaturedAgentsTable
|
||||
agents={agents.agents}
|
||||
globalActions={[
|
||||
{
|
||||
component: <Button>Remove</Button>,
|
||||
action: async (rows) => {
|
||||
"use server";
|
||||
const all = rows.map((row) => removeFeaturedAgent(row.id));
|
||||
await Promise.all(all);
|
||||
revalidatePath("/marketplace");
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Agent } from "@/lib/marketplace-api";
|
||||
import AdminMarketplaceCard from "./AdminMarketplaceCard";
|
||||
import { ClipboardX } from "lucide-react";
|
||||
|
||||
export default function AdminMarketplaceAgentList({
|
||||
agents,
|
||||
className,
|
||||
}: {
|
||||
agents: Agent[];
|
||||
className?: string;
|
||||
}) {
|
||||
if (agents.length === 0) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<h3 className="text-lg font-semibold">Agents to review</h3>
|
||||
<div className="flex flex-col items-center justify-center py-12 text-gray-500">
|
||||
<ClipboardX size={48} />
|
||||
<p className="mt-4 text-lg font-semibold">No agents to review</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col gap-4 ${className}`}>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Agents to review</h3>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{agents.map((agent) => (
|
||||
<AdminMarketplaceCard agent={agent} key={agent.id} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { approveAgent, rejectAgent } from "./actions";
|
||||
import { Agent } from "@/lib/marketplace-api";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
function AdminMarketplaceCard({ agent }: { agent: Agent }) {
|
||||
const [isApproved, setIsApproved] = useState(false);
|
||||
const [isRejected, setIsRejected] = useState(false);
|
||||
const [comment, setComment] = useState("");
|
||||
|
||||
const approveAgentWithId = approveAgent.bind(
|
||||
null,
|
||||
agent.id,
|
||||
agent.version,
|
||||
comment,
|
||||
);
|
||||
const rejectAgentWithId = rejectAgent.bind(
|
||||
null,
|
||||
agent.id,
|
||||
agent.version,
|
||||
comment,
|
||||
);
|
||||
|
||||
const handleApprove = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await approveAgentWithId();
|
||||
setIsApproved(true);
|
||||
};
|
||||
|
||||
const handleReject = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await rejectAgentWithId();
|
||||
setIsRejected(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isApproved && !isRejected && (
|
||||
<Card key={agent.id} className="m-3 flex h-[300px] flex-col p-4">
|
||||
<div className="mb-2 flex items-start justify-between">
|
||||
<Link
|
||||
href={`/marketplace/${agent.id}`}
|
||||
className="text-lg font-semibold hover:underline"
|
||||
>
|
||||
{agent.name}
|
||||
</Link>
|
||||
<Badge variant="outline">v{agent.version}</Badge>
|
||||
</div>
|
||||
<p className="mb-2 text-sm text-gray-500">by {agent.author}</p>
|
||||
<ScrollArea className="flex-grow">
|
||||
<p className="mb-2 text-sm text-gray-600">{agent.description}</p>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{agent.categories.map((category) => (
|
||||
<Badge key={category} variant="secondary">
|
||||
{category}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{agent.keywords.map((keyword) => (
|
||||
<Badge key={keyword} variant="outline">
|
||||
{keyword}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<div className="mb-2 flex justify-between text-xs text-gray-500">
|
||||
<span>
|
||||
Created: {new Date(agent.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
<span>
|
||||
Updated: {new Date(agent.updatedAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-4 flex justify-between text-sm">
|
||||
<span>👁 {agent.views}</span>
|
||||
<span>⬇️ {agent.downloads}</span>
|
||||
</div>
|
||||
<div className="mt-auto space-y-2">
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Add a comment (optional)"
|
||||
value={comment}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
/>
|
||||
{!isRejected && (
|
||||
<form onSubmit={handleReject}>
|
||||
<Button variant="outline" type="submit">
|
||||
Reject
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
{!isApproved && (
|
||||
<form onSubmit={handleApprove}>
|
||||
<Button type="submit">Approve</Button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminMarketplaceCard;
|
||||
@@ -0,0 +1,114 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { DataTable } from "@/components/ui/data-table";
|
||||
import { Agent } from "@/lib/marketplace-api";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { ArrowUpDown } from "lucide-react";
|
||||
import { removeFeaturedAgent } from "./actions";
|
||||
import { GlobalActions } from "@/components/ui/data-table";
|
||||
|
||||
export const columns: ColumnDef<Agent>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
Name
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
accessorKey: "name",
|
||||
},
|
||||
{
|
||||
header: "Description",
|
||||
accessorKey: "description",
|
||||
},
|
||||
{
|
||||
header: "Categories",
|
||||
accessorKey: "categories",
|
||||
},
|
||||
{
|
||||
header: "Keywords",
|
||||
accessorKey: "keywords",
|
||||
},
|
||||
{
|
||||
header: "Downloads",
|
||||
accessorKey: "downloads",
|
||||
},
|
||||
{
|
||||
header: "Author",
|
||||
accessorKey: "author",
|
||||
},
|
||||
{
|
||||
header: "Version",
|
||||
accessorKey: "version",
|
||||
},
|
||||
{
|
||||
header: "actions",
|
||||
cell: ({ row }) => {
|
||||
const handleRemove = async () => {
|
||||
await removeFeaturedAgentWithId();
|
||||
};
|
||||
// const handleEdit = async () => {
|
||||
// console.log("edit");
|
||||
// };
|
||||
const removeFeaturedAgentWithId = removeFeaturedAgent.bind(
|
||||
null,
|
||||
row.original.id,
|
||||
);
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleRemove}>
|
||||
Remove
|
||||
</Button>
|
||||
{/* <Button variant="outline" size="sm" onClick={handleEdit}>
|
||||
Edit
|
||||
</Button> */}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default function FeaturedAgentsTable({
|
||||
agents,
|
||||
globalActions,
|
||||
}: {
|
||||
agents: Agent[];
|
||||
globalActions: GlobalActions<Agent>[];
|
||||
}) {
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={agents}
|
||||
filterPlaceholder="Search agents..."
|
||||
filterColumn="name"
|
||||
globalActions={globalActions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
"use server";
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
import MarketplaceAPI from "@/lib/marketplace-api";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export async function approveAgent(
|
||||
agentId: string,
|
||||
version: number,
|
||||
comment: string,
|
||||
) {
|
||||
const api = new MarketplaceAPI();
|
||||
await api.approveAgentSubmission(agentId, version, comment);
|
||||
console.debug(`Approving agent ${agentId}`);
|
||||
revalidatePath("/marketplace");
|
||||
}
|
||||
|
||||
export async function rejectAgent(
|
||||
agentId: string,
|
||||
version: number,
|
||||
comment: string,
|
||||
) {
|
||||
const api = new MarketplaceAPI();
|
||||
await api.rejectAgentSubmission(agentId, version, comment);
|
||||
console.debug(`Rejecting agent ${agentId}`);
|
||||
revalidatePath("/marketplace");
|
||||
}
|
||||
|
||||
export async function getReviewableAgents() {
|
||||
const api = new MarketplaceAPI();
|
||||
return api.getAgentSubmissions();
|
||||
}
|
||||
|
||||
export async function getFeaturedAgents(
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
) {
|
||||
const api = new MarketplaceAPI();
|
||||
const featured = await api.getFeaturedAgents(page, pageSize);
|
||||
console.debug(`Getting featured agents ${featured.agents.length}`);
|
||||
return featured;
|
||||
}
|
||||
|
||||
export async function getFeaturedAgent(agentId: string) {
|
||||
const api = new MarketplaceAPI();
|
||||
const featured = await api.getFeaturedAgent(agentId);
|
||||
console.debug(`Getting featured agent ${featured.agentId}`);
|
||||
return featured;
|
||||
}
|
||||
|
||||
export async function addFeaturedAgent(
|
||||
agentId: string,
|
||||
categories: string[] = ["featured"],
|
||||
) {
|
||||
const api = new MarketplaceAPI();
|
||||
await api.addFeaturedAgent(agentId, categories);
|
||||
console.debug(`Adding featured agent ${agentId}`);
|
||||
revalidatePath("/marketplace");
|
||||
}
|
||||
|
||||
export async function removeFeaturedAgent(
|
||||
agentId: string,
|
||||
categories: string[] = ["featured"],
|
||||
) {
|
||||
const api = new MarketplaceAPI();
|
||||
await api.removeFeaturedAgent(agentId, categories);
|
||||
console.debug(`Removing featured agent ${agentId}`);
|
||||
revalidatePath("/marketplace");
|
||||
}
|
||||
|
||||
export async function getCategories() {
|
||||
const api = new MarketplaceAPI();
|
||||
const categories = await api.getCategories();
|
||||
console.debug(`Getting categories ${categories.unique_categories.length}`);
|
||||
return categories;
|
||||
}
|
||||
|
||||
export async function getNotFeaturedAgents(
|
||||
page: number = 1,
|
||||
pageSize: number = 100,
|
||||
) {
|
||||
const api = new MarketplaceAPI();
|
||||
const agents = await api.getNotFeaturedAgents(page, pageSize);
|
||||
console.debug(`Getting not featured agents ${agents.agents.length}`);
|
||||
return agents;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import React, { useState } from "react"
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -9,28 +9,35 @@ import {
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import AutoGPTServerAPI, { Graph, GraphCreatable } from "@/lib/autogpt_server_api"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { EnterIcon } from "@radix-ui/react-icons"
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import AutoGPTServerAPI, {
|
||||
Graph,
|
||||
GraphCreatable,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EnterIcon } from "@radix-ui/react-icons";
|
||||
|
||||
// Add this custom schema for File type
|
||||
const fileSchema = z.custom<File>((val) => val instanceof File, {
|
||||
message: "Must be a File object",
|
||||
});
|
||||
|
||||
const formSchema = z.object({
|
||||
agentFile: z.instanceof(File),
|
||||
agentFile: fileSchema,
|
||||
agentName: z.string().min(1, "Agent name is required"),
|
||||
agentDescription: z.string(),
|
||||
importAsTemplate: z.boolean(),
|
||||
})
|
||||
});
|
||||
|
||||
export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>> = (
|
||||
{ className, ...props }
|
||||
) => {
|
||||
const [agentObject, setAgentObject] = useState<GraphCreatable | null>(null)
|
||||
const api = new AutoGPTServerAPI()
|
||||
export const AgentImportForm: React.FC<
|
||||
React.FormHTMLAttributes<HTMLFormElement>
|
||||
> = ({ className, ...props }) => {
|
||||
const [agentObject, setAgentObject] = useState<GraphCreatable | null>(null);
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -39,12 +46,12 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
|
||||
agentDescription: "",
|
||||
importAsTemplate: false,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
if (!agentObject) {
|
||||
form.setError("root", { message: "No Agent object to save" })
|
||||
return
|
||||
form.setError("root", { message: "No Agent object to save" });
|
||||
return;
|
||||
}
|
||||
const payload: GraphCreatable = {
|
||||
...agentObject,
|
||||
@@ -54,15 +61,20 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
|
||||
is_template: values.importAsTemplate,
|
||||
};
|
||||
|
||||
(values.importAsTemplate ? api.createTemplate(payload) : api.createGraph(payload))
|
||||
(values.importAsTemplate
|
||||
? api.createTemplate(payload)
|
||||
: api.createGraph(payload)
|
||||
)
|
||||
.then((response) => {
|
||||
const qID = values.importAsTemplate ? "templateID" : "flowID";
|
||||
window.location.href = `/build?${qID}=${response.id}`;
|
||||
})
|
||||
.catch(error => {
|
||||
const entity_type = values.importAsTemplate ? 'template' : 'agent';
|
||||
form.setError("root", { message: `Could not create ${entity_type}: ${error}` });
|
||||
})
|
||||
.catch((error) => {
|
||||
const entity_type = values.importAsTemplate ? "template" : "agent";
|
||||
form.setError("root", {
|
||||
message: `Could not create ${entity_type}: ${error}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -85,21 +97,22 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
field.onChange(file)
|
||||
field.onChange(file);
|
||||
const reader = new FileReader();
|
||||
// Attach parser to file reader
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const obj = JSON.parse(
|
||||
event.target?.result as string
|
||||
event.target?.result as string,
|
||||
);
|
||||
if (
|
||||
!["name", "description", "nodes", "links"]
|
||||
.every(key => !!obj[key])
|
||||
!["name", "description", "nodes", "links"].every(
|
||||
(key) => !!obj[key],
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"Invalid agent object in file: "
|
||||
+ JSON.stringify(obj, null, 2)
|
||||
"Invalid agent object in file: " +
|
||||
JSON.stringify(obj, null, 2),
|
||||
);
|
||||
}
|
||||
const agent = obj as Graph;
|
||||
@@ -157,14 +170,26 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
|
||||
<FormItem>
|
||||
<FormLabel>Import as</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex space-x-2 items-center">
|
||||
<span className={field.value ? "text-gray-400 dark:text-gray-600" : ""}>Agent</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span
|
||||
className={
|
||||
field.value ? "text-gray-400 dark:text-gray-600" : ""
|
||||
}
|
||||
>
|
||||
Agent
|
||||
</span>
|
||||
<Switch
|
||||
disabled={field.disabled}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<span className={field.value ? "" : "text-gray-400 dark:text-gray-600"}>Template</span>
|
||||
<span
|
||||
className={
|
||||
field.value ? "" : "text-gray-400 dark:text-gray-600"
|
||||
}
|
||||
>
|
||||
Template
|
||||
</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -176,5 +201,5 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
11
rnd/autogpt_builder/src/components/build/actions.ts
Normal file
11
rnd/autogpt_builder/src/components/build/actions.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
"use server";
|
||||
|
||||
import { TutorialStepData } from "@/lib/autogpt-server-api/types";
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api/client";
|
||||
|
||||
export const sendTutorialStep = async (data: TutorialStepData) => {
|
||||
console.log("sendTutorialStep", data);
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
await api.logTutorialStep(data);
|
||||
};
|
||||
44
rnd/autogpt_builder/src/components/customedge.css
Normal file
44
rnd/autogpt_builder/src/components/customedge.css
Normal file
@@ -0,0 +1,44 @@
|
||||
.edge-label-renderer {
|
||||
position: absolute;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.edge-label-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #eee;
|
||||
border: 1px solid #fff;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
color: #555;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.2s ease-in-out,
|
||||
background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.edge-label-button.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.edge-label-button:hover {
|
||||
box-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.08);
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.edge-label-button svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.react-flow__edge-interaction {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-flow__edges > svg:has(> g.selected) {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
@@ -1,100 +1,44 @@
|
||||
.custom-node {
|
||||
padding: 15px;
|
||||
border: 3px solid #000; /* Thicker border */
|
||||
border-radius: 12px;
|
||||
background: #ffffff; /* White background */
|
||||
color: #000000;
|
||||
width: 500px;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
.custom-node .mb-2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
/* Increased to accommodate larger buttons */
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.node-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.toggle-button:hover {
|
||||
color: #000000;
|
||||
background: #d1d1d1;
|
||||
}
|
||||
|
||||
.handle-label {
|
||||
color: #000000;
|
||||
margin-left: 10px;
|
||||
.custom-node .mb-2 .text-lg {
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.output-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* Existing styles */
|
||||
.handle-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.clickable-input {
|
||||
margin-bottom: 0px;
|
||||
padding: 5px;
|
||||
width: 325px;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d1d1d1;
|
||||
color: #000000;
|
||||
cursor: pointer;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
min-height: 44px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.clickable-input span {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: calc(100% - 100px);
|
||||
vertical-align: middle;
|
||||
.react-flow__handle {
|
||||
background: transparent;
|
||||
width: auto;
|
||||
height: auto;
|
||||
border: 0;
|
||||
position: relative;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.border-error {
|
||||
border: 1px solid #d9534f;
|
||||
}
|
||||
|
||||
.select-input {
|
||||
@@ -116,7 +60,6 @@
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #000;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
@@ -156,41 +99,11 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.node-output {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
background: #fff;
|
||||
border: 1px solid #000; /* Border for output section */
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d9534f;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.object-input {
|
||||
margin-left: 10px;
|
||||
border-left: 1px solid #000; /* Border for nested inputs */
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.nested-input {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.key-value-input {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.key-value-input input {
|
||||
flex-grow: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Styles for node states */
|
||||
@@ -206,13 +119,24 @@
|
||||
border-color: #c0392b; /* Red border for failed nodes */
|
||||
}
|
||||
|
||||
/* Adjust handle size */
|
||||
.custom-node .react-flow__handle {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
.incomplete {
|
||||
border-color: #9f14ab; /* Pink border for incomplete nodes */
|
||||
}
|
||||
|
||||
/* Make edges thicker */
|
||||
.react-flow__edge {
|
||||
stroke-width: 2px !important;
|
||||
.queued {
|
||||
border-color: #25e6e6; /* Cyan border for queued nodes */
|
||||
}
|
||||
|
||||
.custom-switch {
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-outer-spin-button,
|
||||
input[type="number"]::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import React, { useState } from "react";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ToyBrick } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { beautifyString } from "@/lib/utils";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Block } from "@/lib/autogpt-server-api";
|
||||
import { PlusIcon } from "@radix-ui/react-icons";
|
||||
import { IconToyBrick } from "@/components/ui/icons";
|
||||
import SchemaTooltip from "@/components/SchemaTooltip";
|
||||
import { getPrimaryCategoryColor } from "@/lib/utils";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface BlocksControlProps {
|
||||
blocks: Block[];
|
||||
addBlock: (id: string, name: string) => void;
|
||||
pinBlocksPopover: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A React functional component that displays a control for managing blocks.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} BlocksControlProps - The properties for the BlocksControl component.
|
||||
* @param {Block[]} BlocksControlProps.blocks - An array of blocks to be displayed and filtered.
|
||||
* @param {(id: string, name: string) => void} BlocksControlProps.addBlock - A function to call when a block is added.
|
||||
* @returns The rendered BlocksControl component.
|
||||
*/
|
||||
export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
blocks,
|
||||
addBlock,
|
||||
pinBlocksPopover,
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
|
||||
// Extract unique categories from blocks
|
||||
const categories = Array.from(
|
||||
new Set(
|
||||
blocks.flatMap((block) => block.categories.map((cat) => cat.category)),
|
||||
),
|
||||
);
|
||||
|
||||
const filteredBlocks = blocks.filter(
|
||||
(block: Block) =>
|
||||
(block.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
beautifyString(block.name)
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase())) &&
|
||||
(!selectedCategory ||
|
||||
block.categories.some((cat) => cat.category === selectedCategory)),
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={pinBlocksPopover ? true : undefined}>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
data-id="blocks-control-popover-trigger"
|
||||
>
|
||||
<IconToyBrick />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Blocks</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent
|
||||
side="right"
|
||||
sideOffset={22}
|
||||
align="start"
|
||||
className="w-[30rem] p-0"
|
||||
data-id="blocks-control-popover-content"
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CardHeader className="flex flex-col gap-x-8 gap-y-2 p-3 px-2">
|
||||
<div className="items-center justify-between">
|
||||
<Label
|
||||
htmlFor="search-blocks"
|
||||
className="whitespace-nowrap border-b-2 border-violet-500 text-base font-semibold text-black 2xl:text-xl"
|
||||
data-id="blocks-control-label"
|
||||
>
|
||||
Blocks
|
||||
</Label>
|
||||
</div>
|
||||
<Input
|
||||
id="search-blocks"
|
||||
type="text"
|
||||
placeholder="Search blocks..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
data-id="blocks-control-search-input"
|
||||
/>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{categories.map((category) => (
|
||||
<Badge
|
||||
key={category}
|
||||
variant={
|
||||
selectedCategory === category ? "default" : "outline"
|
||||
}
|
||||
className={`cursor-pointer ${getPrimaryCategoryColor([{ category, description: "" }])}`}
|
||||
onClick={() =>
|
||||
setSelectedCategory(
|
||||
selectedCategory === category ? null : category,
|
||||
)
|
||||
}
|
||||
>
|
||||
{beautifyString(category)}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-1">
|
||||
<ScrollArea
|
||||
className="h-[60vh]"
|
||||
data-id="blocks-control-scroll-area"
|
||||
>
|
||||
{filteredBlocks.map((block) => (
|
||||
<Card
|
||||
key={block.id}
|
||||
className={`m-2 ${getPrimaryCategoryColor(block.categories)}`}
|
||||
data-id={`block-card-${block.id}`}
|
||||
>
|
||||
<div className="m-3 flex items-center justify-between">
|
||||
<div className="mr-2 min-w-0 flex-1">
|
||||
<span
|
||||
className="block truncate font-medium"
|
||||
data-id={`block-name-${block.id}`}
|
||||
>
|
||||
{beautifyString(block.name)}
|
||||
</span>
|
||||
</div>
|
||||
<SchemaTooltip description={block.description} />
|
||||
<div
|
||||
className="flex flex-shrink-0 items-center gap-1"
|
||||
data-id={`block-tooltip-${block.id}`}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => addBlock(block.id, block.name)}
|
||||
aria-label="Add block"
|
||||
data-id={`add-block-button-${block.id}`}
|
||||
>
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* Represents a control element for the ControlPanel Component.
|
||||
* @type {Object} Control
|
||||
* @property {React.ReactNode} icon - The icon of the control from lucide-react https://lucide.dev/icons/
|
||||
* @property {string} label - The label of the control, to be leveraged by ToolTip.
|
||||
* @property {onclick} onClick - The function to be executed when the control is clicked.
|
||||
*/
|
||||
export type Control = {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
interface ControlPanelProps {
|
||||
controls: Control[];
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ControlPanel component displays a panel with controls as icons.tsx with the ability to take in children.
|
||||
* @param {Object} ControlPanelProps - The properties of the control panel component.
|
||||
* @param {Array} ControlPanelProps.controls - An array of control objects representing actions to be preformed.
|
||||
* @param {Array} ControlPanelProps.children - The child components of the control panel.
|
||||
* @param {string} ControlPanelProps.className - Additional CSS class names for the control panel.
|
||||
* @returns The rendered control panel component.
|
||||
*/
|
||||
export const ControlPanel = ({
|
||||
controls,
|
||||
children,
|
||||
className,
|
||||
}: ControlPanelProps) => {
|
||||
return (
|
||||
<Card className={cn("w-14", className)}>
|
||||
<CardContent className="p-0">
|
||||
<div className="rounded-radius flex flex-col items-center gap-8 px-2 sm:py-5">
|
||||
{children}
|
||||
<Separator />
|
||||
{controls.map((control, index) => (
|
||||
<Tooltip key={index} delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => control.onClick()}
|
||||
data-id={`control-button-${index}`}
|
||||
>
|
||||
{control.icon}
|
||||
<span className="sr-only">{control.label}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{control.label}</TooltipContent>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default ControlPanel;
|
||||
112
rnd/autogpt_builder/src/components/edit/control/SaveControl.tsx
Normal file
112
rnd/autogpt_builder/src/components/edit/control/SaveControl.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { IconSave } from "@/components/ui/icons";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface SaveControlProps {
|
||||
agentMeta: GraphMeta | null;
|
||||
onSave: (isTemplate: boolean | undefined) => void;
|
||||
onNameChange: (name: string) => void;
|
||||
onDescriptionChange: (description: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A SaveControl component to be used within the ControlPanel. It allows the user to save the agent / template.
|
||||
* @param {Object} SaveControlProps - The properties of the SaveControl component.
|
||||
* @param {GraphMeta | null} SaveControlProps.agentMeta - The agent's metadata, or null if creating a new agent.
|
||||
* @param {(isTemplate: boolean | undefined) => void} SaveControlProps.onSave - Function to save the agent or template.
|
||||
* @param {(name: string) => void} SaveControlProps.onNameChange - Function to handle name changes.
|
||||
* @param {(description: string) => void} SaveControlProps.onDescriptionChange - Function to handle description changes.
|
||||
* @returns The SaveControl component.
|
||||
*/
|
||||
export const SaveControl = ({
|
||||
agentMeta,
|
||||
onSave,
|
||||
onNameChange,
|
||||
onDescriptionChange,
|
||||
}: SaveControlProps) => {
|
||||
/**
|
||||
* Note for improvement:
|
||||
* At the moment we are leveraging onDescriptionChange and onNameChange to handle the changes in the description and name of the agent.
|
||||
* We should migrate this to be handled with form controls and a form library.
|
||||
*/
|
||||
|
||||
// Determines if we're saving a template or an agent
|
||||
let isTemplate = agentMeta?.is_template ? true : undefined;
|
||||
const handleSave = () => {
|
||||
onSave(isTemplate);
|
||||
};
|
||||
|
||||
const getType = () => {
|
||||
return agentMeta?.is_template ? "template" : "agent";
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<IconSave />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Save</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent side="right" sideOffset={15} align="start">
|
||||
<Card className="border-none shadow-none">
|
||||
<CardContent className="p-4">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Enter your agent name"
|
||||
className="col-span-3"
|
||||
defaultValue={agentMeta?.name || ""}
|
||||
onChange={(e) => onNameChange(e.target.value)}
|
||||
/>
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
placeholder="Your agent description"
|
||||
className="col-span-3"
|
||||
defaultValue={agentMeta?.description || ""}
|
||||
onChange={(e) => onDescriptionChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||
<Button className="w-full" onClick={handleSave}>
|
||||
Save {getType()}
|
||||
</Button>
|
||||
{!agentMeta && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
isTemplate = true;
|
||||
handleSave();
|
||||
}}
|
||||
>
|
||||
Save as Template
|
||||
</Button>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -1,36 +1,18 @@
|
||||
/* flow.css or index.css */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
input,
|
||||
textarea {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
border: 1px solid #555;
|
||||
@@ -38,10 +20,10 @@ input, textarea {
|
||||
border-radius: 4px;
|
||||
width: calc(100% - 18px);
|
||||
box-sizing: border-box;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
input::placeholder, textarea::placeholder {
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@@ -91,13 +73,14 @@ input::placeholder, textarea::placeholder {
|
||||
top: 0;
|
||||
left: -600px;
|
||||
width: 350px;
|
||||
height: 100%;
|
||||
height: calc(100vh - 68px); /* Full height minus top offset */
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 20px;
|
||||
transition: left 0.3s ease;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
margin-top: 68px; /* Margin to push content below the top fixed area */
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
@@ -130,26 +113,4 @@ input::placeholder, textarea::placeholder {
|
||||
.flow-container {
|
||||
width: 100%;
|
||||
height: 600px; /* Adjust this height as needed */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.flow-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flow-controls {
|
||||
position: absolute;
|
||||
left: -80px;
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.flow-controls.open {
|
||||
transform: translateX(350px);
|
||||
}
|
||||
|
||||
96
rnd/autogpt_builder/src/components/history.ts
Normal file
96
rnd/autogpt_builder/src/components/history.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
// history.ts
|
||||
import { CustomNodeData } from "./CustomNode";
|
||||
import { CustomEdgeData } from "./CustomEdge";
|
||||
import { Edge } from "@xyflow/react";
|
||||
|
||||
type ActionType =
|
||||
| "ADD_NODE"
|
||||
| "DELETE_NODE"
|
||||
| "ADD_EDGE"
|
||||
| "DELETE_EDGE"
|
||||
| "UPDATE_NODE"
|
||||
| "MOVE_NODE"
|
||||
| "UPDATE_INPUT"
|
||||
| "UPDATE_NODE_POSITION";
|
||||
|
||||
type AddNodePayload = { node: CustomNodeData };
|
||||
type DeleteNodePayload = { nodeId: string };
|
||||
type AddEdgePayload = { edge: Edge<CustomEdgeData> };
|
||||
type DeleteEdgePayload = { edgeId: string };
|
||||
type UpdateNodePayload = { nodeId: string; newData: Partial<CustomNodeData> };
|
||||
type MoveNodePayload = { nodeId: string; position: { x: number; y: number } };
|
||||
type UpdateInputPayload = {
|
||||
nodeId: string;
|
||||
oldValues: { [key: string]: any };
|
||||
newValues: { [key: string]: any };
|
||||
};
|
||||
type UpdateNodePositionPayload = {
|
||||
nodeId: string;
|
||||
oldPosition: { x: number; y: number };
|
||||
newPosition: { x: number; y: number };
|
||||
};
|
||||
|
||||
type ActionPayload =
|
||||
| AddNodePayload
|
||||
| DeleteNodePayload
|
||||
| AddEdgePayload
|
||||
| DeleteEdgePayload
|
||||
| UpdateNodePayload
|
||||
| MoveNodePayload
|
||||
| UpdateInputPayload
|
||||
| UpdateNodePositionPayload;
|
||||
|
||||
type Action = {
|
||||
type: ActionType;
|
||||
payload: ActionPayload;
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
};
|
||||
|
||||
class History {
|
||||
private past: Action[] = [];
|
||||
private future: Action[] = [];
|
||||
|
||||
push(action: Action) {
|
||||
this.past.push(action);
|
||||
this.future = [];
|
||||
}
|
||||
|
||||
undo() {
|
||||
const action = this.past.pop();
|
||||
if (action) {
|
||||
action.undo();
|
||||
this.future.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
redo() {
|
||||
const action = this.future.pop();
|
||||
if (action) {
|
||||
action.redo();
|
||||
this.past.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
canUndo(): boolean {
|
||||
return this.past.length > 0;
|
||||
}
|
||||
|
||||
canRedo(): boolean {
|
||||
return this.future.length > 0;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.past = [];
|
||||
this.future = [];
|
||||
}
|
||||
|
||||
getHistoryState() {
|
||||
return {
|
||||
past: [...this.past],
|
||||
future: [...this.future],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const history = new History();
|
||||
@@ -0,0 +1,175 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Download,
|
||||
Calendar,
|
||||
Tag,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
AgentDetailResponse,
|
||||
InstallationLocation,
|
||||
} from "@/lib/marketplace-api";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Node, Edge } from "@xyflow/react";
|
||||
import MarketplaceAPI from "@/lib/marketplace-api";
|
||||
import AutoGPTServerAPI, { GraphCreatable } from "@/lib/autogpt-server-api";
|
||||
|
||||
const ReactFlow = dynamic(
|
||||
() => import("@xyflow/react").then((mod) => mod.ReactFlow),
|
||||
{ ssr: false },
|
||||
);
|
||||
const Controls = dynamic(
|
||||
() => import("@xyflow/react").then((mod) => mod.Controls),
|
||||
{ ssr: false },
|
||||
);
|
||||
const Background = dynamic(
|
||||
() => import("@xyflow/react").then((mod) => mod.Background),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { beautifyString } from "@/lib/utils";
|
||||
import { makeAnalyticsEvent } from "./actions";
|
||||
|
||||
function convertGraphToReactFlow(graph: any): { nodes: Node[]; edges: Edge[] } {
|
||||
const nodes: Node[] = graph.nodes.map((node: any) => {
|
||||
let label = node.block_id || "Unknown";
|
||||
try {
|
||||
label = beautifyString(label);
|
||||
} catch (error) {
|
||||
console.error("Error beautifying node label:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
position: node.metadata.position || { x: 0, y: 0 },
|
||||
data: {
|
||||
label,
|
||||
blockId: node.block_id,
|
||||
inputDefault: node.input_default || {},
|
||||
...node, // Include all other node data
|
||||
},
|
||||
type: "custom",
|
||||
};
|
||||
});
|
||||
|
||||
const edges: Edge[] = graph.links.map((link: any) => ({
|
||||
id: `${link.source_id}-${link.sink_id}`,
|
||||
source: link.source_id,
|
||||
target: link.sink_id,
|
||||
sourceHandle: link.source_name,
|
||||
targetHandle: link.sink_name,
|
||||
type: "custom",
|
||||
data: {
|
||||
sourceId: link.source_id,
|
||||
targetId: link.sink_id,
|
||||
sourceName: link.source_name,
|
||||
targetName: link.sink_name,
|
||||
isStatic: link.is_static,
|
||||
},
|
||||
}));
|
||||
|
||||
return { nodes, edges };
|
||||
}
|
||||
|
||||
async function installGraph(id: string): Promise<void> {
|
||||
const apiUrl =
|
||||
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL ||
|
||||
"http://localhost:8015/api/v1/market";
|
||||
const api = new MarketplaceAPI(apiUrl);
|
||||
|
||||
const serverAPIUrl = process.env.AGPT_SERVER_API_URL;
|
||||
const serverAPI = new AutoGPTServerAPI(serverAPIUrl);
|
||||
try {
|
||||
console.log(`Installing agent with id: ${id}`);
|
||||
let agent = await api.downloadAgent(id);
|
||||
console.log(`Agent downloaded:`, agent);
|
||||
const data: GraphCreatable = {
|
||||
id: agent.id,
|
||||
version: agent.version,
|
||||
is_active: true,
|
||||
is_template: false,
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
nodes: agent.graph.nodes,
|
||||
links: agent.graph.links,
|
||||
};
|
||||
const result = await serverAPI.createTemplate(data);
|
||||
makeAnalyticsEvent({
|
||||
event_name: "agent_installed_from_marketplace",
|
||||
event_data: {
|
||||
marketplace_agent_id: id,
|
||||
installed_agent_id: result.id,
|
||||
installation_location: InstallationLocation.CLOUD,
|
||||
},
|
||||
});
|
||||
console.log(`Agent installed successfully`, result);
|
||||
} catch (error) {
|
||||
console.error(`Error installing agent:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function AgentDetailContent({ agent }: { agent: AgentDetailResponse }) {
|
||||
const [isGraphExpanded, setIsGraphExpanded] = useState(false);
|
||||
const { nodes, edges } = convertGraphToReactFlow(agent.graph);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<Link
|
||||
href="/marketplace"
|
||||
className="inline-flex items-center text-indigo-600 hover:text-indigo-500"
|
||||
>
|
||||
<ArrowLeft className="mr-2" size={20} />
|
||||
Back to Marketplace
|
||||
</Link>
|
||||
<Button
|
||||
onClick={() => installGraph(agent.id)}
|
||||
className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
<Download className="mr-2" size={16} />
|
||||
Download Agent
|
||||
</Button>
|
||||
</div>
|
||||
<div className="overflow-hidden bg-white shadow sm:rounded-lg">
|
||||
<div className="px-4 py-5 sm:px-6">
|
||||
<h1 className="text-3xl font-bold text-gray-900">{agent.name}</h1>
|
||||
<p className="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
{agent.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-t border-gray-200 px-4 py-5 sm:p-0">
|
||||
<dl className="sm:divide-y sm:divide-gray-200">
|
||||
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<dt className="flex items-center text-sm font-medium text-gray-500">
|
||||
<Calendar className="mr-2" size={16} />
|
||||
Last Updated
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{new Date(agent.updatedAt).toLocaleDateString()}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<dt className="flex items-center text-sm font-medium text-gray-500">
|
||||
<Tag className="mr-2" size={16} />
|
||||
Categories
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{agent.categories.join(", ")}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgentDetailContent;
|
||||
@@ -0,0 +1,9 @@
|
||||
"use server";
|
||||
|
||||
import MarketplaceAPI, { AnalyticsEvent } from "@/lib/marketplace-api";
|
||||
|
||||
export async function makeAnalyticsEvent(event: AnalyticsEvent) {
|
||||
const apiUrl = process.env.AGPT_SERVER_API_URL;
|
||||
const api = new MarketplaceAPI();
|
||||
await api.makeAnalyticsEvent(event);
|
||||
}
|
||||
191
rnd/autogpt_builder/src/components/monitor/AgentFlowList.tsx
Normal file
191
rnd/autogpt_builder/src/components/monitor/AgentFlowList.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import AutoGPTServerAPI, { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ChevronDownIcon, EnterIcon } from "@radix-ui/react-icons";
|
||||
import { AgentImportForm } from "@/components/agent-import-form";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import moment from "moment/moment";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { DialogTitle } from "@/components/ui/dialog";
|
||||
|
||||
export const AgentFlowList = ({
|
||||
flows,
|
||||
flowRuns,
|
||||
selectedFlow,
|
||||
onSelectFlow,
|
||||
className,
|
||||
}: {
|
||||
flows: GraphMeta[];
|
||||
flowRuns?: FlowRun[];
|
||||
selectedFlow: GraphMeta | null;
|
||||
onSelectFlow: (f: GraphMeta) => void;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [templates, setTemplates] = useState<GraphMeta[]>([]);
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
useEffect(() => {
|
||||
api.listTemplates().then((templates) => setTemplates(templates));
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader className="flex-row items-center justify-between space-x-3 space-y-0">
|
||||
<CardTitle>Agents</CardTitle>
|
||||
|
||||
<div className="flex items-center">
|
||||
{/* Split "Create" button */}
|
||||
<Button variant="outline" className="rounded-r-none" asChild>
|
||||
<Link href="/build">Create</Link>
|
||||
</Button>
|
||||
<Dialog>
|
||||
{/* https://ui.shadcn.com/docs/components/dialog#notes */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={"rounded-l-none border-l-0 px-2"}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem>
|
||||
<EnterIcon className="mr-2" /> Import from file
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
{templates.length > 0 && (
|
||||
<>
|
||||
{/* List of templates */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Use a template</DropdownMenuLabel>
|
||||
{templates.map((template) => (
|
||||
<DropdownMenuItem
|
||||
key={template.id}
|
||||
onClick={() => {
|
||||
api
|
||||
.createGraph(template.id, template.version)
|
||||
.then((newGraph) => {
|
||||
window.location.href = `/build?flowID=${newGraph.id}`;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{template.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="sr-only">Import Agent</DialogTitle>
|
||||
<h2 className="text-lg font-semibold">
|
||||
Import an Agent (template) from a file
|
||||
</h2>
|
||||
</DialogHeader>
|
||||
<AgentImportForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
{/* <TableHead>Status</TableHead> */}
|
||||
{/* <TableHead>Last updated</TableHead> */}
|
||||
{flowRuns && (
|
||||
<TableHead className="md:hidden lg:table-cell">
|
||||
# of runs
|
||||
</TableHead>
|
||||
)}
|
||||
{flowRuns && <TableHead>Last run</TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{flows
|
||||
.map((flow) => {
|
||||
let runCount = 0,
|
||||
lastRun: FlowRun | null = null;
|
||||
if (flowRuns) {
|
||||
const _flowRuns = flowRuns.filter(
|
||||
(r) => r.graphID == flow.id,
|
||||
);
|
||||
runCount = _flowRuns.length;
|
||||
lastRun =
|
||||
runCount == 0
|
||||
? null
|
||||
: _flowRuns.reduce((a, c) =>
|
||||
a.startTime > c.startTime ? a : c,
|
||||
);
|
||||
}
|
||||
return { flow, runCount, lastRun };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (!a.lastRun && !b.lastRun) return 0;
|
||||
if (!a.lastRun) return 1;
|
||||
if (!b.lastRun) return -1;
|
||||
return b.lastRun.startTime - a.lastRun.startTime;
|
||||
})
|
||||
.map(({ flow, runCount, lastRun }) => (
|
||||
<TableRow
|
||||
key={flow.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSelectFlow(flow)}
|
||||
data-state={selectedFlow?.id == flow.id ? "selected" : null}
|
||||
>
|
||||
<TableCell>{flow.name}</TableCell>
|
||||
{/* <TableCell><FlowStatusBadge status={flow.status ?? "active"} /></TableCell> */}
|
||||
{/* <TableCell>
|
||||
{flow.updatedAt ?? "???"}
|
||||
</TableCell> */}
|
||||
{flowRuns && (
|
||||
<TableCell className="md:hidden lg:table-cell">
|
||||
{runCount}
|
||||
</TableCell>
|
||||
)}
|
||||
{flowRuns &&
|
||||
(!lastRun ? (
|
||||
<TableCell />
|
||||
) : (
|
||||
<TableCell title={moment(lastRun.startTime).toString()}>
|
||||
{moment(lastRun.startTime).fromNow()}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default AgentFlowList;
|
||||
134
rnd/autogpt_builder/src/components/monitor/FlowInfo.tsx
Normal file
134
rnd/autogpt_builder/src/components/monitor/FlowInfo.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import AutoGPTServerAPI, {
|
||||
Graph,
|
||||
GraphMeta,
|
||||
safeCopyGraph,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ClockIcon, ExitIcon, Pencil2Icon } from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
import { exportAsJSONFile } from "@/lib/utils";
|
||||
import { FlowRunsStats } from "@/components/monitor/index";
|
||||
|
||||
export const FlowInfo: React.FC<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRuns: FlowRun[];
|
||||
flowVersion?: number | "all";
|
||||
}
|
||||
> = ({ flow, flowRuns, flowVersion, ...props }) => {
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
|
||||
const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null);
|
||||
const [selectedVersion, setSelectedFlowVersion] = useState(
|
||||
flowVersion ?? "all",
|
||||
);
|
||||
const selectedFlowVersion: Graph | undefined = flowVersions?.find(
|
||||
(v) =>
|
||||
v.version == (selectedVersion == "all" ? flow.version : selectedVersion),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
api.getGraphAllVersions(flow.id).then((result) => setFlowVersions(result));
|
||||
}, [flow.id, api]);
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader className="flex-row justify-between space-x-3 space-y-0">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">
|
||||
Agent ID: <code>{flow.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
{(flowVersions?.length ?? 0) > 1 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<ClockIcon className="mr-2" />
|
||||
{selectedVersion == "all"
|
||||
? "All versions"
|
||||
: `Version ${selectedVersion}`}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuLabel>Choose a version</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup
|
||||
value={String(selectedVersion)}
|
||||
onValueChange={(choice) =>
|
||||
setSelectedFlowVersion(
|
||||
choice == "all" ? choice : Number(choice),
|
||||
)
|
||||
}
|
||||
>
|
||||
<DropdownMenuRadioItem value="all">
|
||||
All versions
|
||||
</DropdownMenuRadioItem>
|
||||
{flowVersions?.map((v) => (
|
||||
<DropdownMenuRadioItem
|
||||
key={v.version}
|
||||
value={v.version.toString()}
|
||||
>
|
||||
Version {v.version}
|
||||
{v.is_active ? " (active)" : ""}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<Link
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" /> Edit
|
||||
</Link>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="px-2.5"
|
||||
title="Export to a JSON-file"
|
||||
onClick={async () =>
|
||||
exportAsJSONFile(
|
||||
safeCopyGraph(
|
||||
flowVersions!.find(
|
||||
(v) => v.version == selectedFlowVersion!.version,
|
||||
)!,
|
||||
await api.getBlocks(),
|
||||
),
|
||||
`${flow.name}_v${selectedFlowVersion!.version}.json`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<ExitIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FlowRunsStats
|
||||
flows={[selectedFlowVersion ?? flow]}
|
||||
flowRuns={flowRuns.filter(
|
||||
(r) =>
|
||||
r.graphID == flow.id &&
|
||||
(selectedVersion == "all" || r.graphVersion == selectedVersion),
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default FlowInfo;
|
||||
79
rnd/autogpt_builder/src/components/monitor/FlowRunInfo.tsx
Normal file
79
rnd/autogpt_builder/src/components/monitor/FlowRunInfo.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useCallback } from "react";
|
||||
import AutoGPTServerAPI, { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import Link from "next/link";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { IconSquare } from "@/components/ui/icons";
|
||||
import { Pencil2Icon } from "@radix-ui/react-icons";
|
||||
import moment from "moment/moment";
|
||||
import { FlowRunStatusBadge } from "@/components/monitor/FlowRunStatusBadge";
|
||||
|
||||
export const FlowRunInfo: React.FC<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRun: FlowRun;
|
||||
}
|
||||
> = ({ flow, flowRun, ...props }) => {
|
||||
if (flowRun.graphID != flow.id) {
|
||||
throw new Error(
|
||||
`FlowRunInfo can't be used with non-matching flowRun.flowID and flow.id`,
|
||||
);
|
||||
}
|
||||
|
||||
const handleStopRun = useCallback(() => {
|
||||
const api = new AutoGPTServerAPI();
|
||||
api.stopGraphExecution(flow.id, flowRun.id);
|
||||
}, [flow.id, flowRun.id]);
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader className="flex-row items-center justify-between space-x-3 space-y-0">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">
|
||||
Agent ID: <code>{flow.id}</code>
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
Run ID: <code>{flowRun.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
{flowRun.status === "running" && (
|
||||
<Button onClick={handleStopRun} variant="destructive">
|
||||
<IconSquare className="mr-2" /> Stop Run
|
||||
</Button>
|
||||
)}
|
||||
<Link
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" /> Edit Agent
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>
|
||||
<strong>Status:</strong>{" "}
|
||||
<FlowRunStatusBadge status={flowRun.status} />
|
||||
</p>
|
||||
<p>
|
||||
<strong>Started:</strong>{" "}
|
||||
{moment(flowRun.startTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Finished:</strong>{" "}
|
||||
{moment(flowRun.endTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Duration (run time):</strong> {flowRun.duration} (
|
||||
{flowRun.totalRunTime}) seconds
|
||||
</p>
|
||||
{/* <p><strong>Total cost:</strong> €1,23</p> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default FlowRunInfo;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user