Compare commits

..

369 Commits

Author SHA1 Message Date
Zamil Majdy
ab3a62995f Revert 2024-09-17 21:00:47 -05:00
Zamil Majdy
f2e9a8463d Move shutil in 2024-09-17 20:55:20 -05:00
Zamil Majdy
107148749b Move venv in 2024-09-17 20:51:56 -05:00
Zamil Majdy
17f1d33ed3 Revert 2024-09-17 20:46:01 -05:00
Zamil Majdy
b3a0fc538a Revert 2024-09-17 20:39:29 -05:00
Zamil Majdy
e818bbf859 Merge branch 'master' into ntindle/samples 2024-09-17 18:30:23 -05:00
Nicholas Tindle
069ec89691 Update test_manager.py 2024-09-17 10:07:41 -05:00
Nicholas Tindle
f6ab15db47 feat(market): add filters to the market queries (#8064) 2024-09-17 14:59:25 +00:00
Nicholas Tindle
84d490bcb1 Merge branch 'master' into ntindle/samples 2024-09-17 09:46:22 -05:00
Krzysztof Czerwinski
80161decb9 feat(server): Add credentials API endpoints (#8024)
- Add two endpoints to OAuth `integrations.py`:
  - `GET /integrations/{provider}/credentials` - list all credentials for a provider, without secrets (metadata only)
   - `GET /integrations/{provider}/credentials/{cred_id}` - retrieve a set of credentials (including secrets)

- Add `username` property to `Credentials` types
   - Add logic to populate `username` in OAuth handlers

- Expand `CredentialsMetaResponse` and remove `credentials_` prefix from properties

- Fix `autogpt_libs` dependency caching issue

- Remove accidentally duplicated OAuth handler files in `autogpt_server/integrations`
2024-09-17 11:16:16 +00:00
Swifty
0bf8edcd96 fix(autogpt_server): Fix vulnerability in Dockerfile (#8071) 2024-09-17 11:37:22 +01:00
Nicholas Tindle
9368956d5d Update test_manager.py 2024-09-16 14:53:28 -05:00
Nicholas Tindle
2c3bde0c53 Merge branch 'master' into ntindle/samples 2024-09-16 14:40:12 -05:00
Nicholas Tindle
104b56628e fix: merge oops 2024-09-16 14:39:23 -05:00
Zamil Majdy
b1347a92de fix(rnd): Fix execution error on non-saved agent (#8054) 2024-09-16 19:35:31 +00:00
Nicholas Tindle
22ce8e0047 feat(builder): sentry integration (#8053) 2024-09-16 23:19:52 +07:00
Bently
5a7193cfb7 Feat(Builder): Add Runner input and ouput screens (#8038)
* Feat(Builder): Add Runner input and ouput screens

* Fix run button not working

* prettier

* prettier again -- forgot flow

* fix input scaling + auto close on run

* removed "Runner Input" button to make it auto open runner input if input block is  + Fixed issue with output not showing in output UI

* replaced runner output icon and added a new icon for it

* replaced IconOutput icon with LogOut from lucide-react

* prettier

* fix type safety issue + add error handling for formatOutput

* Updates based on comments

* prettier for utils
2024-09-16 13:05:07 +02:00
Nicholas Tindle
15ac526eee Merge branch 'master' into ntindle/samples 2024-09-15 10:26:49 -05:00
Zamil Majdy
c1f301ab8b feat(rnd): Add initial credit accounting system for block execution (#8047)
### Background

We need a way to set an execution quota per user for each block execution.

### Changes 🏗️

* Introduced a `UserBlockCredit`, a transaction table tracking the block usage along with it cost/quota.
* The tracking is toggled by `ENABLE_CREDIT` config, default = false.
* Introduced  `BLOCK_COSTS` | `GET /blocks/costs` as a source of information for the cost on each block depending on the input configuration.

Improvements:
* Refactor logging in manager.py to always print a prefix and pass the metadata.
* Make executionStatus on AgentNodeExecution prisma enum. And add executionStatus on AgentGraphExecution.
* Use executionStatus from AgentGraphExecution to improve waiting logic on test_manager.py.
2024-09-14 23:47:28 +07:00
Nicholas Tindle
5f83e354b9 Merge branch 'master' into ntindle/samples 2024-09-13 23:35:34 -05:00
Nicholas Tindle
70ebf4d58b Update test_manager.py 2024-09-13 22:31:33 -05:00
Nicholas Tindle
6d0d264d99 feat(server): set timeout to 44 second 2024-09-13 18:09:16 -05:00
Zamil Majdy
f32244a112 fix(rnd): Fix broken save feature on Agent Builder (#8052) 2024-09-13 18:04:51 -05:00
Aarushi
9395706841 fix(rnd,market): Fix docker issues with market, and DB connection (#8050)
fix docker issues with market, and DB connection
2024-09-13 16:15:06 +01:00
Nicholas Tindle
8e24b546a3 Merge branch 'master' into ntindle/samples 2024-09-12 23:57:23 -05:00
Nicholas Tindle
d4838cdc45 Update test_manager.py 2024-09-12 23:51:01 -05:00
Nicholas Tindle
acaca35498 Update test_manager.py 2024-09-12 23:43:58 -05:00
Nicholas Tindle
9ee0825f21 Update test_manager.py 2024-09-12 23:36:06 -05:00
Nicholas Tindle
5fde0f2c67 lets try 34 secondsd 2024-09-12 23:29:13 -05:00
Nicholas Tindle
e3407fdfb4 Update test_manager.py 2024-09-12 23:02:08 -05:00
Nicholas Tindle
b98e62cdef Update test_manager.py 2024-09-12 22:54:07 -05:00
Nicholas Tindle
4d82f78f04 Update test_manager.py 2024-09-12 22:47:36 -05:00
Nicholas Tindle
c5d2586f6c Update test_manager.py 2024-09-12 22:34:56 -05:00
SwiftyOS
a98677b79d Revert "updated lock file"
This reverts commit 056eb46c0f.
2024-09-12 17:59:39 +02:00
Nicholas Tindle
589c8d94ec feat: warning 2024-09-12 10:59:33 -05:00
SwiftyOS
056eb46c0f updated lock file 2024-09-12 16:18:13 +02:00
Nicholas Tindle
136d258a46 Merge branch 'master' into ntindle/samples 2024-09-12 07:02:42 -05:00
Swifty
6fde030c37 feat(rnd): Reduce container size remove dep with forge and autogpt (#8040)
* Remove forge and autogpt

* update lock files

* Update build process to reduce image size

* Reduced built image size

* fixed docker compose watch

* Updated logging

* updated env.example

* formatting

* linting issue

* linting not working in github actions..

* trying to get around github action linting issue

* updated version

* sleep for prisma issues

* add exp backoff on connection issues

* updated config based on review comments

* Sorting alphabetical

* updated default config

* updated depends checks

* fixed missing prisma binaries

* remove dead layer

* remove try

* remove dead layer
2024-09-12 13:03:37 +02:00
Aarushi
bf1e01d423 tweak(rnd) Add prefix in logs (#8001)
* add prefix

* fix typos

* fix conflicts
2024-09-12 11:48:47 +01:00
Zamil Majdy
52c731abd6 fix(rnd): Fix decorator function type hint (#8043) 2024-09-12 05:35:33 +07:00
Aarushi
c8fbce643e fix(rnd): Add connection timeout (#8041)
add connection timeout
2024-09-11 18:32:07 +01:00
Swifty
6c001bd595 Create Input Node Custom UI Node (#8016) 2024-09-11 14:14:03 +02:00
Aarushi
f5b89672f8 feat(rnd): Add k8s default health check (#8037)
add k8s default health check
2024-09-11 12:30:34 +01:00
Aarushi
76480ffa03 fix(rnd): Update port in market (#8036)
update port
2024-09-11 12:19:11 +01:00
Aarushi
ab60a57379 tweak(rnd): Ignore .env in market (#8035)
ignore .env
2024-09-11 11:01:34 +01:00
Aarushi
1d9b01fc77 tweak(rnd): Use docker compose not docker-compose (#8034)
* use docker compose not docker-compose

* linting
2024-09-11 10:20:31 +01:00
Swifty
e81d9f9f0b docker nits (#8033) 2024-09-11 10:31:12 +02:00
Bentlybro
0d5d0270ea Merge branch 'master' of https://github.com/Significant-Gravitas/AutoGPT 2024-09-10 18:24:53 +01:00
SwiftyOS
bd25f9223c expose schedular port and fix marketplace port 2024-09-10 17:09:39 +02:00
SwiftyOS
07305b55ff fix(rnd) use migrate deploy 2024-09-10 16:18:42 +02:00
SwiftyOS
cdfe3e5fbc Updated rnd/README.md 2024-09-10 16:16:16 +02:00
Swifty
e992cdf8c2 Fixing docker setup for local testing (#8026)
* Fixing docker setup

* Updated docker compose setup

* update helm charts

* Corrected agent server host name
2024-09-10 15:46:22 +02:00
Aarushi
ebd2ecd84c docs(server): Update docs (#8031)
update docs
2024-09-10 10:24:54 +01:00
Aarushi
0b919522ae feat(rnd): Split Execution Manager (#8008)
* split execution manager and removed ns and use direct uri with k8s and docker specific dns

* formating

* split execution manager

* refactor(builder): Fix linting warning and errors (#8021)

* Fix lint errors

* Fix dependency loop

* address feedback

* docker compose

* remove ns entirely

* remove yarn lock changes

* update readme

* remove ref

* dockerfile and log

* update log

* debug

* rename to executor

* remove execution from rest

* exec.py

* linting

* udpate tests to use config

* fix test

---------

Co-authored-by: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com>
2024-09-10 10:05:31 +01:00
Nicholas Tindle
ef691359b7 feat: document the use of isolation better (#8028) 2024-09-09 20:55:05 +00:00
Aarushi
f8815c3053 fix(builder): Use escaped apostrophe (#8027)
* use escaped apostrophe

* linter
2024-09-09 18:08:32 +00:00
Reinier van der Leer
a60ed21404 feat(server): Add OAuth flow endpoints for integrations (#7872)
- feat(server): Initial draft of OAuth init and exchange endpoints
  - Add `supabase` dependency
  - Add Supabase credentials to `Secrets`
  - Add `get_supabase` utility to `.server.utils`
  - Add `.server.integrations` API segment with initial implementations for OAuth init and exchange endpoints
- Move integration OAuth handlers to `autogpt_server.integrations.oauth`
- Change constructor of `SupabaseIntegrationCredentialsStore` to take a Supabase client
- Fix type issues in `GoogleOAuthHandler`
2024-09-09 17:21:56 +02:00
Krzysztof Czerwinski
2618d1d87c refactor(builder): Fix linting warning and errors (#8021)
* Fix lint errors

* Fix dependency loop
2024-09-09 09:54:36 +02:00
Nicholas Tindle
92bcc39f4d Merge branch 'master' into ntindle/samples 2024-09-08 07:48:24 -05:00
Andy Hooker
e17ea22a0a feat(builder): Aligning error and loading for NextJS best practices (#7894)
* feat(builder): Add skeleton loading components for Monitor views

Introduce skeleton components for Agents, Flow Runs List, and Flow Runs Status sections to enhance loading state indication. These components help improve user experience by visually outlining content placeholders while data is being fetched.

* feat(builder): Leveraging NextJS's error boundary with error.tsx

Replace the basic error page with a more detailed and interactive error component. The new component includes a retry option, a link to the homepage, and logs the error details to the console. It also aligns with NextJS standards

---------
2024-09-08 12:47:36 +02:00
Nicholas Tindle
5909697215 Merge branch 'master' into ntindle/samples 2024-09-06 22:47:56 -05:00
Nicholas Tindle
60669903a0 fix: couple block categories improved (#8017) 2024-09-06 22:47:40 -05:00
Nicholas Tindle
bf34801a74 feat: longer timeout? 2024-09-06 22:05:16 -05:00
Nicholas Tindle
154eccb9af fix: longer for tests? 2024-09-06 21:56:25 -05:00
Nicholas Tindle
14f8a92c20 Merge branch 'master' into ntindle/samples 2024-09-06 21:48:22 -05:00
Zamil Majdy
b1b31390a4 feat(rnd): Add creation & update time for AgentGraphExecutionSchedule, AgentGraphExecution, and AgentGraph (#8015) 2024-09-06 21:29:53 +00:00
Nicholas Tindle
3c12a398ae feat: marketplace analytics (#7998) 2024-09-06 19:22:24 +00:00
Aarushi
126d070396 tweak(rnd,redis) Make redis logging more clear (#8014)
make redis logging more clear
2024-09-06 15:23:33 +00:00
Reinier van der Leer
090f22b05c fix(server): Improve logging consistency (#8012)
- Make process/service startup/shutdown messages consistent
- Configure `uvicorn` to use our logging config instead of its own
- Replace `print(..)` statements in ws_api.py with log statements
- Improve log statements in ws_api.py
2024-09-06 17:05:30 +02:00
Reinier van der Leer
1b9adf5434 fix(server): Always JSON-encode AgentNodeExecutionInputOutput data (#8010)
- Handle JSON-encoding inside `.data.execution.upsert_execution_output(..)` to ensure it is always encoded the same
- Amend `.executor.manager.execute_node(..)` to pass unencoded data into `upsert_execution_output(..)`
2024-09-06 16:58:04 +02:00
Reinier van der Leer
3bd8040d6a feat(server): Clean up resources when spinning down services/processes (#7938)
- Add SIGTERM handler and `cleanup()` hook to `AppProcess`
- Implement `cleanup()` on `AppService` to close DB and Redis connections
- Implement `cleanup()` on `ExecutionManager` to shut down worker pool
- Add `atexit` and SIGTERM handlers to node executor to close DB connection and shut down node workers
- Improve logging in `.executor.manager`
- Fix shutdown order of `.util.test:SpinTestServer`
2024-09-06 16:50:59 +02:00
Aarushi
b12dba13f4 docs(readme) Update readme to include non docker steps (#8013)
update readme
2024-09-06 15:33:17 +01:00
Swifty
2cae9ba8da feat(server): Updated Output block (#7997)
* Add Block UI Types and StickyNote Block

* Renamed StickyNote to Note

* Add comment

* Updated Input Block

* rename default_values to placeholder_values

* Update sample graph

* Update input block output to match previous change

* fixing test

* Updated the output block

* re-remove old output block
2024-09-06 15:56:41 +02:00
Swifty
3753906482 feat(builder): Block UI Types and StickyNote (#7994)
* Add Block UI Types and StickyNote Block

* Renamed StickyNote to Note

* Add comment
2024-09-06 15:52:03 +02:00
Swifty
fd54ad8666 update(server): update input nodes (#7996)
* Add Block UI Types and StickyNote Block

* Updated Input Block
2024-09-06 13:27:37 +01:00
Nicholas Tindle
2c07c64ccf Update code.py 2024-09-05 18:16:34 -05:00
Nicholas Tindle
ef21d359a6 Update code.py 2024-09-05 17:44:37 -05:00
Nicholas Tindle
f4bd998fa2 Merge branch 'master' into ntindle/samples 2024-09-05 17:43:45 -05:00
Zamil Majdy
e645cc4b33 tweak(rnd): Remove duplicated call 2024-09-05 17:20:55 -05:00
Zamil Majdy
010a8ffaaf tweak(rnd): Stop publishing custom Sentry metrics (#8000) 2024-09-05 21:51:00 +00:00
Zamil Majdy
2df325d033 fix(rnd): Fix prisma connection acquisition intermittent error on linux (#7999) 2024-09-06 03:22:25 +07:00
Nicholas Tindle
79ebc4c13b feat(builder): promotion/demotion of featured agents (#7932) 2024-09-05 20:04:11 +00:00
Zamil Majdy
e5eb42d84a tweak(rnd): Post infra change cleanup - fix process creation lifecycle (#7981) 2024-09-06 01:41:24 +07:00
Bently
d62b940baf Feat(Monitor/Builder): Fix loading of "Import from file" modal (#7976)
* Feat(Monitor/Builder): Fix loading of "Import from file" modal

* prettier

* prettier - forgot page.tsx
2024-09-05 15:02:48 +00:00
Reinier van der Leer
8fd22bcfd7 feat(server, builder): Implement "STOP" button for graph runs (#7892)
- feat(builder): Add "Stop Run" buttons to monitor and builder
  - Implement additional state management in `useAgentGraph` hook
    - Add "stop" request mechanism
    - Implement execution status tracking using WebSockets
    - Add `isSaving`, `isRunning`, `isStopping` outputs
    - Add `requestStopRun` method
      - Rename `requestSaveRun` to `requestSaveAndRun` for clarity
  - Add needed functionality for the above to `AutoGPTServerAPI` client
    - Add `stopGraphExecution` method
    - Add support for multiple handlers per WebSocket method
    - Fix parsing of timestamps in `execution_event` WebSocket messages
  - Add `IconSquare` from Lucide to `@/components/ui/icons`

- feat(server): Add `POST /graphs/{graph_id}/executions/{graph_exec_id}/stop` route
  - Add `stop_graph_run` method to `AgentServer`

- feat(server): Add `cancel_execution` method to `ExecutionManager`
  - Replace node executor `ProcessPoolExecutor` by `multiprocessing.Pool` (which has a `terminate()` method)
    - Remove now unnecessary `Executor.wait_future(..)` method
  - Add `get_graph_execution(..)` in `.data.execution`

- fix(server): Reduce number of node executors to 5 per graph executor
  This is necessary because `multiprocessing.Pool` spawns its workers on init, instead of based on demand like `ProcessPoolExecutor` does

- dx(server): Improve debug logging in `ExecutionManager`
- ci(server): Add debug logging mode to CI Pytest step

### Other improvements
Server:
- Improve output type of `ExecutionManager.add_execution(..)`
- Renamed a few things in `.server.rest_api` for consistency

Front end:
- Improved typing in `AutoGPTServerAPI` client
2024-09-05 14:42:28 +02:00
Bently
11827835a0 Feat(Builder): Add tooltips to Blocks and Save buttons (#7975) 2024-09-05 12:04:29 +00:00
Zamil Majdy
70fab8711a fix(rnd): avoid duplicating name on input/output pin for blocks (#7979) 2024-09-05 10:54:02 +00:00
Swifty
8ec015ba72 fix(builder): update tutorial routing to work on safari (#7992) 2024-09-05 10:27:15 +00:00
SwiftyOS
bc7d2f0f37 added test password as default for redis queue 2024-09-05 11:41:53 +02:00
Zamil Majdy
54694709bb tweak(rnd): Hide non required field on Blocks (#7977)
Add prompt_values for LLM block, and make it non-advanced.
Make all-field, advanced by default.
cleanup .env.example and .env.template
2024-09-05 05:00:14 +07:00
Zamil Majdy
b4b5a09b6b fix(rnd): Dockerfile Avoid full rebuild on each file change (#7971)
Co-authored-by: Aarushi <50577581+aarushik93@users.noreply.github.com>
2024-09-04 17:30:13 +00:00
Reinier van der Leer
82239dd129 fix(server): Fix node input concurrency deadlock (#7936)
In `autogpt_server.util.lock:KeyedMutex`:
- track number of pending requests for each lock
- only remove a lock from `self.locks` when the number of pending lock requests hits 0
2024-09-04 18:13:35 +02:00
Aarushi
078ad29356 update docker compose (#7972) 2024-09-04 16:19:41 +01:00
Aarushi
5000aa7ee0 tweak(rnd,docker) Remove SQLite (#7966)
* move migrations, update networking and dockignore

* update docs

* remove sqlite from ci

* remove schema linting checks

* fix formatting

* remove schema linting

* add test script

* formatting and linting

* stop pg not down

* seperate test db

* diff port

* remove duplicate
2024-09-04 10:18:57 +01:00
Bently
dc1077f893 Feat(Builder): Replace `Math Block with Calculator Block` (#7969) 2024-09-04 08:12:01 +00:00
Swifty
80df44a978 feat(server): Add endpoint to calculate required graph inputs (#7965) 2024-09-04 09:45:45 +02:00
Zamil Majdy
c2a79d2f10 feat(rnd): Add Node & Graph level execution stats instrumentation (#7957)
### **User description**
### Background

The scope of this change is collecting the required information that will be needed for the execution analytics.

### Changes 🏗️

* Add sentry integration.
* Refactor logging_metadata on manager.py.
* Collect graph-level & node-level instrumentation.
* Introduced `stats` column for `AgentNodeExecution` & `AgentGraphExecution`.
2024-09-04 02:45:19 +07:00
Bently
7db85a8990 Feat(Builder): Update tutorial to work with latest block UI (#7962)
* Feat(Builder): Update tutorial to work with latest block UI

* prettier
2024-09-03 14:56:55 +00:00
Krzysztof Czerwinski
0454a9a7be fix(builder): Prevent zooming on input field modal
- Add `nowheel` class to Textarea parent div
2024-09-03 14:30:35 +02:00
Krzysztof Czerwinski
09951fed4b feat(builder, server): Add advanced block inputs (#7934)
- Add `advanced` to `SchemaField` and pass it to `json_extra`
- Add `advanced` to `BlockIOSubSchemaMeta` type
- Update `CustomNode`, so that:
  - non-required advanced inputs are hidden
  - non-advanced and required inputs are always shown
2024-09-03 12:01:42 +01:00
Aarushi
6204d82d84 feat(server): Integrate forge.logging (#7915)
* feat(server): Integrate `forge.logging`

- Add `configure_logging()` in `.util.logging` - a wrapper for `forge.logging.configure_logging()` with project-specific extras
- Call `configure_logging()` in `.app.main()`, and in child process initializers (e.g. `AppProcess.execute_run_command(..)`, `ExecutorManager.on_graph_executor_start()`)
- Change some `logger.warning` statements to `logger.info` where appropriate

* fix warnings to info

* fix(rnd): Fix broken test and Input/Output block field renaming

* Rename

* fix(rnd): Fix flaky CI

* feat(server): Add OAuth handlers for GitHub, Notion, Google & amend store data structure (#7868)

- Add `BaseOAuthHandler` + 3 initial implementations
  - Add `GitHubOAuthHandler`
  - Add `NotionOAuthHandler`
  - Add `GoogleOAuthHandler`
- Amend `OAuth2Credentials` type
  - Add `metadata` attribute
  - Make `access_token_expires_at`, `refresh_token`, `refresh_token_expires_at` optional

* extend GCP Logger

* update manager & add flag

* linting

* use default logger behaviour

* update messages

* update another message

---------

Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
2024-09-03 11:56:21 +01:00
Krzysztof Czerwinski
8c9fe5c167 feat(builder): Resizable input modal (#7955)
- Add minimize/maximize button in the corner of modal to make it significantly larger and centered
- Add copy button to copy all text
- Add optional `title` to display as a modal header
2024-09-03 11:42:25 +01:00
Reinier van der Leer
71de1a6a5e fix(server): Fix type checking and propagation issues (#7941)
- fix type propagation by `AppService.run_and_wait(..)`
- fix type propagation by `@expose` and add note
- fix type propagation by `wait(..)` in `.executor.manager.execute_node(..)`
- fix type propagation by `wait(..)` in `.executor.manager._enqueue_next_nodes(..)`
- remove unnecessary null checks for `.data.graph.get_node(..)`
- fix type issue in `ExecutionScheduler`
- reduce use of `# type: ignore` in `.data.execution`
- reduce usage of `# type: ignore` in `.executor.manager`
- reduce usage of `# type: ignore` in `.server`
- reduce usage of `# type: ignore` in cli.py
- update `pyright` to v1.1.378
2024-09-02 14:13:56 +00:00
Bently
956165adf3 Feat(Builder): Fix block menu width (#7946) 2024-09-02 09:54:52 +00:00
Aarushi
e4dc16a867 docs(server): Update docs with new docker compose steps (#7944)
* update setup

* add docker links
2024-09-02 10:55:54 +02:00
Aarushi
cfa0b6610c feat(rnd,infra): Pull out websockets away from server api (#7899)
* standalone websocket server

* add websocket url

* wip: talk to ws directly

* rename to api server

* dockerfile and queue

* fix paths

* update poetry lock

* helm charts for websockets

* create seperate deployments for websockets and rest server with redis queue for async comms

* delete duplicate queue

* add depends in ws_api

* singleton for conn manager

* update from review

* fix CI

* address feedback

* update readme

* update docker file and add migration step in readm

* ad watch

* add step to copy example env file

* put connect back in
2024-09-02 10:32:51 +02:00
Krzysztof Czerwinski
933baa0e8d feat(builder): Store and display output data history in nodes (#7930)
- Update styling and use tailwind more
- Add `react-toast` dependency
- Fix output button not changing checked state on execution
- Make status a badge in node's corner
- Rename `output_data` to `executionResults` and store multiple results with execution UUIDs
- Add `DataTable` component that displays execution outputs
- Outputs can be copied and there's a toast displayed as a feedback
2024-09-01 11:18:57 +01:00
Bently
370b2dabe8 Feat(Builder): Add block categories to block menu (#7918) 2024-08-31 19:22:37 +00:00
Nicholas Tindle
baa00a5b03 feat(builder,server): add review of agent submissions to the admin portal (#7914)
Co-authored-by: Aarushi <aarushik93@gmail.com>
2024-08-30 18:20:59 +00:00
Zamil Majdy
60a8e00578 fix(rnd): Fix overflowing card due to long block name (#7931) 2024-08-30 18:02:55 +00:00
Bently
85e7d678ce Feat(Builder): Fix Multiple Connections Between Pins (#7924) 2024-08-30 18:15:18 +01:00
Krzysztof Czerwinski
476b307d69 feat(builder): Draw selected edges above nodes (#7893) 2024-08-30 16:39:22 +01:00
Bently
5dbfb4e3f1 feat(builder): Turn block border red on error (#7910)
* Turn block border red on error

* prettier
2024-08-30 15:09:19 +00:00
Zamil Majdy
f6d09c74f5 feat(rnd): Rename Blocks (#7925)
### Background

Standardize block names with a focus of making them intuitive to a non-technical person.

### Changes 🏗️

Replace these names:
* TextLlmCallBlock -> AITextGeneratorBlock
* ObjectLlmCallBlock -> AIStructuredResponseBlock
* AdvancedLlmCallBlock -> AIConversationBlock
* CreateTalkingAvatarClipBlock -> CreateTalkingAvatarVideoBlock
* DiscordReaderBlock -> ReadDiscordMessagesBlock
* DiscordMessageSenderBlock -> SendDiscordMessageBlock
* YouTubeTranscriberBlock -> TranscribeYouTubeVideoBlock
* CreateMediumPostBlock -> PublishToMediumBlock
* ForEachBlock -> ListIteratorBlock
* MathsBlock -> CalculatorBlock
* CounterBlock -> CountItemsBlock
* ValueBlock -> StoreValueBlock
* PrintingBlock -> PrintToConsoleBlock
* DictionaryAddEntryBlock -> AddToDictionaryBlock
* ListAddEntryBlock -> AddToListBlock
* ObjectLookupBlock -> FindInDictionaryBlock
* RedditGetPostsBlock -> GetRedditPostsBlock
* RedditPostCommentBlock -> PostRedditCommentBlock
* WikipediaSummaryBlock -> GetWikipediaSummaryBlock
* WebSearchBlock -> SearchTheWebBlock
* WebScraperBlock -> ExtractWebsiteContentBlock
* GetOpenWeatherMapBlock -> GetWeatherInformationBlock
* HttpRequestBlock -> SendWebRequestBlock
* CurrentTimeBlock -> GetCurrentTimeBlock
* CurrentDateBlock -> GetCurrentDateBlock
* CurrentDateAndTimeBlock -> GetCurrentDateAndTimeBlock
* TimerBlock -> CountdownTimerBlock
* RSSReaderBlock -> ReadRSSFeedBlock
* TextMatcherBlock -> MatchTextPatternBlock
* TextParserBlock -> ExtractTextInformationBlock
* TextFormatterBlock -> FillTextTemplateBlock
* TextCombinerBlock -> CombineTextsBlock
2024-08-30 21:36:42 +07:00
Zamil Majdy
6d17e627e8 fix(rnd): Add null checking & remove console logs on Input UI component (#7927)
fix(rnd): And null checking & remove logs
2024-08-30 12:21:09 +00:00
Bently
5cfa807f00 Feat(Builder): Make the copy block button work (#7920)
* Feat(Builder): Make the copy block button work

* prettier

* Fixes

* fix block id

* prettier

* fix type
2024-08-30 11:48:33 +00:00
Bently
6fff06f0f6 Feat(Builder): Fix delete block is non-undoable (#7922)
* Feat(Builder): Fix delete block is non-undoable

* prettier
2024-08-30 08:05:37 +00:00
Aarushi
cbe553a547 feat(infra): Add deployment for Market (#7907)
* deployment for marketplace

* set up deployment for marketplace
update nodes

update helm

* update health check & allow builder origin
2024-08-29 18:23:40 +00:00
Reinier van der Leer
96ef35536c ci(server): Run tests even if linting fails 2024-08-29 15:42:49 +02:00
Reinier van der Leer
087d3a3760 test(server): Add type to server fixture usages 2024-08-29 15:42:15 +02:00
Zamil Majdy
5da58aa284 fix(rnd): Fix jumping caret problem on builder input text field (#7917)
Issue 1:
Input text field cursor keeps moving to the end of the text.
Try to type "Hello World!" into the input text. Then try to type "some string" in the middle of the "Hello" and "World".

Issue 2:
History should only tracks on the input box onBlur/onLeave
Try to type a "longcharacters" and try to undo it, the undo is removing 1 character at a time, polluting the history, and make the undo pretty much unusable.

Issue 3:
KeyValue & ArrayInput is non-undoable.
Try to add key-value or add an entry to the list, it doesn't undo the value, but you need to click as many number of entries being added to make the undo work again
2024-08-29 13:12:10 +00:00
Bently
7de12a2200 Feat(Builder): Add first guide tutorial (#7862)
* Feat(Builder): Add first guide tutorial

* added more steps + some fixes

* added local storage to fix starting every time going to build

* update copy & paste to support mac

* small fix

* Prettier fixes

* Added "Skip Tutorial" button to first step

* some fixes based on requests

* revert camelCase change

* add ability to use url to reset tutorial

* prettier

* Added Tutorial button next to tally

* prettier

* change pinBlocksPopover to setPinBlocksPopover

* fixes + update + prettier

* made the resetTutorial url dynamic

* force to /build on reset tutorial

* fix renaming

* prettier
2024-08-29 07:53:45 -05:00
Reinier van der Leer
8f1c63a7ea feat(server): Add OAuth handlers for GitHub, Notion, Google & amend store data structure (#7868)
- Add `BaseOAuthHandler` + 3 initial implementations
  - Add `GitHubOAuthHandler`
  - Add `NotionOAuthHandler`
  - Add `GoogleOAuthHandler`
- Amend `OAuth2Credentials` type
  - Add `metadata` attribute
  - Make `access_token_expires_at`, `refresh_token`, `refresh_token_expires_at` optional
2024-08-29 09:11:37 +00:00
Zamil Majdy
6ec200f912 fix(rnd): Fix flaky CI 2024-08-28 15:59:51 -05:00
Zamil Majdy
b5db7f575e Rename 2024-08-28 15:56:02 -05:00
Zamil Majdy
98c909f99f fix(rnd): Fix broken test and Input/Output block field renaming 2024-08-28 15:54:48 -05:00
Krzysztof Czerwinski
c5615aa862 fix(builder): Prevent overflow in node output (#7912) 2024-08-28 16:27:42 +01:00
Bently
e725305e15 fix(builder): Set default value in NodeStringInput to silence uncontrolled input warning (#7909)
add default value to fix bug
2024-08-28 12:44:06 +01:00
Aarushi
9551f54c35 feat(infra): Add websockets IP (#7905)
tf changes from original branch to unblock other work
2024-08-27 20:35:31 +01:00
Bently
777f7d25bf Fix copy-paste of text creating unintended blocks (#7903) 2024-08-27 11:49:35 +01:00
Zamil Majdy
ea6f37bf98 feat(rnd): Integrate Jinja2 into TextFormatter (#7891)
Add Jinja2 to TextFormatter while maintaining backward compatibility.
2024-08-27 07:17:57 +00:00
Nicholas Tindle
4ebae90f62 Merge branch 'master' into ntindle/samples 2024-08-26 13:05:11 -05:00
Krzysztof Czerwinski
299530cf95 refactor(builder): Update ReactFlow to version 12 & split up Flow.tsx (#7808)
Update ReactFlow to version 12 and split `Flow.tsx` into `useAgentGraph` hook that takes care of agent state and API calls to the server.

- Update ReactFlow to v12 ([migration guide](https://reactflow.dev/learn/troubleshooting/migrate-to-v12))
- Move `setIsAnyModalOpen` to `FlowContext`
- Make `setHardcodedValues` and `setErrors` functions of `CustomNode` and utilize new `updateNodeData` ReactFlow API
- Fix type errors
- `useAgentGraph` hook
  - Take care of all API calls, websocket, agent state and logic
  - Make saving and execution async and thus more consistent and reliable
	- Save&run requests are state
	- Wait for node ids to sync with backend reactively
	- Queue execution updates
  - Memoize functions using `useCallback`
2024-08-26 11:45:05 +01:00
Nicholas Tindle
1df7d527dd Remove debug (#7890) 2024-08-25 17:59:41 +00:00
Aarushi
407cf858e7 fix(server): Ensure nameserver is started before other processes (#7788)
ensure nameserver is started before other processes
2024-08-24 20:54:11 +00:00
Aarushi
a670b384f6 tweak(infra): Add 1 more node to GKE (#7889)
add one more node
2024-08-24 18:53:24 +01:00
Zamil Majdy
f9b8b0a41a feat(rnd): Add dynamic input pin for input object construction (#7871)
### Background

Currently, there is no way to construct the output of nodes into a composite data structure (list/dict/object) using the builder UI.

The backend already supports this feature by connecting the output pin to the input pin using these format:
* <pin_name>_$_<list_index> for constructing list
* <pin_name>_#_<dict_key> for constructing dict
* <pin_name>_@_<field_name> for constructing object

The scope of this PR is implementing the UX for this in the builder UI.

### Changes 🏗️

<img width="765" alt="image" src="https://github.com/user-attachments/assets/8fc319a4-1350-410f-98cf-24f2aa2bc34b">

This allows you to add more pins in a key value & list input: `_$_` list constructor & `_#_` dict constructor.
2024-08-23 18:21:38 +00:00
Zamil Majdy
e59e138352 fix(rnd): Prevent boolean with no default value on AGPT-builder (#7884)
### Background

Boolean without default value is a UX problem. It's currently displayed as a toggle and it has no way to describe the `null` value.
So we need to prevent blocks from introducing a nullable boolean.

### Changes 🏗️

Add explicit check to prevent nullable boolean. Fix existing block field that has nullable boolean.
2024-08-23 18:03:21 +00:00
Aarushi
a95ee693dd fix(rnd): Add missing libs folder (#7887)
add missing libs folder
2024-08-23 16:42:24 +00:00
Aarushi
26f56114d1 feat(infra): Add ws server infra changes (#7886)
add ws server infra changes
2024-08-23 11:25:57 -05:00
Aarushi
45ace8ccab Add number of workers to environment variables in in charts (#7869)
add workers to env vars
2024-08-23 20:21:51 +07:00
Aarushi
95af63b5ad feat(libs): Add integration credentials store (#7826)
- Add `SupabaseIntegrationCredentialsStore` in `.supabase_integration_credentials_store`
- Add `supabase` dependency
- Add `pydantic` dependency

---------

Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
2024-08-23 12:39:26 +02:00
Aarushi
012bad72e8 feat(rnd,blocks): Add D-ID Block (#7798)
* talking head

* linting

* remove clip id, not needed

* add more descriptive name

* add min requirement to polling attempts and intervals

* add docs and link to docs

* remove extra space

* force new tab

* fix linting

* add did key to .env.template
2024-08-23 09:39:55 +01:00
Nicholas Tindle
efcd0f93ed ci(all): take two on the global status checker (#7849)
* ci(all): fundamentally change how we do this

* Update workflow-checker.yml

* ci: dupe and move file in attempt to figure out why its not accessible

* Update workflow-checker.yml

* fix: sleep before checking, move files, improve code

* Update workflow-checker.yml

* Update check_actions_status.py

* Update check_actions_status.py

* Update workflow-checker.yml

* Update workflow-checker.yml

* ci: remove debug, add more

* Update check_actions_status.py

* Update check_actions_status.py

* Update check_actions_status.py

* Revert "Update check_actions_status.py"

This reverts commit 36134527af.

* Revert "Update check_actions_status.py"

This reverts commit 828aabf532.

* Update check_actions_status.py

* Update check_actions_status.py

* Update check_actions_status.py

* Update check_actions_status.py
2024-08-22 07:06:15 -05:00
Swifty
4c32b46d40 feat(builder): Add google analytics to the project (#7860) 2024-08-22 10:24:12 +01:00
Konstantinos Voulgaropoulos
41fbfe35fb tweak(rnd): Trim Whitespace from BlockSecret to Prevent Authentication Issues (#7789)
* ensure secret value of BlockSecret is always trimmed

* avoid logging secret values when trimming
2024-08-21 14:44:26 -05:00
Zamil Majdy
c719e4f177 fix(rnd): Fix JS event-loop freeze caused by websocket connection retry (#7861)
### Background

Websocket connection retry has no backoff period which causes event-loop freeze.

### Changes 🏗️

Add a back-off period on retry.
2024-08-21 21:41:15 +07:00
Zamil Majdy
3d62cec553 fix(rnd): Guarantee execution ordering per node by waiting the node completion (#7855)
### Background

We don't have an ordering guarantee on the node execution.
Let's say we have a node that has to execute different data A, B, and C.
The current implementation limits the execution to 1 execution at a time, but there is no guarantee that A, B, and C will be executed in order.

The initial implementation did not have any restrictions, so it used to be A, B, and C executed in parallel
In the current implementation with the per-node constraint, it's A, B, C are executed serially but with no guarantee of ordering.

The scope of this PR is to guarantee that order.

### Changes 🏗️

Guaranteeing the execution per node ordering by avoiding any re-enqueue mechanism. If there are two executions run in the same node, the first one will be executed and the other will block. The blocking mechanism is indeed sub-optimal, the performance improvement can be done later (a follow-up issue will be added).
2024-08-21 19:08:18 +07:00
Aarushi
fa12564954 bug(infra): Add frontend config to ingres (#7854)
add frontend config to ingres
2024-08-21 11:31:16 +01:00
Nicholas Tindle
f6d8e597e1 clean(builder): learned we should be using this to keep our tailwind classes sorted (#7836) 2024-08-21 05:14:21 -05:00
Krzysztof Czerwinski
a1cbc101a5 fix(builder): Correctly display static links on new edges (#7851)
Fix static links on edge creation
2024-08-21 10:24:01 +01:00
Aarushi
afc8338145 feat(infra) Create builder account with necessary permissions (#7840)
* create builder account with necessary permissions

* remove unrelated changes
2024-08-21 10:18:51 +01:00
Aarushi
7fe4e455fd feat(infra): Ensure http is always redirected to HTTPS (#7853)
redirect to https always
2024-08-21 10:01:06 +01:00
Bently
52d40d0f8b Feat(Builder): Make blocks spawn in the center of the screen not at 0,0 (#7805)
Feat(Builder): Make nodes spawn in the center of the screen not at 0,0
2024-08-20 15:05:12 +01:00
Aarushi
9e35f8c5cb tweak(infra): Rename builder for consistency (#7837)
rename builder for consistency
2024-08-20 13:37:40 +01:00
Nicholas Tindle
c0afb133a7 feat(builder): checkbox for tos on login page and submit agent (#7745)
* feat(builder): checkbox for tos on login page

* feat(builder): submit agent page

DOES NOT WORK

* feat(builder): basic upload (not working)

* feat(builder): submit page more working but still not

* fix(builder): working categories, not dynamic

* feat(builder, server): enable submissions (auth error)

* fix(lint): linting

* feat(builder): submit page terms of service

* fix(builder): update lockfile

* lint(builder): lint marketplace files
2024-08-20 07:04:22 -05:00
Bentlybro
09d3768948 fix output to make pytest work 2024-08-17 21:37:32 +01:00
Nicholas Tindle
8c6adaeaa1 feat(server): linting and bug fix on llm 2024-08-16 19:22:12 -05:00
Nicholas Tindle
dabd2e1610 Merge branch 'master' into ntindle/samples 2024-08-16 17:19:36 -07:00
Zamil Majdy
526364297c feat(rnd): Add staticOutput field on block API (#7802) 2024-08-16 22:13:10 +07:00
Krzysztof Czerwinski
aed067e61c feat(builder): Support static connections (#7799)
- Add static link/connection support on the frontend and display them as dashed lines
- Remove queueing for static connections - there'll always be only one bead waiting at the end
- Make beads slightly larger and further from the end arrow
2024-08-16 13:39:48 +01:00
Zamil Majdy
653eb4964f fix(rnd): Fix graph validation error message (#7797) 2024-08-16 17:10:11 +07:00
SwiftyOS
406206f5d0 Add categories to all the blocks 2024-08-16 10:11:30 +02:00
SwiftyOS
1e05d6a8e9 added categories 2024-08-16 10:10:18 +02:00
Nicholas Tindle
b228c4445e feat(server): much better execution of unified 2024-08-15 10:29:03 -05:00
Nicholas Tindle
05c9931c11 feat(server): more complicated blocks 2024-08-14 21:42:04 -05:00
Nicholas Tindle
9198a86c0e fix(server): no default was provided 2024-08-14 21:41:45 -05:00
Nicholas Tindle
c8fedf3dad feat(server): even more advanced 2024-08-14 21:28:51 -05:00
Nicholas Tindle
0c7e1838cd feat(server): more advanced coding blocks 2024-08-14 21:20:35 -05:00
Nicholas Tindle
979d80cd17 feat(server): broken code exec lol 2024-08-14 21:06:59 -05:00
Nicholas Tindle
4f7ffd13e4 feat(server): timeouts on code 2024-08-14 21:01:25 -05:00
Nicholas Tindle
b944e0f6da feat(server): code args 2024-08-14 20:57:38 -05:00
Nicholas Tindle
51aaaf6ddc fix(server): stratified sampling 2024-08-14 20:42:46 -05:00
Nicholas Tindle
3c662af1ba feat(server): allow yielding all data at once rather than row by row 2024-08-14 20:40:17 -05:00
Nicholas Tindle
17370116f6 feat(server): improve various sampling techniques 2024-08-14 20:39:48 -05:00
Nicholas Tindle
d15049e9a7 Merge branch 'master' into ntindle/samples 2024-08-14 16:12:00 -05:00
Nicholas Tindle
da4afd4530 fix(server): anthropic retry didn't work 2024-08-14 13:48:12 -05:00
Nicholas Tindle
7617aa6d1f Merge branch 'master' into ntindle/samples 2024-08-14 13:29:39 -05:00
Krzysztof Czerwinski
848637bfeb feat(builder): Visualise data beads on connections (#7791)
* Visualise data beads on edges

* Add `useBezierPath` hook

* Fix edge color on load

* Updates

* Merge branch 'master' into kpczerwinski/open-1580-visualise-data-coming-down-connections

* Add `visualizeBeads` state in `FlowEditor`

* Add `FlowContext`

Allow disabling beads animation

* fix(builder): linting

---------

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-14 13:29:19 -05:00
Swifty
cea81bfe4e feat(builder): Updated Block Categories and colouring (#7794)
* Updated Block Categories

* formatting

* Added category coloring
2024-08-14 11:06:01 +02:00
Zamil Majdy
1e92c284d9 fix(rnd): Fix text blocks, make outbound links on ValueBlock static (#7796) 2024-08-14 10:03:43 +01:00
Swifty
98c1cb8ff9 feat(builder): Added tooltips for blocks (#7793) 2024-08-14 10:41:01 +02:00
Nicholas Tindle
b190e1f2aa feat(server): sampling and code block 2024-08-14 00:13:15 -05:00
Zamil Majdy
58dc8296db fix(rnd): Fix unparsed string on HTTP block (#7795) 2024-08-14 06:19:46 +02:00
Zamil Majdy
4782f4383c fix(rnd): string to list auto-conversion 2024-08-13 18:08:24 +02:00
Zamil Majdy
2b60a392fb fix(rnd): Fix broken test 2024-08-13 17:51:59 +02:00
Konstantinos Voulgaropoulos
f30b2cdf25 inclusion of poetry run prisma generate in the documentation (#7790) 2024-08-13 10:46:28 -05:00
Zamil Majdy
9084c31662 fix(rnd): Make discord bot continuously running (#7792) 2024-08-13 10:39:39 -05:00
SwiftyOS
183c72b2d0 fix typo 2024-08-13 15:07:57 +02:00
Swifty
55e100ee1e tweak(builder) Changes to transparency, connector color and title boldness (#7766)
Changes to transparency, connector color and title boldness
2024-08-13 15:07:07 +02:00
Toran Bruce Richards
82c5cd2d79 Add Advanced Chatbot with History using Discord (#7786)
* Add Advanced Chatbot with History using Discord

* Update Discord Chatbot with History_v145.json

update is_active to false and is_template to true

---------

Co-authored-by: Bently <tomnoon9@gmail.com>
2024-08-13 09:03:14 +01:00
Aarushi
f0ab795248 fix(builder,server) Add missing create user calls (#7784)
* fix missing create user calls

* formating and linting
2024-08-09 16:49:21 +01:00
Zamil Majdy
5b9caa4345 fix(rnd): Added run-time implicit type conversion on agent node execution (#7775)
The execution graph is supposed to be typed, but there are cases where generic types like Any were used, and there are cases, where incompatible data passed into the wrong type. 

If such a thing happens on runtime, we should do the best-effort conversion instead of breaking the run. E.g.: try to json-stringify the object to str input, or try to parse number in the string to int input, etc.
2024-08-09 20:38:33 +07:00
Bently
1e054064f6 Feat(Builder): Allow for zooming out more on builder UI (#7760)
add min and max zoom
2024-08-09 07:43:06 -05:00
Aarushi
646d98470f fix(builder): Temporarily disbale TS checks (#7777)
* disable ts checks temporarily

* prettier
2024-08-09 09:59:39 +01:00
Reinier van der Leer
5a68be5419 refactor(server, builder): Remove unused/duplicate WebSocket endpoints (#7740)
* Remove WS API method `runGraph` in favor of REST method `executeGraph`

* Remove unused WebSocket methods from `autogpt_server.server`
2024-08-08 17:01:37 +02:00
Reinier van der Leer
2ff8a0743a fix(server): Make userId required on DB entities and apply default ID to existing entries (#7755)
* Make `userId` required on DB entities `AgentGraph`, `AgentGraphExecution`, and `AgentGraphExecutionSchedule`

* Add SQLite and Postgres migrations to make `userId` required and set `userId` to `3e53486c-cf57-477e-ba2a-cb02dc828e1a` on existing entries without `userId`

* Amend `create_graph` endpoint and `.data.graph`, `.data.execution` methods to handle required `user_id`

* Add `.data.user.DEFAULT_USER_ID` constant to replace hardcoded literals
2024-08-08 16:57:59 +02:00
Reinier van der Leer
582571631e fix+refactor(builder): Sort out FlowEditor+ReactFlow state management (#7737)
* refactor(builder): Migrate `FlowEditor` to use ReactFlow's state management system

  We have been keeping two copies of node and edge data: one inside ReactFlow and one outside.
  It works, but it's accidental and implicit and there is no reason to be using shadow copies rather than a single data source.

  - Replace `useNodesState` and `useEdgesState` with `useReactFlow` hook
  - Use `addNodes`, `addEdges`, and `deleteElements` where appropriate instead of `setNodes`/`setEdges` to allow use of event hooks
  - Consolidate all edge -> node state sync logic into `onEdgesChange` event handler
    This replaces `updateNodesOnEdgeChange`, part of `onConnect`, and `onEdgesDelete`.
  - Move node deletion logic from `CustomNode` to `FlowEditor:onNodesChange`

* fix(builder): Refactor and fix copy-paste mechanism
  - Rename variables for readability
  - Use an ID map to correctly set the source and target IDs for the pasted edges
2024-08-08 16:18:15 +02:00
Reinier van der Leer
bf10df612e feat(builder): Move /monitor to / (#7769)
- Move `monitor/page.tsx` to `page.tsx`
- Remove redirect from `/` to `/build`
- Set temporary redirect from `/monitor` to `/` to prevent breaking UX (may be removed after a grace period, e.g. 2024-09-01)
2024-08-08 16:07:12 +02:00
SwiftyOS
c577d04692 Changed background to white and add an effect on it 2024-08-08 15:50:59 +02:00
Swifty
85d895ef77 tweak(builder): Node styling (#7763)
* wix width

* teak(builder): blocks control styling

* tweak node styling

* formatting
2024-08-08 13:16:38 +01:00
Swifty
deacc2bd8f tweak(builder) block control styling (#7762)
* wix width

* teak(builder): blocks control styling
2024-08-08 13:15:04 +01:00
Swifty
3eb0d73461 fix(builder): Fix the control panel being too narrow for its elements (#7758)
wix width
2024-08-08 13:38:40 +02:00
Swifty
be0f6498ed fix(builder): Added header background and moved some styling to tailwind (#7756)
Added header background and moved some styling to tailwind
2024-08-08 12:12:20 +01:00
Swifty
0bab2714e9 feat(market): Agent Submission Process (#7718)
* adding auth to store

* Add ability to submit agents and review them before being added to the market

* Added auth decorator

* Added auth to market api client

* fix(builder): Fix drag-select behavior on `NodeKeyValueInput`

* fix(github): Added in fallback variables for postgres testing (#7715)

Co-authored-by: Leslie Cruz <lelcruz@users.noreply.github.com>
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>

* feat(builder): basic tally feedback form (#7725)

* removed database changes

* moved auth to libs project

* fixed formatting

* cleaned up auth

* Added tests and database migration

* delete decorator
2024-08-08 11:12:35 +02:00
Aarushi
9c74d76a3a fix(tests): Ensure user ID is not None on schedule in tests (#7749)
* ensure user id is never None on schedule

* remove comment
2024-08-08 11:11:57 +02:00
Bently
78e96f8a1a Feat(Builder): Add TimerBlock (#7728) 2024-08-08 09:59:06 +01:00
Bently
904b444b13 Feat(Builder): Add email sending block (#7736) 2024-08-08 10:57:39 +02:00
Andy Hooker
3cad0f89ee feat(builder): Standardize icons and integrate with ControlPanel and NavBar (#7743)
* feat(builder): Add new icons and integrate with ControlPanel

Introduce SVG icons and replace ControlPanel dependencies with new icons. The ControlPanel component now uses the new icon components for improved consistency.

* feat(builder): add additional icon and update icons in NavBar

Introduced the new icon with relevant documentation and examples. Replaced existing icon imports with the newly defined icons in the NavBar component for consistency.

* feat(builder): Add icon for megaphone and replace usage

Introduced the IconMegaphone for reuse across the application. Updated TallyPopup to utilize IconMegaphone instead of the previous Megaphone icon from lucide-react.

* fix(builder): Running prettier to format changed files.

Adjusted various files for consistent code formatting. Ensured proper spacing and alignment of imports, JSX tags, and object properties to enhance readability and maintain coding standards. No functional changes made.
2024-08-08 10:55:57 +02:00
lelcruz
8131fc385b fix(builder): Added option to pan view to center agents on build screen (#7742)
Co-authored-by: Leslie Cruz <lelcruz@users.noreply.github.com>
Co-authored-by: Bently <tomnoon9@gmail.com>
2024-08-08 10:54:37 +02:00
Reinier van der Leer
335fea8605 fix(builder): Always show connected optional pins on node (#7724)
- Always show connected optional pins on node
- Refactor `hasOptionalFields()` to a constant `hasOptionalFields`
2024-08-08 10:48:14 +02:00
Reinier van der Leer
55d32f0324 fix(builder): Automatically (re)connect websocket when sending message (#7739)
- Amend `AutoGPTServerAPI.sendWebSocketMessage(..)` to automatically (re)connect the websocket if disconnected
- Amend `AutoGPTServerAPI.connectWebSocket()` to prevent race conditions
2024-08-08 09:09:50 +01:00
Swifty
56ce7ac628 feat(market): Added Auth to Market and Protect routes (#7717)
* adding auth to store

* Added auth decorator

* Added auth to market api client

* removed database changes

* moved auth to libs project

* fixed formatting

* Switched to using fastapi dependencies

* Return a user object

* removed logging of the token

* Added tests
2024-08-08 09:28:44 +02:00
Zamil Majdy
81adf84032 fix(rnd): Fix flaky CurrentDateAndTimeBlock test (#7741)
### Background

CurrentDateAndTimeBlock would fail if the test is not complete within 1-second wall-time.
In the case a test started at the second 01:59:59, it becomes flaky.

We can change the test to only assert the type. But this is also a good chance to add more assertion options for Block: a custom function.

### Changes 🏗️

Change assertion for the time block using an additional margin of error.
2024-08-07 23:44:47 -05:00
Aarushi
f8d07a27af feat(builder): Add jwt support on websockets (#7734)
add jwt support on websockets
2024-08-07 14:47:35 +01:00
Aarushi
1bad26657c feat(rnd): Add user management (#7663)
* add user management support

* fixing tests

* fix formatting and linting

* default user, formatting & linting

* remove unused code

* remove default creation when auth enabled

* add client side calls

* addressing feedback

* prettier

* add defaults for websockets

* linting

---------

Co-authored-by: Swifty <craigswift13@gmail.com>
2024-08-07 14:15:29 +01:00
Nicholas Tindle
31dbb543a2 feat(builder): basic tally feedback form (#7725) 2024-08-07 09:14:18 +02:00
lelcruz
60d25135e6 fix(github): Added in fallback variables for postgres testing (#7715)
Co-authored-by: Leslie Cruz <lelcruz@users.noreply.github.com>
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-06 15:09:02 -05:00
Reinier van der Leer
4678ed2e57 fix(builder): Fix drag-select behavior on NodeKeyValueInput 2024-08-06 20:53:22 +02:00
Nicholas Tindle
98a07f1265 Add new agent ideas to docs (#7716) 2024-08-06 13:45:28 -05:00
Bently
5e8ff5e3ed Feat(Builder): Add discord bot templates (#7711)
Add discord bot templates
2024-08-06 19:44:43 +01:00
Bently
89adcefd63 Feat(Builder): added time blocks (#7710)
* added time blocks

* lint & format
2024-08-06 13:43:43 -05:00
Reinier van der Leer
d82e577196 feat(builder): Rewrite & split up node inputs (#7722)
- feat(builder): Rewrite & split up `NodeInputField`
  - Create `NodeObjectInputTree`
  - Create `NodeGenericInputField`
  - Create `NodeKeyValueInput`
  - Create `NodeArrayInput`
  - Create `NodeStringInput`
  - Create `NodeNumberInput`
  - Create `NodeBooleanInput`
  - Create `NodeFallbackInput`
  - Create `ClickableInput` from `renderClickableInput(..)`
  - Amend usage in `CustomNode`
  - Remove deprecated/unused styling from `flow.css` and `customnode.css`
  - Fix alignment between `NodeHandle` and `NodeInputField`
  - Split up `BlockIOSchema` & rename to `BlockIOSubSchema`
    - Create `BlockIOObjectSubSchema`
    - Create `BlockIOKVSubSchema`
    - Create `BlockIOArraySubSchema`
    - Create `BlockIOStringSubSchema`
    - Create `BlockIONumberSubSchema`
    - Create `BlockIOBooleanSubSchema`
    - Create `BlockIONullSubSchema`
  - Install `Select` component from shad/cn

- refactor(builder): Replace boilerplate `button` styling with `Button` components

- refactor(builder): Move `NodeInputField.tsx` to `node-input-components.tsx`

---------

Co-authored-by: Krzysztof Czerwinski <kpczerwinski@gmail.com>
2024-08-06 20:32:49 +02:00
Andy Hooker
e6cc8687a5 feat(builder): Refactor components and types for monitoring page streamlining (#7714) 2024-08-05 21:13:08 -05:00
Aarushi
fbad0d01ee feat(rnd, infra): Add deployment for frontend (#7705)
* add helm charts

* dont check in secrets
2024-08-05 21:12:57 +01:00
Zamil Majdy
fe5c1968bc fix(rnd): Cleanup block names and add explicit block name check (#7709) 2024-08-05 17:58:03 +00:00
Swifty
951abf6d5b feat(rnd) Agent Marketplace MVP (#7657)
* Added listing, sorting, filtering and ordering of agents

* feat(market): general upkeep for vscode and small docs

* feat(market): most of search

* fix(market): hinting on the sort was weird + linting

* feat(market): migrations and schema updates

* lint(market): autolint

* feat(market): better search

* feat(market): file download

* feat(market): analytics of downloads

* Added tracking of views

* changed all imports to be fully qualified

* Upgrade sentry sdk

* Added an admin endpoint to submit new agents

* fixes

* Added endpoint that just tracks download

* Starting adding the marketplace page

* Marketplace client

* Create template of the marketplace page

* Updated client

* fix(market): debug port

* feat(market): agents by downloads

* fix(market, builder): hook up frontend and backend

* feat(builder, market): build a "better" market page that loads data

* feat(builder): updated search (working) and page (kinda working)

* feat(builder): add a feature agents ui (not backed yet)

* feat(builder): improve detail page content

* Added run script

* Added pre populate database command

* Add AnalyticsTracker on create agent

* Add download counts for top agents

* Add hb page prometheus metrics

* Added featured agents funcitonality

* renamed endpoint to health

* Adding download flow

* normalised api routes

* update readme

* feat(market) : default featured

* formatting

* revert changes to autogpt and forge

* Updated Readme

* Eerror when creating an agent from a template installed from (#7697)

fix creating graph from template

* Add dockerfile

* z level fix

* Updated env vars

* updated populate url

* formatting

* fixed linting error

* Set defaults

* Allow only next.js dev server

* fixed url

* removed graph reassignment as due to change in master

---------

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-05 16:51:17 +02:00
SwiftyOS
9ae6389c6c fix(autogpt_server): Incorrect graph reassignment 2024-08-05 16:16:56 +02:00
Zamil Majdy
4cf1dd30f1 feat(rnd): Introduce Sub-Graph on Agent Server (#7693)
### Background

This change brings the capability to decompose a graph into sub-graphs. The objective of this feature is to allow a user to build a visually modular, and easier-to-understand graph. Also, allowing you to import a graph into your existing graph, without decluttering your existing graph.

This feature will require more implementation on the UI side, to allow the grouping of subgraph to be represented as a node in the builder.

### Changes 🏗️

Introduced a subgraph functionality with the following property:

* Sub-graph is simply a set of nodes that are grouped together, making it representable as a node.
* Sub-graph input & output pins/schema are the `InputBlock` / `OutputBlock` nodes present in the subgraph.
* The previous point implies that connecting two nodes from different sub-graphs, other than input/output nodes, is not allowed.
* Graph can be nested, but defined flatly, e.g.: graph is now only represented by three components: nodes, links, and subgraphs (a set of list of nodes). A nested subgraph is simply connecting a node inside a subgraph into another `InputBlock` node of another subgraph.
2024-08-05 16:48:14 +07:00
Swifty
c7fdfa0f77 fix(builder): Apply Prettier Formatting (#7695)
formatting
2024-08-05 09:18:08 +02:00
Andy Hooker
6fa7d22c91 feat(builder): Addition of prettier for aligned of development styles (#7629)
* fix(builder): Adding prettier configuration files and to package.

* fix(builder): Running script "format" added to the package.json

* feat(builder): Adding a job to the yaml file. This job will run "format" which leverages prettier.

* feat(builder): Running script "format" and merging master

* feat(builder): Setting configuration to prettier defaults in .prettierrc.json, and adding a requested newline in the .prettierignore

* feat(builder): Updating the CI to not add a job for prettier but instead add a check to verify prettier was run before commiting.

* feat(builder): Confirming CI update fails when user does not run prettier first. Updating with file changes after prettier

* feat(builder): Running prettier write to fix warnings

* fix(builder): Removing .prett
per PR change request

* fix(builder): Running prettier formatter

* fix(builder): Running prettier formatter check found additional files

* fix(builder): Running prettier format

* fix(builder): Removing running "format" command from PR due to a change request.

* fix(builder): Removing running "format" command from PR due to a change request.

---------

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-05 09:14:02 +02:00
Amelia Bruno
52bd033a02 tweak(setup): Improvements to setup script (#7469)
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
Co-authored-by: Toran Bruce Richards <toran.richards@gmail.com>
2024-08-05 00:13:25 -05:00
Andy Hooker
bb5baadeb2 feat(builder): Implementation of UI / UX improvement to editor (#7690)
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-05 00:09:31 -05:00
Andy Hooker
db97b24518 feat(builder): Addition of blocks control component (#7688)
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-04 23:58:12 -05:00
Andy Hooker
533d7b7da8 feat(builder): Addition of SaveControl component for ControlPanel (#7687)
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-04 23:39:42 -05:00
Andy Hooker
183c2a4845 feat(builder): Addition ControlPanel and Separator components (#7680) 2024-08-04 23:38:14 -05:00
alex
6440a8e217 fix punctuation in setup.md (#7689) 2024-08-04 06:53:32 -07:00
Toran Bruce Richards
e0930ba39d feat(Block): Add AdvancedLlmCallBlock (#7677)
* feat(Block): Add AdvancedLlmCallBlock

Adds a block for handling advanced LLM calls, enabling messages to be handled within the AutoGPT builder.

* fix linting
2024-08-02 18:17:03 -05:00
Andy Hooker
a21fd30fce fix(github): Update CODEOWNERS for rnd directories (#7684)
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-02 17:23:28 -05:00
Nicholas Tindle
e2df2cd90d Revert "feat(builder): Rewrite node input UI" (#7685) 2024-08-02 17:07:03 -05:00
Toran Bruce Richards
6bdb849150 feat(Block): Add blocks for Building Lists, Dictionaries & Strings (#7678)
* feat(Block): Add block for building lists

* feat(Block): Add block for building dictionaries

* feat(Block): Add TextCombinerBlock for combining multiple input texts

* Fix linting issues where default values are None

---------

Co-authored-by: Bently <tomnoon9@gmail.com>
2024-08-02 16:49:51 -05:00
Bently
8469fafc6f Feat(Builder): Discord bots blocks (#7670)
* Super early version of the discord bots blocks

* updated to add secrets for token + other fixes

* lint & format

* rename DiscordBot to DiscordReader

* add discord-py

* fix poetry lock

* rm duplicated file

* updated name to add Block to end

* update .env.template to add DISCORD_BOT_TOKEN

* updates to add description Field

* swap channel name and message content + add field description

---------

Co-authored-by: Bently <bently@bentlybro.com>
2024-08-02 22:21:58 +01:00
Reinier van der Leer
3c2c3e57a0 feat(builder): Rewrite node input UI (#7626)
- feat(builder): Rewrite & split up `NodeInputField`
  - Create `NodeObjectInputTree`
  - Create `NodeGenericInputField`
  - Create `NodeKeyValueInput`
  - Create `NodeArrayInput`
  - Create `NodeStringInput`
  - Create `NodeNumberInput`
  - Create `NodeBooleanInput`
  - Create `NodeFallbackInput`
  - Create `ClickableInput` from `renderClickableInput(..)`
  - Amend usage in `CustomNode`
  - Remove deprecated/unused styling from `flow.css` and `customnode.css`
  - Fix alignment between `NodeHandle` and `NodeInputField`
  - Split up `BlockIOSchema` & rename to `BlockIOSubSchema`
    - Create `BlockIOObjectSubSchema`
    - Create `BlockIOKVSubSchema`
    - Create `BlockIOArraySubSchema`
    - Create `BlockIOStringSubSchema`
    - Create `BlockIONumberSubSchema`
    - Create `BlockIOBooleanSubSchema`
    - Create `BlockIONullSubSchema`
  - Install `Select` component from shad/cn

- refactor(builder): Move `NodeInputField.tsx` to `node-input.tsx`
2024-08-02 19:28:46 +01:00
lelcruz
ec6bae0467 fix(documentation): Corrected Markdown formatting for server start command (#7649)
Co-authored-by: Leslie Cruz <lelcruz@users.noreply.github.com>
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-08-02 12:33:12 -05:00
Bently
f5fe96260e Fix(Builder): beautifyString duplicated import (#7679) 2024-08-02 15:39:42 +01:00
SwiftyOS
49a18437ac fix(builder) fix merge conflict errors 2024-08-02 16:29:55 +02:00
Reinier van der Leer
3cee893314 fix(builder): Rewrite additionalProperties input field (#7608)
Co-authored-by: Swifty <craigswift13@gmail.com>
2024-08-02 16:11:38 +02:00
Reinier van der Leer
5d1035aeb0 fix(builder): Fix BlockSchema handling & type propagation (#7604)
- Merge `Flow.tsx:CustomNodeData` type definition into `CustomNode.tsx:CustomNodeData`
- Move `lib/types:BlockSchema` type definition into `lib/autogpt-server-api/types` in place of `ObjectSchema`
- Expand and rename `BlockSchema` -> `BlockIOSchema` + `BlockIORootSchema`
- Fix all `BlockIOSchema` related type narrowing checks
- Add warning messages to fallback cases in `NodeInputField` logic

Co-authored-by: Swifty <craigswift13@gmail.com>
2024-08-02 16:09:46 +02:00
Bently
2e2c6fed52 feat(Builder): Implement undo/redo functionality (#7559)
* feat(Builder): Implement undo/redo functionality

* updates to work with latest UI

* add CTRL + Z & CTRL + Y support

* fixed undo/redo for inputs in nodes

* fix for deleting node

* fixes to make the undo/redo work better

* small fix

* Updates based on feedback

* add margin right to space out buttons

* added CTRL + SHIFT + Z for redo
2024-08-02 16:06:40 +02:00
Krzysztof Czerwinski
ca9c52f76a feat(builder): Add Supabase Auth, session and sign in UI (#7655)
This PR adds Supabase Auth (cloud) integration, login and profile UI, configures password login and three OAuth providers: Google, GitHub and Discord.

For `Account` button to show up and ability to login two env vars need to be set in `.env.local`: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`. OAuth providers are by the Supabase and don't require env vars.

Email confirmation (for email/password signup) is disabled because there's limit of 3 emails per hour without custom SMTP server configuration. [Link](https://supabase.com/dashboard/project/adfjtextkuilwuhzdjpf/auth/templates) to configure custom SMTP server and email template.

### Added dependencies:
- "@supabase/ssr": "^0.4.0"
- "@supabase/supabase-js": "^2.45.0"
- "react-icons": "^5.2.1"

### Added pages/routes:
- `app/auth/auth-code-error/page.tsx`: displayed when login using OAuth provider fails
- `app/auth/callback/route.ts`: route accessed when logging in using OAuth provider; it passes session code to Supabase
- `app/auth/confirm/route.ts`: accessed when confirming email, users will be directed here from email they get after signing in.
- `app/error/page.tsx`: Generic error page without explanation (any errors should be visible in the console)
- `app/login/page.tsx` and `app/login/actions.ts`: Login page and related login/signup server actions
- `app/profile/page.tsx`: Profile page, displays email address of the user and button to logout

### Changes
- Update `layout.tsx`: add `Log In` button and make icons consistent. The log in button shows up if user is logged out, avatar is shown when logged in, and if supabase is unavailable nothing shows up.
- Login form is verified using `zod` on the frontend (recommended by shadcn) and in case login fails feedback is displayed. On successful login users are redirected to `/profile`
- Add `PasswordInput` component, [source](https://gist.github.com/mjbalcueva/b21f39a8787e558d4c536bf68e267398)
- Add `SupabaseProvider` with context for Supabase accessed via hook `useSupabase(): { supabase: SupabaseClient | null, isLoading: boolean }`
- Add `useUser` hook to get `{ user, session, isLoading, error }` on the client 
- Add `getServerUser`: async function to get `{ user: User | null, error: string | null }` on the server side
- Add `src/middleware.ts` and `client.ts`, `server.ts`, `middleware.ts` in `src/lib/supabase` which are utility functions and middleware to refresh auth token
2024-08-02 12:58:28 +01:00
Zamil Majdy
973822d973 feat(rnd): Add InputBlock & OutputBlock (#7654)
### Background

We need an explicit block for providing input & output for the graph.
This will later allow us to build a subgraph with pre-declared input & output schema.
This will also allow us to set input for the node in the middle of the graph, and enable a graph to have output values.

### Changes 🏗️

* Add InputBlock & OutputBlock
* Add graph structure validation  on the graph execution step that asserts the following property:
    - All mandatory input pin, has to be connected or have a default value, except the `InputBlock` node.
    - All links have to connect valid nodes, and the sink & source name using the valid block field.
2024-08-02 13:33:34 +07:00
Bently
e773329391 Feat(Builder): Clear Status and Output upon graph edit (#7656)
* Feat(Builder): Clear Status and Output upon graph edit

* close output dropdown on graph edit

* make deleting edge clear block outputs

* make it so deleting nodes clears block output
2024-08-01 17:21:01 +01:00
Zamil Majdy
c9d41e69bd feat(rnd): Add postgres schema check on linting step + auto gen migration and schema sync command (poetry run schema) (#7667)
### Background

Migration not being synced has caused issues and CI is not catching this yet.

### Changes 🏗️

* Added schema check in both sqlite & postgres prisma.schema on linting step.
* Introduced `poetry run schema` for syncing prisma.schema + generating migration in both files.
2024-08-01 20:11:07 +07:00
Nicholas Tindle
08905d71f9 docs(server): add docs link to server readme (#7638)
Co-authored-by: Swifty <craigswift13@gmail.com>
2024-08-01 14:32:08 +02:00
Aarushi
8becde370c docs(ollama) Add ollama to mkdocs (#7666)
add ollama to mkdocs
2024-08-01 13:14:40 +01:00
Aarushi
dccc33152b feat(docs) Add how to run ollama with AutoGPT (#7665)
add ollama howto
2024-08-01 12:12:01 +01:00
Aarushi
b23bd9c479 feat(server): Add JWT validation (#7642)
* add auth middleware

* update readnme

* Update rnd/autogpt_server/pyproject.toml

Co-authored-by: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com>

* address newline feedback

* update poetry lock

---------

Co-authored-by: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com>
2024-08-01 11:11:40 +01:00
Aarushi
ac45b7cae9 feat(rnd,infra): Add Helm linting in CI (#7633)
* Set up helm and tf for backend

* update helm charts and settings

* remove example files

* use latest tag

* delay and timeouts for probes

* env based pyro host

* default backend

* linting

* add helm linting in CI

* read from settings

* fix formatting

* update to use config
2024-08-01 09:22:04 +01:00
Aarushi
3d54a9103c feat(rnd, infra): Add deployment for backend (#7612)
* Set up helm and tf for backend

* update helm charts and settings

* remove example files

* use latest tag

* delay and timeouts for probes

* env based pyro host

* default backend

* linting

* read from settings

* fix formatting

* update to use config
2024-08-01 09:21:12 +01:00
Nicholas Tindle
ca7182403b fix(server): enable other methods of setting configs outside of the config.json file (#7645) 2024-07-31 12:33:36 -05:00
Zamil Majdy
53826ab360 fix(rnd): Fix bug caused by different AJV version (#7653) 2024-07-31 16:47:31 +07:00
Andy Hooker
eac5548023 feat(builder): UI / UX upgrade and simplification of tailwind styles (#7647)
* fix(builder): Implementing a basic shadCn theme until color palette is decided upon

* feat(builder): Separating NavBar into its own component and providing a standard UI/UX Approach

* feat(builder): Removing old implementation of logo, removing excessive css implementation, updating styles to better support standard desktop views.

* feature(builder): Addition of UI component Sheet from ShadCn for update
2024-07-31 10:43:13 +02:00
Zamil Majdy
122f544966 fix(rnd): Disable unused prisma connection on pyro API Server process (#7641)
### Background

Pyro for API Server is not using Prisma, but still holding a Prisma connection.
The fast-API thread is also holding a Prisma connection, making Prisma connected in two different loop within a single process.

### Changes 🏗️

Disable a Prisma connection on Pyro thread for Server API process.
Fix test flakiness issue due to concurrency issue.
2024-07-30 22:33:28 +07:00
Zamil Majdy
29ba4c2c73 feat(rnd): Add freezed/static input link feature for retaining input data to be re-used in multiple node executions (#7631)
### Background

Input from the input pin is consumed only once. While this is required in most of the use cases, there are some cases where the input can only be produced once, and that input needs to be re-used just like an input default value, that is passively providing input data, without triggering any execution. The scope of this change is providing that functionality in the link level, this property will be called **`static link`** in this system.

### Changes 🏗️

Provides a static link feature with the following behaviours:
* A link can be marked `static` to become a static link.
* Once a node produces an output it will persist the output data and propagate the output to the other nodes through the link, for a static link, instead of making the data queued in the input pin, it will override the default value.
* Any input executions still waiting for the input will be backfilled using this output produced by the static link.
* And any upcoming executions that will use the input will always reuse the output produced by the static link.

See the added test to see the expected usage.
2024-07-30 10:37:38 +07:00
Nicholas Tindle
76feead3b1 docs(server): written setup guide for server (#7595)
Co-authored-by: Swifty <craigswift13@gmail.com>
2024-07-29 22:24:03 +01:00
Bently
081df805df Fix(Builder): Copy and Paste when editing textbox causes issue (#7636)
Fix(Builder): Copy and Paste when editing textbox causes graph elements to be erroneously pasted
2024-07-29 21:55:19 +01:00
Nicholas Tindle
acc1d79146 feat(builder): add delete handles to the edges and nodes (#7614)
Co-authored-by: Andy Hooker <58448663+andrewhooker2@users.noreply.github.com>
Co-authored-by: Toran Bruce Richards <toran.richards@gmail.com>
2024-07-29 21:30:56 +01:00
Nicholas Tindle
07811b2133 fix(builder): show colors for all statuses (#7625) 2024-07-29 20:04:48 +01:00
Amr Aly
01b6c2d4bf Update README.advanced.md (#7607)
* Update README.advanced.md

The copy .env command is wrong. it should be .env.example instead of env.example

* fix(server): extra space removal

---------

Co-authored-by: Nicholas Tindle <nicktindle@outlook.com>
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
Co-authored-by: Aarushi <50577581+aarushik93@users.noreply.github.com>
2024-07-29 12:00:58 +01:00
Krzysztof Czerwinski
905b1df218 feat(builder): Add input validation on the frontend (#7606)
- Add `ajv` dependency to check values against json schema
- Add `errors` and `setErrors` to `CustomNodeData`
- Add `validateNodes` run before executing agent
- Add `*` on labels for required fields
- Add `setNestedProperty` and `removeEmptyStringsAndNulls` utility function
2024-07-29 10:17:27 +01:00
Andy Hooker
edf84fb9f8 feat(builder): Apply standard theming for shadCn and centralize providers (#7622) 2024-07-26 17:59:44 -05:00
Swifty
b62c24dc77 Add Marketplace template (#7605)
* init

* Added sentry.io integration

* updated readme
2024-07-26 17:11:26 +02:00
Zamil Majdy
dfa855f533 fix(rnd): Fix broken server execution update due to split prisma connection to different loops (#7620) 2024-07-26 15:47:41 +01:00
Aarushi
da2111bafb fix(server): Add missing PG migrations (#7619)
missing migrations
2024-07-26 14:38:05 +01:00
Reinier van der Leer
b2dba39810 fix(builder/api-client): Fix websocket client types (#7603)
- Fix type signatures of `sendWebSocketMessage(..)`, `onWebSocketMessage(..)`, `runGraph(..)` in `autogpt-server-api/client`
  - Add `WebsocketMessageTypeMap`

- Fix type signature of `updateNodesWithExecutionData` in `FlowEditor`
2024-07-26 13:26:11 +02:00
Zamil Majdy
d2a5bb286f feat(rnd): Fix concurrency issue on Agent Server & Apply max-1-execution-per-node constraint in graph execution (#7551)
### Background

When multiple executors are executing the same node within the same graph execution, two node executions can read the same queue of input and read the same value—making the data that is supposed to be consumed once, consumed by two executions. The lack of lock & concurrency support for parallel execution within a single graph causes this issue.

Node concurrency also introduces poor UX in the current frontend implementation, when two nodes are executed in parallel, the current UI will not display its parallel execution update, but instead, it shows the updates that override each other. Until the execution observability is improved on the builder UI, this capability will be limited.

### Changes 🏗️

The scope of this change is to solve this issue by:
* Decouple Graph execution & Node execution, each has its own configured process pool.
* Make sure there is only 1 execution per node (we still allow parallel executions on different nodes) in a graph.
* Fixed concurrency issue by adding distributed lock API on agent_server.
* Few cleanups:
    - Add more logging with geid & neid prefix on graph/node executions
    - Moved execution status update to agent-server for a single source of status update (required by conn-manager/web-socket)
    - Configured node parallelism to 10 & graph parallelism to 10 by default, so in the very rare worst-case, there can be 100 node executions.
    - Re-use server resource for each integration test run
2024-07-26 17:08:03 +07:00
Toran Bruce Richards
36b9a0a930 feat(Blocks): Add conditional logic block (#7536)
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-07-25 14:25:07 -05:00
Aarushi
f40db85b43 feat(rnd, infra) Add static ips module (#7588)
* add terraform

* gitignore update

* linting

* formatting and linting in ci

* store state in backend bucket

* Update .github/workflows/autogpt-infra-ci.yml

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>

* add static ips module

* formatting

---------

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
Co-authored-by: Swifty <craigswift13@gmail.com>
2024-07-25 17:50:19 +01:00
Reinier van der Leer
0767b17779 dx: Set Python type checking mode to "basic" for all subprojects (#7599) 2024-07-25 12:25:56 +01:00
Nicholas Tindle
0f0c13bae8 fix(server): gitignore was missing new file name (#7596)
Co-authored-by: Aarushi <50577581+aarushik93@users.noreply.github.com>
2024-07-25 10:07:16 +01:00
A.Daee
3b0cd9518d feat(froge): Improvement in web components (#7068)
- Add `duckduckgo_backend` field to `WebSearchComponent` configuration
- Add `selenium_proxy` to `WebSeleniumComponent` configuration
- Update docs
2024-07-25 10:02:34 +01:00
Aarushi
22b6dbbf6a feat(rnd,infra) Set up terraform (#7565)
* add terraform

* gitignore update

* linting

* formatting and linting in ci

* store state in backend bucket
2024-07-25 09:45:36 +01:00
Reinier van der Leer
d9a1a1edc8 fix(builder): Correctly load node connections (#7592)
- Set `node.data.connections` based on `graph.links` in `loadGraph(..)`
- Use reactflow's built-in `useNodesState`, `useEdgesState` to replace some boilerplate functions that do exactly the same

Resolves #7392
2024-07-25 09:30:35 +01:00
vasnt
3c0d37d5d1 docs(forge):fix #7198 dead image links (#7593)
Co-authored-by: Nicholas Tindle <nicktindle@outlook.com>
2024-07-24 22:55:07 -05:00
Bently
c98061bc3b fix(Builder): fixes to copy and pasted blocks (#7591)
* fix(Builder): fixes to copy and pasted blocks

* clear the blocks state on paste
2024-07-24 17:00:59 +01:00
Toran Bruce Richards
a8c0cbef54 refactor(Blocks): Simplify MathsBlock and CounterBlock to Return Numeric Results Only (#7582)
* refactor(MathsBlock): Simplify output to return numeric result directly

- Remove MathsResult class and explanation field
- Update Output schema to use float type
- Simplify run method to yield numeric result only
- Adjust error handling to return inf or nan for errors
- Update test cases to reflect new output structure

* run format

* refactor(CounterBlock): Simplify output to return count as integer

- Remove CounterResult class
- Update Output schema to use int type directly
- Simplify run method to yield count without explanation
- Modify error handling to return -1 for any errors
- Update test case to reflect new output structure
2024-07-24 08:55:18 -05:00
Zamil Majdy
8ccd14c4bf fix(rnd): Disable BlockInstallationBlock and add disable field for block (#7583) 2024-07-24 08:52:14 -05:00
Nicholas Tindle
3e384c9771 ci(server): add sqlite processing (#7586)
* ci(server): add sqlite processing

* ci(server): try setting DATABASE_URL based on db platform

* fix(server): swap default back to sqlite

* ci(server): go back to database url

---------

Co-authored-by: Aarushi <50577581+aarushik93@users.noreply.github.com>
2024-07-24 14:46:39 +01:00
Aarushi
22f2a05f08 feat(server) Fix schema file (#7589)
* fix schema file

* remove invalide migrations

* make sqlite url hardcode
2024-07-24 14:31:48 +01:00
Krzysztof Czerwinski
e94a7b08c9 refactor(builder): Move renderInputField to NodeInputField component (#7581) 2024-07-24 11:14:20 +01:00
Aarushi
7b8928f49b feat(server) Update readme (#7580)
update readme
2024-07-24 16:53:09 +07:00
Aarushi
699087e289 feat(rnd) Add dockerfiles (#7523)
* replace SQLite with Postgres

* dockerfiles and optional docker compose set up

* Update rnd/autogpt_builder/Dockerfile

Co-authored-by: Reinier van der Leer <pwuts@agpt.co>

* address feedback

* Update .dockerignore

Co-authored-by: Reinier van der Leer <pwuts@agpt.co>

* Remove example files folder

* remove backend and frontend from docker compose

---------

Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
2024-07-24 10:01:22 +01:00
Bently
aa8ca37f86 feat(Builder): Work on copy and paste of blocks (#7564)
Co-authored-by: Swifty <craigswift13@gmail.com>
2024-07-24 09:30:48 +01:00
Toran Bruce Richards
8bdb48cba4 feat(blocks): Add basic YouTube transcriber block (#7495)
* feat: Add YouTubeTranscriber block for extracting transcripts from YouTube videos

* feat: Add youtube-transcript-api dependency to pyproject.toml

* feat: Add SchemaField and test_mock to YoutTube Transcriber.

* feat: update lock

* fix(server): the agbenchmark was out of date

* fix(server): linting

* fix(server): drop mock

* fix(server): poetry locked in

* fix(server): test had ... at the end?

---------

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-07-24 09:00:09 +01:00
Bently
03ea51b266 feat(Builder): Limit Output preview size (#7554)
* feat(Builder): Limit Output preview size

* fix indent
2024-07-24 09:23:43 +02:00
Aarushi
77034f2df0 feat(blocks): Add 405b on ollama (#7573)
405b on ollama
2024-07-24 09:22:18 +02:00
Nicholas Tindle
ccf4397883 feat(server): author and contributor information on blocks (#7575) 2024-07-24 09:19:33 +02:00
Reinier van der Leer
e7885f943b fix(agent): Unbreak LLM status check on start-up
Fixes #7508

- Amend `app/configurator.py:check_model(..)` to check multiple models at once and save duplicate API calls
- Amend `MultiProvider.get_available_providers()` to verify availability by fetching models and handle failure
2024-07-23 22:36:04 +02:00
Aarushi
aca7165694 feat(rnd) Fix linting and formatting (#7568)
fix linting and formatting
2024-07-23 18:25:04 +01:00
Toran Bruce Richards
22b223037e feat(Blocks): Add support for new Llama 3.1 models with Groq! (#7567)
feat: Add support for new Groq models

The commit adds support for new Groq models, including LLAMA3_1_405B, LLAMA3_1_70B, and LLAMA3_1_8B. These models are part of the preview release and offer enhanced reasoning and versatility capabilities.
2024-07-23 16:48:39 +01:00
Toran Bruce Richards
6747ae1559 feat(Blocks): Add Blocks Providing Basic Arithmetic Operations (#7529)
* feat(blocks): Add MathsBlock for performing mathematical operations

The commit adds a new block called MathsBlock to perform various mathematical operations such as addition, subtraction, multiplication, division, and exponentiation. The block takes input parameters for the operation type, two numbers, and an option to round the result. It returns the result of the calculation along with an explanation of the performed operation.

---------

Co-authored-by: Swifty <craigswift13@gmail.com>
2024-07-23 16:28:59 +02:00
Aarushi
39afba6da8 feat(ci) Fix migrations in CI (#7563)
* update CI

* add poetry run

* schema prisma
2024-07-23 14:01:45 +01:00
Toran Bruce Richards
a00df25092 feat(Blocks): Add RSS Reader Block for reading RSS feeds (#7533)
* feat: Add RSSReaderBlock for reading RSS feeds

The commit adds a new `RSSReaderBlock` class in the `rss-reader-block.py` file. This block allows users to read RSS feeds by providing the URL of the feed, start datetime, polling rate, and a flag to run the block continuously. The block fetches the feed using the `feedparser` library and returns the title, link, description, publication date, author, and categories of each RSS item.

This commit also includes the addition of the `feedparser` dependency in the `pyproject.toml` file.

* fix(server): update lock file

* updated poetry lock

* fixed rss reader testing

* Updated error message in test to include check info

* Set starttime as 1 day ago

* Changed start time to time period

---------

Co-authored-by: Swifty <craigswift13@gmail.com>
2024-07-23 13:15:14 +02:00
Toran Bruce Richards
ea698ab0fe feat(Blocks): Add For-Each Block for iterating over a List. (#7531)
* feat: Add ForEachBlock for iterating over a List.

---------

Co-authored-by: Swifty <craigswift13@gmail.com>
2024-07-23 11:40:41 +02:00
Krzysztof Czerwinski
902d2a8924 feat(builder): UX and style updates (#7550)
- Handles:
  - Add `NodeHandle` to draw input and output handles
  - Position handles relatively
  - Make entire handle label clickable/connectable
  - Add input/output types below labels
  - Change color on hover and when connected
  - "Connected" no longer shows up when connected
- Edges:
  - Draw edge above node when connecting to the same node
  - Add custom `ConnectionLine`; drawn when making a connection
  - Add `CustomEdge`; drawn for existing connections
  - Add arrow to the edge end
  - Colorize depending on type
- Input field modal:
  - Select all text when opened
  - Disable node dragging
- CSS:
  - Remove not needed styling
  - Use tailwind classes instead of css for some components
  - Minor style changes
- Add shadcn switch
- Change bottom node buttons (for properties and advanced) to switches
- Format code
2024-07-23 09:36:42 +01:00
Reinier van der Leer
ab0df04bfe fix(builder/monitor): Fix Graph export (#7556)
- fix(builder/monitor): Export `Graph` rather than `GraphMeta`
  - Fixes #7557

- refactor(builder): Split up `lib/autogpt_server_api` into multi-file module
  - Resolves #7555
  - Rename `lib/autogpt_server_api` to `lib/autogpt-server-api`
  - Split up `lib/autogpt-server-api` into `/client`, `/types`
  - Move `ObjectSchema` from `lib/types` to `lib/autogpt-server-api/types`
  - Make definition of `Node['metadata']['position']` independent of `reactflow.XYPosition`

- fix(builder/monitor): Strip secrets from graph on export
  - Resolves #7492
  - Add `safeCopyGraph` function in `lib/autogpt-server-api/utils`
  - Use `safeCopyGraph` to strip secrets from graph on export in `/monitor` > `FlowInfo`
2024-07-23 09:28:06 +02:00
Zamil Majdy
d407fd101e fix(rnd): Make Agent Server's pin connections become the mandatory source of input (#7539)
### Background

Input from the input pin is consumed only once, and the default input can always be used. So when you have an input pin overriding the default input, the value will be used only once and the following run will always fall back to the default input. This can mislead the user.

Expected behaviour: the node should NOT RUN, making connected pins only use their connection(s) for sources of data.

### Changes 🏗️

* Make pin connection the mandatory source of input and not falling back to default value.
* Fix the type flakiness on block input & output. Unify the typing for BlockInput & BlockOutput using the right alias to avoid wrong typing.
* Add comment on alias
* automated test on the new behaviour.
2024-07-23 09:06:26 +07:00
Aarushi
a911f9a5eb feat(rnd) Add Postgres support (#7513)
* replace SQLite with Postgres

* make sqlite default

* add migrations for sqlite and postgres

* update readme

* fix formatting
2024-07-22 23:43:49 +01:00
Reinier van der Leer
470c738732 fix(builder/monitor): Fix # of runs count on first load (#7558)
Return mutated copy rather than in-place mutated `flowRuns` in `refreshFlowRuns(..)`

Fixes #7507
2024-07-22 23:15:54 +01:00
Nicholas Tindle
7de49dfbe5 Update workflow-checker.yml (#7553) 2024-07-22 13:49:27 -05:00
Reinier van der Leer
a02b017cea feat(builder): Built-in templates (#7511)
- Add "Medium Blogger" template to `graph_templates` folder
- Add `import_packaged_templates()` function in `data.graph`
- Call `import_packaged_templates()` in `AgentServer` lifecycle setup
- Fix `util.json:loads` typing and parameter forwarding
2024-07-22 17:54:48 +02:00
Nicholas Tindle
56b82369b6 ci(all): verify the workflows as part of each run (#7500) 2024-07-22 07:23:48 -05:00
Tyrion Lannister
a7926584ca Fix(rnd): Add Prerequisites Documentation in README.md (Server) (#7546) 2024-07-22 07:19:20 -05:00
Nicholas Tindle
6ffa644fb6 fix(ops/issues): update template for bugs (#7211) 2024-07-22 07:16:51 -05:00
Bently
a82317e2ac feat(builder): fix sidebar scroll issue (#7548)
fix sidebar scroll issue
2024-07-22 13:05:50 +01:00
Bently
fd000a4173 feat(Builder): Fix sidebar covering navbar (#7544)
Fix sidebar covering flow
2024-07-22 10:11:46 +01:00
Nicholas Tindle
235715e054 feat(server): read_csv block (#7499) 2024-07-22 09:58:02 +02:00
Nicholas Tindle
d0ec31b698 docs: fix dead links (#7470) 2024-07-22 09:56:32 +02:00
Zamil Majdy
fa1b486c64 feat(rnd): Add Agent block UUID validation for Agent Server (#7501)
Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-07-20 09:14:18 +07:00
Reinier van der Leer
21084c5817 feat(builder, server): Speed up fetching graphs (#7477)
- Let `GET /graphs` return `GraphMeta[]` instead of `string[]` (list of IDs)
- Rename `AutoGPTServerAPI` method `listGraphIDs` -> `listGraphs` and adjust return type
   - Replace all usages of `Graph` with `GraphMeta` in `/monitor`
- Delete `data.graph:get_graph_ids()`
2024-07-19 18:28:59 -05:00
Zamil Majdy
82fd3166ef feat(rnd): Add block description and categories (#7463)
### Background

Add block description and categories metadata.

### Changes 🏗️

* Add block description and categories metadata.
* Initialize description and categories on the existing blocks.
2024-07-19 11:10:16 -05:00
Toran Bruce Richards
f833fa3624 feat: Update CreateMediumPostBlock to use secret value for author_id
This commit updates the `CreateMediumPostBlock` class in `create_medium_post.py` to use the secret value for the `author_id` parameter. Previously, it was using the plain value, which caused the value to be sent incorrectly to the API.
2024-07-19 14:52:02 +01:00
Aarushi
e6f9870f2e feat(blocks) Add ollama integration (#7505)
* add ollama integration

* address review comments & formatting
2024-07-19 11:27:48 +01:00
Bently
6e319a6881 Feat(Builder): Decrease the delay before a tooltip opens (#7478)
Make tooltip show in 10 ms
2024-07-19 09:21:34 +01:00
Nicholas Tindle
64edf12c31 fix(server): code.py as a filename overshadows an internal python file (#7496) 2024-07-19 10:30:54 +07:00
Nicholas Tindle
e1795b8216 fix(server): add create medium post test missing params (#7497) 2024-07-19 10:28:43 +07:00
Nicholas Tindle
057d0848ef fix(server): various linting errors (#7498) 2024-07-19 10:28:21 +07:00
Bently
2dc673614f feat(doc): Update node creation docs (#7462)
* feat(doc): Update node creation docs

* small fixes

* fix formatting

* remove mention of registering a block

* Update docs/content/server/new_blocks.md

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>

---------

Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
2024-07-18 20:01:40 -05:00
Toran Bruce Richards
24e08d57ef Update README.md 2024-07-19 00:46:17 +01:00
Toran Bruce Richards
bd540b5cc4 tweak(builder): Replace erroneous mentions of "Properties" to "Output" (#7489) 2024-07-19 00:42:22 +01:00
Toran Bruce Richards
6d192429a6 feat(blocks): Add Medium Post block (#7494)
* feat: Add CreateMediumPostBlock to create Medium posts

* feat: Add medium_api_key to Secrets class in settings.py

* feat: Update medium post block to work with latest system.

* feat: Add medium_author_id field to Secrets class in settings.py

* run isort

* run black
2024-07-19 00:26:32 +01:00
Toran Bruce Richards
314a24ab8f fix(blocks): resolve duplicate ID in ObjectLookupBlock - basic.py 2024-07-18 23:58:19 +01:00
Toran Bruce Richards
ff962d8d88 Merge branch 'master' of https://github.com/Significant-Gravitas/AutoGPT 2024-07-18 23:56:09 +01:00
Toran Bruce Richards
5e5182e236 feat: Add CreateMediumPostBlock to create Medium posts 2024-07-18 22:54:13 +01:00
Nicholas Tindle
04dcd230cd feat(builder): allow deletion of nodes and edges via del key (#7483) 2024-07-18 22:45:28 +01:00
Nicholas Tindle
c8b46109fe feat(builder, server): hide secrets from the UI and mark them as secrets in the api (#7490)
* feat(server): add json_extra marking some fields as secret

* devx(builder): add a launch.json for debugging

* feat(builder): hide secret strings

---------

Co-authored-by: Toran Bruce Richards <toran.richards@gmail.com>
2024-07-18 16:30:11 -05:00
Toran Bruce Richards
c00caa4bcf tweak(Builder): Remove unnecessary node output heading in CustomNode component (#7487)
feat: Remove unnecessary node output heading in CustomNode component
2024-07-18 21:43:47 +01:00
Bently
e382dcf823 feat(blocks): Adds node to get weather from openweathermap (#7382)
* Add node for openweathermap

* fix naming

* Rename GetOpenweatherMapWeather.py to getopenweathermapweather.py

* fix naming

* fix conflicts

* fix conflicts

* Updates to node + moved to search.py

* small cleanup
2024-07-18 21:36:09 +01:00
Toran Bruce Richards
27e6c3a95d feat(block): Add support for GPT-4O Mini model in LLM block (#7480)
feat: Add support for GPT-4O Mini model in LLM block
2024-07-18 19:02:36 +01:00
Bently
aaf650ee23 feat(builder): Fix message covering navbar on < lg screen size (#7474)
* Remove excess styling from message box
* Replace "node" with "block" in message (-> "Get started by adding a block")
2024-07-18 16:46:28 +02:00
Reinier van der Leer
4c003d6e20 fix(builder): Make detail view on /monitor obey grid
Unwrap the detail view element and style it directly instead
2024-07-18 04:12:51 +02:00
Reinier van der Leer
8264d7bf5a feat(builder): Importing/exporting Agents and Templates (#7466)
Builder:
* Add download button to agent info view
   - Add download button to `FlowInfo`
   - Add `exportAsJSONFile(..)` function to lib/utils.ts

* Add Create button + menu to /monitor page
   - Add "Create | v" split button to Agent list
   - Add list of templates to Create menu
   - Add "Import from file" option + dialog
      - Create `AgentImportForm` component
         - Install `Form`, `Label`, `Switch` components from shad/cn UI
      - Install `Dialog` component from shad/cn

* Support saving/editing Templates
   - Add `templateID` query parameter to `/build`
   - Use `templateID` query parameter in `AgentImportForm` redirect if imported as template
   - Make `FlowEditor` suitable for saving/editing templates
      - Add `template` (boolean) parameter to `FlowEditor` component
      - Add conditions to all `createGraph` or `updateGraph` calls, to use `createTemplate`/`updateTemplate` if applicable
      - Add "Save as Template" button, visible if the graph is new (unsaved)
      - Hide "Save & Run Agent" button when editing a template

* Add template endpoints to `AutoGPTServerAPI` client
   - Add `listTemplates()`
   - Add `getTemplate(id, version?)`
   - Add `getTemplateAllVersions(id)`
   - Add `createTemplate(templateCreateBody)`
   - Add `updateTemplate(id, template)`

* fix inner alignment of `<Input type="file">` elements

Server:
* fix(server): Fix return of `create_graph` for templates
2024-07-18 13:19:56 +01:00
Reinier van der Leer
37b7053e14 feat(server): Add API prefix /api to prevent route collisions with frontend (#7472)
- Add prefix `/api` to `APIRouter` in server.py
- Update API client in Builder
   - Update default `AGPT_SERVER_URL` in .env.template
   - Update default `baseUrl` in `AutoGPTServerAPI` constructor
2024-07-18 07:16:58 -05:00
Reinier van der Leer
354e626965 feat(dx): Add Builder and Server to auto-labeler 2024-07-18 00:19:38 +02:00
Reinier van der Leer
90371e1781 fix(builder): Remove legacy global styling from globals.css
This global styling was clashing with the theme of shad/cn and making text unreadable.
2024-07-18 00:06:52 +02:00
Reinier van der Leer
e128bfaf5f refactor(builder): Clean up AutoGPTServerAPI implementation (#7468)
- Abstract request implementation out of individual endpoints
- Rename Flow -> Graph (in type, method, and variable names)
2024-07-17 21:51:03 +02:00
Kate Silverstein
62c420e26f feat(forge/llm): Add LlamafileProvider (#7091)
* Add minimal implementation of `LlamafileProvider`, a new `ChatModelProvider` for llamafiles. It extends `BaseOpenAIProvider` and only overrides methods that are necessary to get the system to work at a basic level.

* Add support for `mistral-7b-instruct-v0.2`. This is the only model currently supported by `LlamafileProvider` because this is the only model I tested anything with.

* Add instructions to use AutoGPT with llamafile in the docs at `autogpt/setup/index.md`
* Add helper script to get it running quickly at `scripts/llamafile/serve.py`

---------

Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
2024-07-17 15:56:55 +02:00
Krzysztof Czerwinski
97a5582c34 feat(agent_builder): Human-readable titles for blocks and fields (#7439)
Display human readable names for blocks and fields. `title` from schema is used if available, otherwise `beautifyString(name)`.
2024-07-17 14:44:41 +01:00
Zamil Majdy
78b84289cb feat(rnd): Add code-formatter & linting on AutoGPT server (#7458)
### Background

Add formatter & linter command.
Tools: ruff --> isort --> black --> pyright.

### Changes 🏗️

Introduced:
* `poetry run format`
* `poetry run lint`

`poetry run lint` will be executed on CI.
2024-07-17 11:54:29 +01:00
Krzysztof Czerwinski
9e22409d66 feat(agent_builder): Add block input fields description tooltip (#7437)
- Renamed `Schema` to `BlockSchema` and moved to `lib/types.ts`
- Add `SchemaTooltip` component that renders markdown tooltip for node fields
- Add `SecretField` function (which uses `BlockSecret` as value) that replaces `BlockFieldSecret` functionality for models
- Rename `get` to `get_secret_value` to make name clearer and inline with pydantic `Secret` types
- Add shadcn tooltip
- Add `react-markdown` dependency
2024-07-17 10:54:43 +01:00
Zamil Majdy
e7c075a521 feat(doc): Remove excessive docs for AutoGPT server (#7457) 2024-07-17 14:22:13 +07:00
Krzysztof Czerwinski
555e113706 feat(agent_builder, agent_server): Add customizable placeholders for input fields (#7451)
- Add `SchemaField` that works like Pydantic `Field` but allows to add extra json schema values. This PR adds `placeholder` entry but it could be extended with other data.
- Render `placeholder` inside input fields if available.
- Restyle placeholders so they are visually distinct from user-entered values
2024-07-16 20:08:43 +01:00
Bently
420e6cae2f feat(autogpt_builder): Fix Read API base URL from .env (#7455) 2024-07-16 16:28:24 +01:00
Toran Bruce Richards
920f931a21 feat(blocks): Add summariser block for recursive text summarization functionality (#7431)
Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
2024-07-16 17:51:37 +07:00
Toran Bruce Richards
e874318832 feat(blocks): Add WebSearch & WebScrapper block for searching the web with Jina Reader (#7445)
Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
2024-07-16 15:32:23 +07:00
Toran Bruce Richards
cb4b96a70c feat(server): Add DictionaryLookup block for looking up values in a dictionary (#7427)
Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
2024-07-16 15:23:12 +07:00
Toran Bruce Richards
03ea4c2690 fix(server): Fix bug in time comparison logic in RedditGetPostsBlock (#7426)
Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
2024-07-16 14:35:51 +07:00
Zamil Majdy
e70e613f73 feat(rnd): Refactor Block testing infra and Add Block auto-generation graph usecase (#7405)
### Background

The main scope of this change is enhancing the system capability (by fixing bug, correcting execution behaviour) to allow for creating a graph with a loop, to allow the use case of block auto-generation agent.

### Changes 🏗️

* Main changes: Add block_autogen.py (block auto-generation agent graph example).
* Refactor on test boilerplate: introduced `util/test` for initiating a server, and waiting graph execution.
* Removing unnecessary db lookup and duplicated codes used for sending execution updates on agent executor.
* Removed redundant code on test and cli code.
* Moved block test execution helper into the main code (so blockinstallerblock can use it).
* Eliminate the need of explicitly add a module into the `AVAILABLE_BLOCKS` list, any block class under the `block` folder will be auto-discovered.
* Few patches on the existing blocks.
2024-07-15 21:41:18 -05:00
Toran Bruce Richards
854f6dcaec Update README.md 2024-07-15 22:31:30 +01:00
Toran Bruce Richards
ea5ba9d193 Update README.md 2024-07-15 22:31:19 +01:00
Toran Bruce Richards
da14957fce Update README.md 2024-07-15 22:29:03 +01:00
Toran Bruce Richards
629f575dde Delete rnd/AutoGPT Server Setup Tutorial.mp4 (#7452) 2024-07-15 22:18:49 +01:00
Toran Bruce Richards
8883d7db53 Create README.md in rnd folder 2024-07-15 22:17:37 +01:00
Toran Bruce Richards
a6063e1550 Add files via upload 2024-07-15 22:06:57 +01:00
Toran Bruce Richards
e311847fa8 Update autogpt_server README.md 2024-07-15 20:26:44 +01:00
Bently
d673bf741a feat(autogpt_builder): Add websocket support to replace polling (#7449)
feat(autogpt_builder): Add websocket support
2024-07-15 20:18:23 +01:00
Swifty
110e093e7b feat(autogpt_server, autogpt_builder): Add Agent Versioning & Templates functionality (#7376)
1. Add graph versioning functionality:
   - Add `version`, `isActive` fields in the `AgentGraph` model
      - Add `agentGraphVersion` field in related models
   - Amend & add API endpoints for graph versioning (see below)
      - Amend & add data layer functions (`autogpt_server.data`) to support new operations & data schema

2. Add graph template functionality:
   - Add `isTemplate` fields in the `AgentGraph` model
   - Add `GraphMeta` model for template/graph metadata
   - Add API endpoints for template management (see below)
      - Amend & add data layer functions (`autogpt_server.data`) to support new operations & data schema

3. Enhance graph creation:
   - Amended `create_graph` route to handle template-based graph creation

4. Code refactoring:
   - Improved import statements
   - Enhanced error handling in graph creation

5. Minor improvements:
   - Add validator to auto-assign `Graph.id` if not set

## API Changes

New endpoints:
- `GET /templates`: Retrieve all templates (metadata only)
- `POST /templates`: Create a new template
- `PUT /graphs/{graph_id}`: Create a new version of a graph
- `GET /templates/{graph_id}`: Get a specific template
- `PUT /templates/{graph_id}`: Create a new version of a graph template
- `GET /graphs/{graph_id}/versions`: Get all versions of a graph
- `GET /templates/{graph_id}/versions`: Get all versions of a graph template
- `GET /graphs/{graph_id}/versions/{version}`: Get a specific graph version
- `PUT /graphs/{graph_id}/versions/active`: Set active graph version

Modified endpoints:
- `POST /graphs`: Now supports creating graphs directly from templates
- `GET /graphs/{graph_id}`: Add `version` query parameter
- `GET /graphs/{graph_id}/executions`: Add `graph_version` query parameters

## UI changes
- Improve `/build` / `FlowEditor` save mechanism
   - Implement updating current agent instead of creating a new agent on every save
   - Add check to only save a new version if local graph has been edited
      - Add `deepEquals` function to lib/utils.ts
- Add version indicators and selector on `/monitor`
  ![image](https://github.com/user-attachments/assets/742a66ee-4d12-4129-981d-fa6399e717fa)
   - Add shad/cn `DropdownMenu` component
- Update `AutoGPTServerAPI` client
   - Update input/output types with added attributes (see above)
   - Add parameter `version` to `getFlow`
   - Add parameter `flowVersion?` to `listFlowRunIDs`
   - Add endpoint `updateFlow(flowID, FlowUpdateable)`
   - Add endpoint `createFlow(fromTemplateID, templateVersion)` (overload)
   - Add endpoint `getFlowAllVersions(id)`
   - Add endpoint `setFlowActiveVersion(flowID, version)`
2024-07-15 20:26:00 +02:00
Toran Bruce Richards
93b6e0ee51 feat(blocks): Add support for additional LLM providers to LLM Block (#7434)
This commit adds support for the following models:

```python
# OpenAI Models
GPT4O = "gpt-4o"
GPT4_TURBO = "gpt-4-turbo"
GPT3_5_TURBO = "gpt-3.5-turbo"

# Anthropic models
CLAUDE_3_5_SONNET = "claude-3-5-sonnet-20240620"
CLAUDE_3_HAIKU = "claude-3-haiku-20240307"

# Groq models
LLAMA3_8B = "llama3-8b-8192"
LLAMA3_70B = "llama3-70b-8192"
MIXTRAL_8X7B = "mixtral-8x7b-32768"
GEMMA_7B = "gemma-7b-it"
GEMMA2_9B = "gemma2-9b-it"
```

Every model has been tested with a single LLM block and is confirmed to be working in that setup.
2024-07-15 20:30:56 +07:00
Bently
450f120510 feat(autogpt_builder): Fixes to wikipedia block (#7435) 2024-07-15 11:10:16 +07:00
425 changed files with 38703 additions and 9602 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -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.

8
.github/labeler.yml vendored
View File

@@ -17,3 +17,11 @@ Frontend:
documentation:
- changed-files:
- any-glob-to-any-file: docs/**
Builder:
- changed-files:
- any-glob-to-any-file: rnd/autogpt_builder/**
Server:
- changed-files:
- any-glob-to-any-file: rnd/autogpt_server/**

View File

@@ -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
View 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 }}

View File

@@ -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,164 +119,37 @@ jobs:
- name: Run Database Migrations
run: poetry run prisma migrate dev --name updates
env:
CONNECTION_STR: ${{ steps.postgres.outputs.connection-uri }}
- 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
View 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 }}

View File

@@ -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",

View File

@@ -1,17 +1,43 @@
# AutoGPT: build & use AI agents
# AutoGPT: Build & Use AI Agents
[![Discord Follow](https://dcbadge.vercel.app/api/server/autogpt?style=flat)](https://discord.gg/autogpt) &ensp;
[![Twitter Follow](https://img.shields.io/twitter/follow/Auto_GPT?style=social)](https://twitter.com/Auto_GPT) &ensp;
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
**AutoGPT** is a generalist LLM based AI agent that can autonomously accomplish minor tasks.
**AutoGPT** is a powerful tool that lets you create and run intelligent agents. These agents can perform various tasks automatically, making your life easier.
**Examples**:
## How to Get Started
- Look up and summarize this research paper
- Write a marketing for food supplements
- Write a blog post detailing the news in AI
https://github.com/user-attachments/assets/8508f4dc-b362-4cab-900f-644964a96cdf
### 🧱 AutoGPT Builder
The AutoGPT Builder is the frontend. It allows you to design agents using an easy flowchart style. You build your agent by connecting blocks, where each block performs a single action. It's simple and intuitive!
[Read this guide](https://docs.agpt.co/server/new_blocks/) to learn how to build your own custom blocks.
### 💽 AutoGPT Server
The AutoGPT Server is the backend. This is where your agents run. Once deployed, agents can be triggered by external sources and can operate continuously.
### 🐙 Example Agents
Here are two examples of what you can do with AutoGPT:
1. **Reddit Marketing Agent**
- This agent reads comments on Reddit.
- It looks for people asking about your product.
- It then automatically responds to them.
2. **YouTube Content Repurposing Agent**
- This agent subscribes to your YouTube channel.
- When you post a new video, it transcribes it.
- It uses AI to write a search engine optimized blog post.
- Then, it publishes this blog post to your Medium account.
These examples show just a glimpse of what you can achieve with AutoGPT!
---
Our mission is to provide the tools, so that you can focus on what matters:
- 🏗️ **Building** - Lay the foundation for something amazing.
@@ -23,11 +49,13 @@ Be part of the revolution! **AutoGPT** is here to stay, at the forefront of AI i
**📖 [Documentation](https://docs.agpt.co)**
&ensp;|&ensp;
**🚀 [Contributing](CONTRIBUTING.md)**
&ensp;|&ensp;
---
## 🤖 AutoGPT Classic
> Below is information about the classic version of AutoGPT.
**🛠️ [Build your own Agent - Quickstart](FORGE-QUICKSTART.md)**
## 🧱 Building blocks
### 🏗️ Forge
**Forge your own agent!** &ndash; Forge is a ready-to-go template for your agent application. All the boilerplate code is already handled, letting you channel all your creativity into the things that set *your* agent apart. All tutorials are located [here](https://medium.com/@aiedge/autogpt-forge-e3de53cc58ec). Components from the [`forge.sdk`](/forge/forge/sdk) can also be used individually to speed up development and reduce boilerplate in your agent project.

View File

@@ -11,6 +11,9 @@
## GROQ_API_KEY - Groq API Key (Example: gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
# GROQ_API_KEY=
## LLAMAFILE_API_BASE - Llamafile API base URL
# LLAMAFILE_API_BASE=http://localhost:8080/v1
## TELEMETRY_OPT_IN - Share telemetry on errors and other issues with the AutoGPT team, e.g. through Sentry.
## This helps us to spot and solve problems earlier & faster. (Default: DISABLED)
# TELEMETRY_OPT_IN=true
@@ -102,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
View File

@@ -0,0 +1,3 @@
{
"python.analysis.typeCheckingMode": "basic",
}

View File

@@ -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)

3
autogpt/scripts/llamafile/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.llamafile
*.llamafile.exe
llamafile.exe

View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""
Use llamafile to serve a (quantized) mistral-7b-instruct-v0.2 model
Usage:
cd <repo-root>/autogpt
./scripts/llamafile/serve.py
"""
import os
import platform
import subprocess
from pathlib import Path
from typing import Optional
import click
LLAMAFILE = Path("mistral-7b-instruct-v0.2.Q5_K_M.llamafile")
LLAMAFILE_URL = f"https://huggingface.co/jartine/Mistral-7B-Instruct-v0.2-llamafile/resolve/main/{LLAMAFILE.name}" # noqa
LLAMAFILE_EXE = Path("llamafile.exe")
LLAMAFILE_EXE_URL = "https://github.com/Mozilla-Ocho/llamafile/releases/download/0.8.6/llamafile-0.8.6" # noqa
@click.command()
@click.option(
"--llamafile",
type=click.Path(dir_okay=False, path_type=Path),
help=f"Name of the llamafile to serve. Default: {LLAMAFILE.name}",
)
@click.option("--llamafile_url", help="Download URL for the llamafile you want to use")
@click.option(
"--host", help="Specify the address for the llamafile server to listen on"
)
@click.option(
"--port", type=int, help="Specify the port for the llamafile server to listen on"
)
@click.option(
"--force-gpu",
is_flag=True,
hidden=platform.system() != "Darwin",
help="Run the model using only the GPU (AMD or Nvidia). "
"Otherwise, both CPU and GPU may be (partially) used.",
)
def main(
llamafile: Optional[Path] = None,
llamafile_url: Optional[str] = None,
host: Optional[str] = None,
port: Optional[int] = None,
force_gpu: bool = False,
):
print(f"type(llamafile) = {type(llamafile)}")
if not llamafile:
if not llamafile_url:
llamafile = LLAMAFILE
else:
llamafile = Path(llamafile_url.rsplit("/", 1)[1])
if llamafile.suffix != ".llamafile":
click.echo(
click.style(
"The given URL does not end with '.llamafile' -> "
"can't get filename from URL. "
"Specify the filename using --llamafile.",
fg="red",
),
err=True,
)
return
if llamafile == LLAMAFILE and not llamafile_url:
llamafile_url = LLAMAFILE_URL
elif llamafile_url != LLAMAFILE_URL:
if not click.prompt(
click.style(
"You seem to have specified a different URL for the default model "
f"({llamafile.name}). Are you sure this is correct? "
"If you want to use a different model, also specify --llamafile.",
fg="yellow",
),
type=bool,
):
return
# Go to autogpt/scripts/llamafile/
os.chdir(Path(__file__).resolve().parent)
on_windows = platform.system() == "Windows"
if not llamafile.is_file():
if not llamafile_url:
click.echo(
click.style(
"Please use --lamafile_url to specify a download URL for "
f"'{llamafile.name}'. "
"This will only be necessary once, so we can download the model.",
fg="red",
),
err=True,
)
return
download_file(llamafile_url, llamafile)
if not on_windows:
llamafile.chmod(0o755)
subprocess.run([llamafile, "--version"], check=True)
if not on_windows:
base_command = [f"./{llamafile}"]
else:
# Windows does not allow executables over 4GB, so we have to download a
# model-less llamafile.exe and run that instead.
if not LLAMAFILE_EXE.is_file():
download_file(LLAMAFILE_EXE_URL, LLAMAFILE_EXE)
LLAMAFILE_EXE.chmod(0o755)
subprocess.run([f".\\{LLAMAFILE_EXE}", "--version"], check=True)
base_command = [f".\\{LLAMAFILE_EXE}", "-m", llamafile]
if host:
base_command.extend(["--host", host])
if port:
base_command.extend(["--port", str(port)])
if force_gpu:
base_command.extend(["-ngl", "9999"])
subprocess.run(
[
*base_command,
"--server",
"--nobrowser",
"--ctx-size",
"0",
"--n-predict",
"1024",
],
check=True,
)
# note: --ctx-size 0 means the prompt context size will be set directly from the
# underlying model configuration. This may cause slow response times or consume
# a lot of memory.
def download_file(url: str, to_file: Path) -> None:
print(f"Downloading {to_file.name}...")
import urllib.request
urllib.request.urlretrieve(url, to_file, reporthook=report_download_progress)
print()
def report_download_progress(chunk_number: int, chunk_size: int, total_size: int):
if total_size != -1:
downloaded_size = chunk_number * chunk_size
percent = min(1, downloaded_size / total_size)
bar = "#" * int(40 * percent)
print(
f"\rDownloading: [{bar:<40}] {percent:.0%}"
f" - {downloaded_size/1e6:.1f}/{total_size/1e6:.1f} MB",
end="",
)
if __name__ == "__main__":
main()

View File

@@ -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
View 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
View File

@@ -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()

View File

@@ -9,7 +9,7 @@ You can set configuration variables via the `.env` file. If you don't have a `.e
- `ANTHROPIC_API_KEY`: Set this if you want to use Anthropic models with AutoGPT
- `AZURE_CONFIG_FILE`: Location of the Azure Config file relative to the AutoGPT root directory. Default: azure.yaml
- `COMPONENT_CONFIG_FILE`: Path to the component configuration file (json) for an agent. Optional
- `DISABLED_COMMANDS`: Commands to disable. Use comma separated names of commands. See the list of commands from built-in components [here](../components/components.md). Default: None
- `DISABLED_COMMANDS`: Commands to disable. Use comma separated names of commands. See the list of commands from built-in components [here](../../forge/components/components.md). Default: None
- `ELEVENLABS_API_KEY`: ElevenLabs API Key. Optional.
- `ELEVENLABS_VOICE_ID`: ElevenLabs Voice ID. Optional.
- `EMBEDDING_MODEL`: LLM Model to use for embedding tasks. Default: `text-embedding-3-small`
@@ -22,6 +22,7 @@ You can set configuration variables via the `.env` file. If you don't have a `.e
- `GROQ_API_KEY`: Set this if you want to use Groq models with AutoGPT
- `HUGGINGFACE_API_TOKEN`: HuggingFace API, to be used for both image generation and audio to text. Optional.
- `HUGGINGFACE_IMAGE_MODEL`: HuggingFace model to use for image generation. Default: CompVis/stable-diffusion-v1-4
- `LLAMAFILE_API_BASE`: Llamafile API base URL. Default: `http://localhost:8080/v1`
- `OPENAI_API_KEY`: Set this if you want to use OpenAI models; [OpenAI API Key](https://platform.openai.com/account/api-keys).
- `OPENAI_ORGANIZATION`: Organization ID in OpenAI. Optional.
- `PLAIN_OUTPUT`: Plain output, which disables the spinner. Default: False

View File

@@ -198,3 +198,66 @@ If you don't know which to choose, you can safely go with OpenAI*.
[groq/api-keys]: https://console.groq.com/keys
[groq/models]: https://console.groq.com/docs/models
### Llamafile
With llamafile you can run models locally, which means no need to set up billing,
and guaranteed data privacy.
For more information and in-depth documentation, check out the [llamafile documentation].
!!! warning
At the moment, llamafile only serves one model at a time. This means you can not
set `SMART_LLM` and `FAST_LLM` to two different llamafile models.
!!! warning
Due to the issues linked below, llamafiles don't work on WSL. To use a llamafile
with AutoGPT in WSL, you will have to run the llamafile in Windows (outside WSL).
<details>
<summary>Instructions</summary>
1. Get the `llamafile/serve.py` script through one of these two ways:
1. Clone the AutoGPT repo somewhere in your Windows environment,
with the script located at `autogpt/scripts/llamafile/serve.py`
2. Download just the [serve.py] script somewhere in your Windows environment
2. Make sure you have `click` installed: `pip install click`
3. Run `ip route | grep default | awk '{print $3}'` *inside WSL* to get the address
of the WSL host machine
4. Run `python3 serve.py --host {WSL_HOST_ADDR}`, where `{WSL_HOST_ADDR}`
is the address you found at step 3.
If port 8080 is taken, also specify a different port using `--port {PORT}`.
5. In WSL, set `LLAMAFILE_API_BASE=http://{WSL_HOST_ADDR}:8080/v1` in your `.env`.
6. Follow the rest of the regular instructions below.
[serve.py]: https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpt/scripts/llamafile/serve.py
</details>
* [Mozilla-Ocho/llamafile#356](https://github.com/Mozilla-Ocho/llamafile/issues/356)
* [Mozilla-Ocho/llamafile#100](https://github.com/Mozilla-Ocho/llamafile/issues/100)
!!! note
These instructions will download and use `mistral-7b-instruct-v0.2.Q5_K_M.llamafile`.
`mistral-7b-instruct-v0.2` is currently the only tested and supported model.
If you want to try other models, you'll have to add them to `LlamafileModelName` in
[`llamafile.py`][forge/llamafile.py].
For optimal results, you may also have to add some logic to adapt the message format,
like `LlamafileProvider._adapt_chat_messages_for_mistral_instruct(..)` does.
1. Run the llamafile serve script:
```shell
python3 ./scripts/llamafile/serve.py
```
The first time this is run, it will download a file containing the model + runtime,
which may take a while and a few gigabytes of disk space.
To force GPU acceleration, add `--use-gpu` to the command.
3. In `.env`, set `SMART_LLM`/`FAST_LLM` or both to `mistral-7b-instruct-v0.2`
4. If the server is running on different address than `http://localhost:8080/v1`,
set `LLAMAFILE_API_BASE` in `.env` to the right base URL
[llamafile documentation]: https://github.com/Mozilla-Ocho/llamafile#readme
[forge/llamafile.py]: https://github.com/Significant-Gravitas/AutoGPT/blob/master/forge/forge/llm/providers/llamafile/llamafile.py

View File

@@ -213,5 +213,5 @@ For example, to disable python coding features, set it to the value below:
DISABLED_COMMANDS=execute_python_code,execute_python_file
```
[components]: ./components/components.md
[commands]: ./components/built-in-components.md
[components]: ../forge/components/components.md
[commands]: ../forge/components/built-in-components.md

View File

@@ -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

View File

@@ -4,13 +4,26 @@ Welcome to the AutoGPT Documentation.
The AutoGPT project consists of four main components:
* The [Agent](#agent) &ndash; also known as just "AutoGPT"
* The [Benchmark](#benchmark) &ndash; AKA `agbenchmark`
* The [Forge](#forge)
* The [Frontend](#frontend)
- The [Server](#server) &ndash; known as the "AutoGPT Platform"
- The [Agent](#agent) &ndash; also known as just "AutoGPT"
- The [Benchmark](#benchmark) &ndash; 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)**
&ensp;|&ensp;
**[📖 Advanced Setup](server/advanced_setup.md)**
&ensp;|&ensp;
**[📖 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

View 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
```

View 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

View File

@@ -1,18 +1,20 @@
# Contributing to AutoGPT Agent Server: Creating Blocks!
# Contributing to AutoGPT Agent Server: Creating and Testing Blocks
This guide will walk you through the process of creating a new block for the AutoGPT Agent Server. We'll use the GetWikipediaSummary block as an example.
This guide will walk you through the process of creating and testing a new block for the AutoGPT Agent Server, using the WikipediaSummaryBlock as an example.
## Understanding Blocks
## Understanding Blocks and Testing
Blocks are reusable components that can be connected to form a graph representing an agent's behavior. Each block has inputs, outputs, and a specific function it performs.
Blocks are reusable components that can be connected to form a graph representing an agent's behavior. Each block has inputs, outputs, and a specific function. Proper testing is crucial to ensure blocks work correctly and consistently.
## Creating a New Block
## Creating and Testing a New Block
To create a new block, follow these steps:
Follow these steps to create and test a new block:
1. **Create a new Python file** in the `autogpt_server/blocks` directory. Name it descriptively and use snake_case. For example: `get_wikipedia_summary.py`.
2. **Import necessary modules and create a class that inherits from `Block`**. Make sure to include all necessary imports for your block. Every block should contain:
2. **Import necessary modules and create a class that inherits from `Block`**. Make sure to include all necessary imports for your block.
Every block should contain the following:
```python
from autogpt_server.data.block import Block, BlockSchema, BlockOutput
@@ -21,10 +23,11 @@ To create a new block, follow these steps:
Example for the Wikipedia summary block:
```python
import requests
from autogpt_server.data.block import Block, BlockSchema, BlockOutput
from autogpt_server.utils.get_request import GetRequest
import requests
class GetWikipediaSummary(Block):
class WikipediaSummaryBlock(Block, GetRequest):
# Block implementation will go here
```
@@ -41,47 +44,60 @@ To create a new block, follow these steps:
class Output(BlockSchema):
summary: str # The summary of the topic from Wikipedia
error: str # Any error message if the request fails
```
4. **Implement the `__init__` method**:
4. **Implement the `__init__` method, including test data and mocks:**
```python
def __init__(self):
super().__init__(
id="h5e7f8g9-1b2c-3d4e-5f6g-7h8i9j0k1l2m", # Unique ID for the block
input_schema=GetWikipediaSummary.Input, # Assign input schema
output_schema=GetWikipediaSummary.Output, # Assign output schema
# 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",
input_schema=WikipediaSummaryBlock.Input, # Assign input schema
output_schema=WikipediaSummaryBlock.Output, # Assign output schema
# Provide sample input and output for testing the block
# Provide sample input, output and test mock for testing the block
test_input={"topic": "Artificial Intelligence"},
test_output={"summary": "Artificial intelligence (AI) is intelligence demonstrated by machines, in contrast to the natural intelligence displayed by humans and animals."},
test_output=("summary", "summary content"),
test_mock={"get_request": lambda url, json: {"extract": "summary content"}},
)
```
- `id`: A unique identifier for the block.
- `input_schema` and `output_schema`: Define the structure of the input and output data.
- `test_input` and `test_output`: Provide sample input and output data for testing the block.
5. **Implement the `run` method**, which contains the main logic of the block:
- `input_schema` and `output_schema`: Define the structure of the input and output data.
Let's break down the testing components:
- `test_input`: This is a sample input that will be used to test the block. It should be a valid input according to your Input schema.
- `test_output`: This is the expected output when running the block with the `test_input`. It should match your Output schema. For non-deterministic outputs or when you only want to assert the type, you can use Python types instead of specific values. In this example, `("summary", str)` asserts that the output key is "summary" and its value is a string.
- `test_mock`: This is crucial for blocks that make network calls. It provides a mock function that replaces the actual network call during testing.
In this case, we're mocking the `get_request` method to always return a dictionary with an 'extract' key, simulating a successful API response. This allows us to test the block's logic without making actual network requests, which could be slow, unreliable, or rate-limited.
5. **Implement the `run` method with error handling:**, this should contain the main logic of the block:
```python
def run(self, input_data: Input) -> BlockOutput:
try:
# Make the request to Wikipedia API
response = requests.get(f"https://en.wikipedia.org/api/rest_v1/page/summary/{input_data.topic}")
response.raise_for_status()
summary_data = response.json()
topic = input_data.topic
url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{topic}"
# Output the summary
yield "summary", summary_data['extract']
response = self.get_request(url, json=True)
yield "summary", response['extract']
except requests.exceptions.HTTPError as http_err:
raise ValueError(f"HTTP error occurred: {http_err}")
yield "error", f"HTTP error occurred: {http_err}"
except requests.RequestException as e:
raise ValueError(f"Request to Wikipedia API failed: {e}")
yield "error", f"Request to Wikipedia failed: {e}"
except KeyError as e:
raise ValueError(f"Error processing Wikipedia data: {e}")
yield "error", f"Error parsing Wikipedia response: {e}"
```
- **Try block**: Contains the main logic to fetch and process the Wikipedia summary.
@@ -89,84 +105,120 @@ To create a new block, follow these steps:
- **Error handling**: Handle various exceptions that might occur during the API request and data processing.
- **Yield**: Use `yield` to output the results.
6. **Register the new block** by adding it to the `__init__.py` file in the `autogpt_server/blocks` directory. This step makes your new block available to the rest of the server.
- Open the `__init__.py` file in the `autogpt_server/blocks` directory.
- Add an import statement for your new block at the top of the file.
- Add the new block to the `AVAILABLE_BLOCKS` and `__all__` lists.
Example:
```python
from autogpt_server.blocks import sample, reddit, text, ai, wikipedia, discord, get_wikipedia_summary # Import your new block
from autogpt_server.data.block import Block
AVAILABLE_BLOCKS = {
block.id: block
for block in [v() for v in Block.__subclasses__()]
}
__all__ = ["ai", "sample", "reddit", "text", "AVAILABLE_BLOCKS", "wikipedia", "discord", "get_wikipedia_summary"]
```
- The import statement ensures your new block is included in the module.
- The `AVAILABLE_BLOCKS` dictionary includes all blocks by their ID.
- The `__all__` list specifies all public objects that the module exports.
### Full Code example
Here is the complete implementation of the `GetWikipediaSummary` blocks:
```python
import requests
from autogpt_server.data.block import Block, BlockSchema, BlockOutput
class GetWikipediaSummary(Block):
# Define the input schema with the required field 'topic'
class Input(BlockSchema):
topic: str # The topic to get the Wikipedia summary for
# Define the output schema with the field 'summary'
class Output(BlockSchema):
summary: str # The summary of the topic from Wikipedia
def __init__(self):
super().__init__(
id="h5e7f8g9-1b2c-3d4e-5f6g-7h8i9j0k1l2m", # Unique ID for the block
input_schema=GetWikipediaSummary.Input, # Assign input schema
output_schema=GetWikipediaSummary.Output, # Assign output schema
# Provide sample input and output for testing the block
test_input={"topic": "Artificial Intelligence"},
test_output={"summary": "Artificial intelligence (AI) is intelligence demonstrated by machines, in contrast to the natural intelligence displayed by humans and animals."},
)
def run(self, input_data: Input) -> BlockOutput:
try:
# Make the request to Wikipedia API
response = requests.get(f"https://en.wikipedia.org/api/rest_v1/page/summary/{input_data.topic}")
response.raise_for_status()
summary_data = response.json()
# Output the summary
yield "summary", summary_data['extract']
except requests.exceptions.HTTPError as http_err:
raise ValueError(f"HTTP error occurred: {http_err}")
except requests.RequestException as e:
raise ValueError(f"Request to Wikipedia API failed: {e}")
except KeyError as e:
raise ValueError(f"Error processing Wikipedia data: {e}")
```
## Key Points to Remember
- **Unique ID**: Give your block a unique ID in the `__init__` method.
- **Unique ID**: Give your block a unique ID in the **init** method.
- **Input and Output Schemas**: Define clear input and output schemas.
- **Error Handling**: Implement error handling in the `run` method.
- **Output Results**: Use `yield` to output results in the `run` method.
- **Register the Block**: Add your new block to the `__init__.py` file to make it available to the server.
- **Testing**: Provide test input and output in the `__init__` method for automatic testing.
- **Testing**: Provide test input and output in the **init** method for automatic testing.
## Understanding the Testing Process
The testing of blocks is handled by `test_block.py`, which does the following:
1. It calls the block with the provided `test_input`.
2. If a `test_mock` is provided, it temporarily replaces the specified methods with the mock functions.
3. It then asserts that the output matches the `test_output`.
For the WikipediaSummaryBlock:
- The test will call the block with the topic "Artificial Intelligence".
- Instead of making a real API call, it will use the mock function, which returns `{"extract": "summary content"}`.
- It will then check if the output key is "summary" and its value is a string.
This approach allows us to test the block's logic comprehensively without relying on external services, while also accommodating non-deterministic outputs.
## Tips for Effective Block Testing
1. **Provide realistic test_input**: Ensure your test input covers typical use cases.
2. **Define appropriate test_output**:
- For deterministic outputs, use specific expected values.
- For non-deterministic outputs or when only the type matters, use Python types (e.g., `str`, `int`, `dict`).
- You can mix specific values and types, e.g., `("key1", str), ("key2", 42)`.
3. **Use test_mock for network calls**: This prevents tests from failing due to network issues or API changes.
4. **Consider omitting test_mock for blocks without external dependencies**: If your block doesn't make network calls or use external resources, you might not need a mock.
5. **Consider edge cases**: Include tests for potential error conditions in your `run` method.
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

View 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

View 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

View File

@@ -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
View File

@@ -0,0 +1,3 @@
{
"python.analysis.typeCheckingMode": "basic",
}

View File

@@ -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

View File

@@ -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():

View File

@@ -0,0 +1,36 @@
# Llamafile Integration Notes
Tested with:
* Python 3.11
* Apple M2 Pro (32 GB), macOS 14.2.1
* quantized mistral-7b-instruct-v0.2
## Setup
Download a `mistral-7b-instruct-v0.2` llamafile:
```shell
wget -nc https://huggingface.co/jartine/Mistral-7B-Instruct-v0.2-llamafile/resolve/main/mistral-7b-instruct-v0.2.Q5_K_M.llamafile
chmod +x mistral-7b-instruct-v0.2.Q5_K_M.llamafile
./mistral-7b-instruct-v0.2.Q5_K_M.llamafile --version
```
Run the llamafile server:
```shell
LLAMAFILE="./mistral-7b-instruct-v0.2.Q5_K_M.llamafile"
"${LLAMAFILE}" \
--server \
--nobrowser \
--ctx-size 0 \
--n-predict 1024
# note: ctx-size=0 means the prompt context size will be set directly from the
# underlying model configuration. This may cause slow response times or consume
# a lot of memory.
```
## TODOs
* `SMART_LLM`/`FAST_LLM` configuration: Currently, the llamafile server only serves one model at a time. However, there's no reason you can't start multiple llamafile servers on different ports. To support using different models for `smart_llm` and `fast_llm`, you could implement config vars like `LLAMAFILE_SMART_LLM_URL` and `LLAMAFILE_FAST_LLM_URL` that point to different llamafile servers (one serving a 'big model' and one serving a 'fast model').
* Authorization: the `serve.sh` script does not set up any authorization for the llamafile server; this can be turned on by adding arg `--api-key <some-key>` to the server startup command. However I haven't attempted to test whether the integration with autogpt works when this feature is turned on.
* Test with other models

View File

@@ -0,0 +1,17 @@
from .llamafile import (
LLAMAFILE_CHAT_MODELS,
LLAMAFILE_EMBEDDING_MODELS,
LlamafileCredentials,
LlamafileModelName,
LlamafileProvider,
LlamafileSettings,
)
__all__ = [
"LLAMAFILE_CHAT_MODELS",
"LLAMAFILE_EMBEDDING_MODELS",
"LlamafileCredentials",
"LlamafileModelName",
"LlamafileProvider",
"LlamafileSettings",
]

View File

@@ -0,0 +1,351 @@
import enum
import logging
import re
from pathlib import Path
from typing import Any, Iterator, Optional, Sequence
import requests
from openai.types.chat import (
ChatCompletionMessage,
ChatCompletionMessageParam,
CompletionCreateParams,
)
from pydantic import SecretStr
from forge.json.parsing import json_loads
from forge.models.config import UserConfigurable
from .._openai_base import BaseOpenAIChatProvider
from ..schema import (
AssistantToolCall,
AssistantToolCallDict,
ChatMessage,
ChatModelInfo,
CompletionModelFunction,
ModelProviderConfiguration,
ModelProviderCredentials,
ModelProviderName,
ModelProviderSettings,
ModelTokenizer,
)
class LlamafileModelName(str, enum.Enum):
MISTRAL_7B_INSTRUCT = "mistral-7b-instruct-v0.2"
LLAMAFILE_CHAT_MODELS = {
info.name: info
for info in [
ChatModelInfo(
name=LlamafileModelName.MISTRAL_7B_INSTRUCT,
provider_name=ModelProviderName.LLAMAFILE,
prompt_token_cost=0.0,
completion_token_cost=0.0,
max_tokens=32768,
has_function_call_api=False,
),
]
}
LLAMAFILE_EMBEDDING_MODELS = {}
class LlamafileConfiguration(ModelProviderConfiguration):
# TODO: implement 'seed' across forge.llm.providers
seed: Optional[int] = None
class LlamafileCredentials(ModelProviderCredentials):
api_key: Optional[SecretStr] = SecretStr("sk-no-key-required")
api_base: SecretStr = UserConfigurable( # type: ignore
default=SecretStr("http://localhost:8080/v1"), from_env="LLAMAFILE_API_BASE"
)
def get_api_access_kwargs(self) -> dict[str, str]:
return {
k: v.get_secret_value()
for k, v in {
"api_key": self.api_key,
"base_url": self.api_base,
}.items()
if v is not None
}
class LlamafileSettings(ModelProviderSettings):
configuration: LlamafileConfiguration # type: ignore
credentials: Optional[LlamafileCredentials] = None # type: ignore
class LlamafileTokenizer(ModelTokenizer[int]):
def __init__(self, credentials: LlamafileCredentials):
self._credentials = credentials
@property
def _tokenizer_base_url(self):
# The OpenAI-chat-compatible base url should look something like
# 'http://localhost:8080/v1' but the tokenizer endpoint is
# 'http://localhost:8080/tokenize'. So here we just strip off the '/v1'.
api_base = self._credentials.api_base.get_secret_value()
return api_base.strip("/v1")
def encode(self, text: str) -> list[int]:
response = requests.post(
url=f"{self._tokenizer_base_url}/tokenize", json={"content": text}
)
response.raise_for_status()
return response.json()["tokens"]
def decode(self, tokens: list[int]) -> str:
response = requests.post(
url=f"{self._tokenizer_base_url}/detokenize", json={"tokens": tokens}
)
response.raise_for_status()
return response.json()["content"]
class LlamafileProvider(
BaseOpenAIChatProvider[LlamafileModelName, LlamafileSettings],
# TODO: add and test support for embedding models
# BaseOpenAIEmbeddingProvider[LlamafileModelName, LlamafileSettings],
):
EMBEDDING_MODELS = LLAMAFILE_EMBEDDING_MODELS
CHAT_MODELS = LLAMAFILE_CHAT_MODELS
MODELS = {**CHAT_MODELS, **EMBEDDING_MODELS}
default_settings = LlamafileSettings(
name="llamafile_provider",
description=(
"Provides chat completion and embedding services "
"through a llamafile instance"
),
configuration=LlamafileConfiguration(),
)
_settings: LlamafileSettings
_credentials: LlamafileCredentials
_configuration: LlamafileConfiguration
async def get_available_models(self) -> Sequence[ChatModelInfo[LlamafileModelName]]:
_models = (await self._client.models.list()).data
# note: at the moment, llamafile only serves one model at a time (so this
# list will only ever have one value). however, in the future, llamafile
# may support multiple models, so leaving this method as-is for now.
self._logger.debug(f"Retrieved llamafile models: {_models}")
clean_model_ids = [clean_model_name(m.id) for m in _models]
self._logger.debug(f"Cleaned llamafile model IDs: {clean_model_ids}")
return [
LLAMAFILE_CHAT_MODELS[id]
for id in clean_model_ids
if id in LLAMAFILE_CHAT_MODELS
]
def get_tokenizer(self, model_name: LlamafileModelName) -> LlamafileTokenizer:
return LlamafileTokenizer(self._credentials)
def count_message_tokens(
self,
messages: ChatMessage | list[ChatMessage],
model_name: LlamafileModelName,
) -> int:
if isinstance(messages, ChatMessage):
messages = [messages]
if model_name == LlamafileModelName.MISTRAL_7B_INSTRUCT:
# For mistral-instruct, num added tokens depends on if the message
# is a prompt/instruction or an assistant-generated message.
# - prompt gets [INST], [/INST] added and the first instruction
# begins with '<s>' ('beginning-of-sentence' token).
# - assistant-generated messages get '</s>' added
# see: https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2
#
prompt_added = 1 # one for '<s>' token
assistant_num_added = 0
ntokens = 0
for message in messages:
if (
message.role == ChatMessage.Role.USER
# note that 'system' messages will get converted
# to 'user' messages before being sent to the model
or message.role == ChatMessage.Role.SYSTEM
):
# 5 tokens for [INST], [/INST], which actually get
# tokenized into "[, INST, ]" and "[, /, INST, ]"
# by the mistral tokenizer
prompt_added += 5
elif message.role == ChatMessage.Role.ASSISTANT:
assistant_num_added += 1 # for </s>
else:
raise ValueError(
f"{model_name} does not support role: {message.role}"
)
ntokens += self.count_tokens(message.content, model_name)
total_token_count = prompt_added + assistant_num_added + ntokens
return total_token_count
else:
raise NotImplementedError(
f"count_message_tokens not implemented for model {model_name}"
)
def _get_chat_completion_args(
self,
prompt_messages: list[ChatMessage],
model: LlamafileModelName,
functions: list[CompletionModelFunction] | None = None,
max_output_tokens: int | None = None,
**kwargs,
) -> tuple[
list[ChatCompletionMessageParam], CompletionCreateParams, dict[str, Any]
]:
messages, completion_kwargs, parse_kwargs = super()._get_chat_completion_args(
prompt_messages, model, functions, max_output_tokens, **kwargs
)
if model == LlamafileModelName.MISTRAL_7B_INSTRUCT:
messages = self._adapt_chat_messages_for_mistral_instruct(messages)
if "seed" not in kwargs and self._configuration.seed is not None:
completion_kwargs["seed"] = self._configuration.seed
# Convert all messages with content blocks to simple text messages
for message in messages:
if isinstance(content := message.get("content"), list):
message["content"] = "\n\n".join(
b["text"]
for b in content
if b["type"] == "text"
# FIXME: add support for images through image_data completion kwarg
)
return messages, completion_kwargs, parse_kwargs
def _adapt_chat_messages_for_mistral_instruct(
self, messages: list[ChatCompletionMessageParam]
) -> list[ChatCompletionMessageParam]:
"""
Munge the messages to be compatible with the mistral-7b-instruct chat
template, which:
- only supports 'user' and 'assistant' roles.
- expects messages to alternate between user/assistant roles.
See details here:
https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2#instruction-format
"""
adapted_messages: list[ChatCompletionMessageParam] = []
for message in messages:
# convert 'system' role to 'user' role as mistral-7b-instruct does
# not support 'system'
if message["role"] == ChatMessage.Role.SYSTEM:
message["role"] = ChatMessage.Role.USER
if (
len(adapted_messages) == 0
or message["role"] != (last_message := adapted_messages[-1])["role"]
):
adapted_messages.append(message)
else:
if not message.get("content"):
continue
# if the curr message has the same role as the previous one,
# concat the current message content to the prev message
if message["role"] == "user" and last_message["role"] == "user":
# user messages can contain other types of content blocks
if not isinstance(last_message["content"], list):
last_message["content"] = [
{"type": "text", "text": last_message["content"]}
]
last_message["content"].extend(
message["content"]
if isinstance(message["content"], list)
else [{"type": "text", "text": message["content"]}]
)
elif message["role"] != "user" and last_message["role"] != "user":
last_message["content"] = (
(last_message.get("content") or "")
+ "\n\n"
+ (message.get("content") or "")
).strip()
return adapted_messages
def _parse_assistant_tool_calls(
self,
assistant_message: ChatCompletionMessage,
compat_mode: bool = False,
**kwargs,
):
tool_calls: list[AssistantToolCall] = []
parse_errors: list[Exception] = []
if compat_mode and assistant_message.content:
try:
tool_calls = list(
_tool_calls_compat_extract_calls(assistant_message.content)
)
except Exception as e:
parse_errors.append(e)
return tool_calls, parse_errors
def clean_model_name(model_file: str) -> str:
"""
Clean up model names:
1. Remove file extension
2. Remove quantization info
Examples:
```
raw: 'mistral-7b-instruct-v0.2.Q5_K_M.gguf'
clean: 'mistral-7b-instruct-v0.2'
raw: '/Users/kate/models/mistral-7b-instruct-v0.2.Q5_K_M.gguf'
clean: 'mistral-7b-instruct-v0.2'
raw: 'llava-v1.5-7b-q4.gguf'
clean: 'llava-v1.5-7b'
```
"""
name_without_ext = Path(model_file).name.rsplit(".", 1)[0]
name_without_Q = re.match(
r"^[a-zA-Z0-9]+([.\-](?!([qQ]|B?F)\d{1,2})[a-zA-Z0-9]+)*",
name_without_ext,
)
return name_without_Q.group() if name_without_Q else name_without_ext
def _tool_calls_compat_extract_calls(response: str) -> Iterator[AssistantToolCall]:
import re
import uuid
logging.debug(f"Trying to extract tool calls from response:\n{response}")
response = response.strip() # strip off any leading/trailing whitespace
if response.startswith("```"):
# attempt to remove any extraneous markdown artifacts like "```json"
response = response.strip("```")
if response.startswith("json"):
response = response.strip("json")
response = response.strip() # any remaining whitespace
if response[0] == "[":
tool_calls: list[AssistantToolCallDict] = json_loads(response)
else:
block = re.search(r"```(?:tool_calls)?\n(.*)\n```\s*$", response, re.DOTALL)
if not block:
raise ValueError("Could not find tool_calls block in response")
tool_calls: list[AssistantToolCallDict] = json_loads(block.group(1))
for t in tool_calls:
t["id"] = str(uuid.uuid4())
# t["function"]["arguments"] = str(t["function"]["arguments"]) # HACK
yield AssistantToolCall.parse_obj(t)

View File

@@ -1,12 +1,13 @@
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
from .anthropic import ANTHROPIC_CHAT_MODELS, AnthropicModelName, AnthropicProvider
from .groq import GROQ_CHAT_MODELS, GroqModelName, GroqProvider
from .llamafile import LLAMAFILE_CHAT_MODELS, LlamafileModelName, LlamafileProvider
from .openai import OPEN_AI_CHAT_MODELS, OpenAIModelName, OpenAIProvider
from .schema import (
AssistantChatMessage,
@@ -24,10 +25,15 @@ from .schema import (
_T = TypeVar("_T")
ModelName = AnthropicModelName | GroqModelName | OpenAIModelName
ModelName = AnthropicModelName | GroqModelName | LlamafileModelName | OpenAIModelName
EmbeddingModelProvider = OpenAIProvider
CHAT_MODELS = {**ANTHROPIC_CHAT_MODELS, **GROQ_CHAT_MODELS, **OPEN_AI_CHAT_MODELS}
CHAT_MODELS = {
**ANTHROPIC_CHAT_MODELS,
**GROQ_CHAT_MODELS,
**LLAMAFILE_CHAT_MODELS,
**OPEN_AI_CHAT_MODELS,
}
class MultiProvider(BaseChatModelProvider[ModelName, ModelProviderSettings]):
@@ -62,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
@@ -114,37 +120,58 @@ 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 {provider_name} is available...")
try:
yield self._get_provider(provider_name)
except Exception:
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)
if not _provider:
Provider = self._get_provider_class(provider_name)
self._logger.debug(
f"{Provider.__name__} not yet in cache, trying to init..."
)
settings = Provider.default_settings.model_copy(deep=True)
settings.budget = self._budget
settings.configuration.extra_request_headers.update(
self._settings.configuration.extra_request_headers
)
if settings.credentials is None:
credentials_field = settings.model_fields["credentials"]
Credentials = get_args( # Union[Credentials, None] -> Credentials
credentials_field.annotation
)[0]
self._logger.debug(f"Loading {Credentials.__name__}...")
try:
Credentials = get_args( # Union[Credentials, None] -> Credentials
settings.model_fields["credentials"].annotation
)[0]
settings.credentials = Credentials.from_env()
except ValidationError as e:
raise ValueError(
f"{provider_name} is unavailable: can't load credentials"
) from e
if credentials_field.is_required():
self._logger.debug(
f"Could not load (required) {Credentials.__name__}"
)
raise ValueError(
f"{Provider.__name__} is unavailable: "
"can't load credentials"
) from e
self._logger.debug(
f"Could not load {Credentials.__name__}, continuing without..."
)
self._provider_instances[provider_name] = _provider = Provider(
settings=settings, logger=self._logger # type: ignore
)
_provider._budget = self._budget # Object binding not preserved by Pydantic
self._logger.debug(f"Initialized {Provider.__name__}!")
return _provider
@classmethod
@@ -155,6 +182,7 @@ class MultiProvider(BaseChatModelProvider[ModelName, ModelProviderSettings]):
return {
ModelProviderName.ANTHROPIC: AnthropicProvider,
ModelProviderName.GROQ: GroqProvider,
ModelProviderName.LLAMAFILE: LlamafileProvider,
ModelProviderName.OPENAI: OpenAIProvider,
}[provider_name]
except KeyError:
@@ -164,4 +192,10 @@ class MultiProvider(BaseChatModelProvider[ModelName, ModelProviderSettings]):
return f"{self.__class__.__name__}()"
ChatModelProvider = AnthropicProvider | GroqProvider | OpenAIProvider | MultiProvider
ChatModelProvider = (
AnthropicProvider
| GroqProvider
| LlamafileProvider
| OpenAIProvider
| MultiProvider
)

View File

@@ -55,6 +55,7 @@ class ModelProviderName(str, enum.Enum):
OPENAI = "openai"
ANTHROPIC = "anthropic"
GROQ = "groq"
LLAMAFILE = "llamafile"
class ChatMessage(BaseModel):

View File

@@ -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)
![Header](../../../docs/content/imgs/quickstart/000_header_img.png)
![Header](..%2F..%2F..%2Fdocs/content/imgs/quickstart/000_header_img.png)
**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.
![The Github repository](../../../docs/content/imgs/quickstart/001_repo.png)
![The Github repository](..%2F..%2F..%2Fdocs/content/imgs/quickstart/001_repo.png)
Follow the on-screen instructions to complete the process.
![Create Fork Page](../../../docs/content/imgs/quickstart/002_fork.png)
![Create Fork Page](..%2F..%2F..%2Fdocs/content/imgs/quickstart/002_fork.png)
### 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.
![img_1.png](../../../docs/content/imgs/quickstart/003A_clone.png)
![img_1.png](..%2F..%2F..%2Fdocs/content/imgs/quickstart/003A_clone.png)
```bash
# replace the url with the one for your forked repo
git clone https://github.com/<YOUR REPO PATH HERE>
```
![Clone the Repository](../../../docs/content/imgs/quickstart/003_clone.png)
![Clone the Repository](..%2F..%2F..%2Fdocs/content/imgs/quickstart/003_clone.png)
### 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.
![Setup the Project](../../../docs/content/imgs/quickstart/005_setup.png)
![Setup Complete](../../../docs/content/imgs/quickstart/006_setup_complete.png)
![Setup the Project](..%2F..%2F..%2Fdocs/content/imgs/quickstart/005_setup.png)
![Setup Complete](..%2F..%2F..%2Fdocs/content/imgs/quickstart/006_setup_complete.png)
## 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.
![Create an Agent](../../../docs/content/imgs/quickstart/007_create_agent.png)
![Create an Agent](..%2F..%2F..%2Fdocs/content/imgs/quickstart/007_create_agent.png)
## 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/`.
![Start the Agent](../../../docs/content/imgs/quickstart/009_start_agent.png)
![Start the Agent](..%2F..%2F..%2Fdocs/content/imgs/quickstart/009_start_agent.png)
### 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.
![Login](../../../docs/content/imgs/quickstart/010_login.png)
![Home](../../../docs/content/imgs/quickstart/011_home.png)
![Login](..%2F..%2F..%2Fdocs/content/imgs/quickstart/010_login.png)
![Home](..%2F..%2F..%2Fdocs/content/imgs/quickstart/011_home.png)
### Stopping and Restarting Your Agent
When needed, use Ctrl+C to end the session or use the stop command:

View File

@@ -7,7 +7,7 @@
---
![Header](../../../docs/content/imgs/quickstart/t2_01.png)
![Header](..%2F..%2Fdocs/content/imgs/quickstart/t2_01.png)
@@ -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 dont just compute; they understand, reason, and then act, drawing from a vast reservoir of information.
![AI visualising AI researchers hard at work](../../../docs/content/imgs/quickstart/t2_02.png)
![AI visualising AI researchers hard at work](..%2F..%2Fdocs/content/imgs/quickstart/t2_02.png)
## The Anatomy of an LLM-Based AI Agent
Diving deep into the core of an LLM-based AI agent, we find its structured much like a human, with distinct components akin to personality, memory, thought process, and abilities. Lets break these down:
![The Github repository](../../../docs/content/imgs/quickstart/t2_03.png)
![The Github repository](..%2F..%2Fdocs/content/imgs/quickstart/t2_03.png)
Anatomy of an Agent from the Agent Landscape Survey
### **Profile**

View File

@@ -1,6 +1,6 @@
# AutoGPT Forge: Crafting Intelligent Agent Logic
![Header](../../../docs/content/imgs/quickstart/t3_01.png)
![Header](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_01.png)
**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!
![SmartAgent](../../../docs/content/imgs/quickstart/t3_02.png)
![SmartAgent](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_02.png)
---
@@ -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 101](../../../docs/content/imgs/quickstart/t3_03.png)
![Prompting 101](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_03.png)
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.
![Benchmarking page of the AutoGPT UI](../../../docs/content/imgs/quickstart/t3_04.png)
![Benchmarking page of the AutoGPT UI](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_04.png)
4. **Select the 'WriteFile' Test**
- Choose the 'WriteFile' test from the available options.

114
rnd/README.md Normal file
View File

@@ -0,0 +1,114 @@
# AutoGPT Platform
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.
## Getting Started
### 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.

0
rnd/__init__.py Normal file
View File

View File

@@ -1 +1,15 @@
AGPT_SERVER_URL=http://localhost:8000
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:8005/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

View File

@@ -34,3 +34,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# Sentry Config File
.env.sentry-build-plugin

View File

@@ -0,0 +1,4 @@
node_modules
.next
build
public

View File

@@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
}

28
rnd/autogpt_builder/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "yarn dev"
},
{
"name": "Next.js: debug client-side",
"type": "msedge",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "yarn dev",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithEdge"
}
}
]
}

View 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"]

View File

@@ -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

View File

@@ -14,4 +14,4 @@
"components": "@/components",
"utils": "@/lib/utils"
}
}
}

View File

@@ -1,8 +0,0 @@
const dotenv = require('dotenv');
dotenv.config();
module.exports = {
env: {
AGPT_SERVER_URL: process.env.AGPT_SERVER_URL,
},
};

View File

@@ -1,14 +1,84 @@
import { withSentryConfig } from "@sentry/nextjs";
import dotenv from "dotenv";
// Load environment variables
dotenv.config();
/** @type {import('next').NextConfig} */
const nextConfig = {
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;
export default withSentryConfig(nextConfig, {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
org: "significant-gravitas",
project: "builder",
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Automatically annotate React components to show their full name in breadcrumbs and session replay
reactComponentAnnotation: {
enabled: true,
},
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
tunnelRoute: "/monitoring",
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
async headers() {
return [
{
source: "/:path*",
headers: [
{
key: "Document-Policy",
value: "js-profiling",
},
],
},
];
},
});

View File

@@ -6,30 +6,55 @@
"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",
"@sentry/nextjs": "^8",
"@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",
"moment": "^2.30.1",
"dotenv": "^16.4.5",
"lucide-react": "^0.407.0",
"moment": "^2.30.1",
"next": "14.2.4",
"next-themes": "^0.3.0",
"react": "^18",
"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"
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
@@ -39,6 +64,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"
}

View File

@@ -0,0 +1,57 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://fe4e4aa4a283391808a5da396da20159@o4505260022104064.ingest.us.sentry.io/4507946746380288",
// Add optional integrations for additional features
integrations: [
Sentry.replayIntegration(),
Sentry.httpClientIntegration(),
Sentry.replayCanvasIntegration(),
Sentry.reportingObserverIntegration(),
Sentry.browserProfilingIntegration(),
// Sentry.feedbackIntegration({
// // Additional SDK configuration goes in here, for example:
// colorScheme: "system",
// }),
],
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,
// Set `tracePropagationTargets` to control for which URLs trace propagation should be enabled
tracePropagationTargets: [
"localhost",
/^https:\/\/dev\-builder\.agpt\.co\/api/,
],
beforeSend(event, hint) {
// Check if it is an exception, and if so, show the report dialog
if (event.exception && event.event_id) {
Sentry.showReportDialog({ eventId: event.event_id });
}
return event;
},
// Define how likely Replay events are sampled.
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0.1,
// Define how likely Replay events are sampled when an error occurs.
replaysOnErrorSampleRate: 1.0,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
// Set profilesSampleRate to 1.0 to profile every transaction.
// Since profilesSampleRate is relative to tracesSampleRate,
// the final profiling rate can be computed as tracesSampleRate * profilesSampleRate
// For example, a tracesSampleRate of 0.5 and profilesSampleRate of 0.5 would
// result in 25% of transactions being profiled (0.5*0.5=0.25)
profilesSampleRate: 1.0,
});

View File

@@ -0,0 +1,16 @@
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
// The config you add here will be used whenever one of the edge features is loaded.
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://fe4e4aa4a283391808a5da396da20159@o4505260022104064.ingest.us.sentry.io/4507946746380288",
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});

View File

@@ -0,0 +1,23 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
// import { NodeProfilingIntegration } from "@sentry/profiling-node";
Sentry.init({
dsn: "https://fe4e4aa4a283391808a5da396da20159@o4505260022104064.ingest.us.sentry.io/4507946746380288",
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
// Integrations
integrations: [
Sentry.anrIntegration(),
// NodeProfilingIntegration,
// Sentry.fsIntegration(),
],
});

View 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 />;
}

View 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>
);
}

View 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 />;
}

View 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 />;
}

View 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 />;
}

View 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>
);
}

View 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`);
}

View 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");
}

View File

@@ -1,43 +1,16 @@
"use client";
import Image from "next/image";
import { useSearchParams } from "next/navigation";
import FlowEditor from '@/components/Flow';
export default function Home() {
return (
<div className="flex flex-col items-center px-12">
<div className="z-10 w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-600 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-900 dark:bg-zinc-900 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
Get started by adding a&nbsp;
<code className="font-mono font-bold">node</code>
</p>
<div
className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none"
>
<a
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
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>
const query = useSearchParams();
<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={useSearchParams().get("flowID") ?? undefined}
/>
</div>
</div>
return (
<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")}
/>
);
}

View 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&apos;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>
);
}

View File

@@ -0,0 +1,27 @@
"use client";
import * as Sentry from "@sentry/nextjs";
import NextError from "next/error";
import { useEffect } from "react";
export default function GlobalError({
error,
}: {
error: Error & { digest?: string };
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<html>
<body>
{/* `NextError` is the default Next.js error page component. Its type
definition requires a `statusCode` prop. However, since the App Router
does not expose status codes for errors, we simply pass 0 to render a
generic error message. */}
<NextError statusCode={0} />
</body>
</html>
);
}

View File

@@ -2,32 +2,74 @@
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
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;
}
}

View File

@@ -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>
);
}

View 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>
);
}

View File

@@ -0,0 +1,63 @@
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { createServerClient } from "@/lib/supabase/server";
import { z } from "zod";
import * as Sentry from "@sentry/nextjs";
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>) {
return await Sentry.withServerActionInstrumentation("login", {}, async () => {
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>) {
return await Sentry.withServerActionInstrumentation(
"signup",
{},
async () => {
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);
}
revalidatePath("/", "layout");
redirect("/profile");
},
);
}

View 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>
);
}

View 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:8001/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>
);
}

View 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:8001/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;

View 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;

View File

@@ -1,577 +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 { Pencil2Icon } from '@radix-ui/react-icons';
import AutoGPTServerAPI, { Flow, NodeExecutionResult } from '@/lib/autogpt_server_api';
import { cn, 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
const Monitor = () => {
const [flows, setFlows] = useState<Flow[]>([]);
const [flowRuns, setFlowRuns] = useState<FlowRun[]>([]);
const [selectedFlow, setSelectedFlow] = useState<Flow | 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() {
// Fetch flow IDs
api.listFlowIDs()
.then(flowIDs => {
Promise.all(flowIDs.map(flowID => {
refreshFlowRuns(flowID);
// Fetch flow
return api.getFlow(flowID);
}))
.then(flows => setFlows(flows));
});
}
function refreshFlowRuns(flowID: string) {
// Fetch flow run IDs
api.listFlowRunIDs(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.getFlowExecutionInfo(flowID, runID)
.then(execInfo => setFlowRuns(flowRuns => {
const flowRunIndex = flowRuns.findIndex(fr => fr.id == runID);
const flowRun = flowRunFromNodeExecutionResults(flowID, runID, execInfo)
if (flowRunIndex > -1) {
flowRuns.splice(flowRunIndex, 1, flowRun)
}
else {
flowRuns.push(flowRun)
}
return flowRuns
}));
}));
}
return (
<div className="grid grid-cols-1 md:grid-cols-5 lg:grid-cols-4 xl:grid-cols-10 gap-4">
<AgentFlowList
className="md:col-span-2 xl:col-span-3 xxl:col-span-2"
flows={flows}
flowRuns={flowRuns}
selectedFlow={selectedFlow}
onSelectFlow={f => {
setSelectedRun(null);
setSelectedFlow(f.id == selectedFlow?.id ? null : f);
}}
/>
<FlowRunsList
className="md:col-span-3 lg:col-span-2 xl:col-span-3 space-y-4"
flows={flows}
runs={
(
selectedFlow
? flowRuns.filter(v => v.flowID == selectedFlow.id)
: flowRuns
)
.toSorted((a, b) => Number(a.startTime) - Number(b.startTime))
}
selectedRun={selectedRun}
onSelectRun={r => setSelectedRun(r.id == selectedRun?.id ? null : r)}
/>
<div className="col-span-full xl:col-span-4 xxl:col-span-5">
{selectedRun && (
<FlowRunInfo
flow={selectedFlow || flows.find(f => f.id == selectedRun.flowID)!}
flowRun={selectedRun}
/>
) || selectedFlow && (
<FlowInfo
flow={selectedFlow}
flowRuns={flowRuns.filter(r => r.flowID == selectedFlow.id)}
/>
) || (
<Card className="p-6">
<FlowRunsStats flows={flows} flowRuns={flowRuns} />
</Card>
)}
</div>
</div>
);
};
type FlowRun = {
id: string
flowID: string
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(
flowID: string, runID: string, 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: runID,
flowID: flowID,
status,
startTime,
endTime,
duration,
totalRunTime,
nodeExecutionResults: nodeExecutionResults
};
}
const AgentFlowList = (
{ flows, flowRuns, selectedFlow, onSelectFlow, className }: {
flows: Flow[],
flowRuns?: FlowRun[],
selectedFlow: Flow | null,
onSelectFlow: (f: Flow) => void,
className?: string,
}
) => (
<Card className={className}>
<CardHeader>
<CardTitle>Agents</CardTitle>
</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.flowID == 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: Flow[];
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.flowID)!.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<{
flow: Flow;
flowRuns: FlowRun[];
}> = ({ flow, flowRuns }) => {
return <Card>
<CardHeader className="flex-row items-center justify-between space-y-0 space-x-3">
<div>
<CardTitle>{flow.name}</CardTitle>
<p className="mt-2">Agent ID: <code>{flow.id}</code></p>
</div>
<Link className={buttonVariants({ variant: "outline" })} href={`/build?flowID=${flow.id}`}>
<Pencil2Icon className="mr-2" /> Edit Agent
</Link>
</CardHeader>
<CardContent>
<FlowRunsStats
flows={[flow]}
flowRuns={flowRuns.filter(r => r.flowID == flow.id)}
/>
</CardContent>
</Card>;
};
const FlowRunInfo: React.FC<{
flow: Flow;
flowRun: FlowRun;
}> = ({ flow, flowRun }) => {
if (flowRun.flowID != flow.id) {
throw new Error(`FlowRunInfo can't be used with non-matching flowRun.flowID and flow.id`)
}
return <Card>
<CardHeader className="flex-row items-center justify-between space-y-0 space-x-3">
<div>
<CardTitle>{flow.name}</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: Flow[],
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: Flow[],
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.flowID);
return (
<Card className="p-2 text-xs leading-normal">
<p><strong>Agent:</strong> {flow ? flow.name : 'Unknown'}</p>
<p>
<strong>Status:</strong>&nbsp;
<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.flowID == 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.flowID) * 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;

View 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;

View 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>
);
}

View File

@@ -0,0 +1,17 @@
"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";
export function Providers({ children, ...props }: ThemeProviderProps) {
return (
<NextThemesProvider {...props}>
<SupabaseProvider>
<TooltipProvider>{children}</TooltipProvider>
</SupabaseProvider>
</NextThemesProvider>
);
}

View 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>
);
}

View 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;

View 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

View 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

View 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;

View File

@@ -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;

View 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>
);
}

View 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;

View 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;

View 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 };

View 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;

View 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;

View File

@@ -0,0 +1,141 @@
import React, {
useState,
useCallback,
forwardRef,
useImperativeHandle,
} from "react";
import RunnerInputUI from "./runner-ui/RunnerInputUI";
import RunnerOutputUI from "./runner-ui/RunnerOutputUI";
import { Node } from "@xyflow/react";
import { filterBlocksByType } from "@/lib/utils";
import { BlockIORootSchema } from "@/lib/autogpt-server-api/types";
interface RunnerUIWrapperProps {
nodes: Node[];
setNodes: React.Dispatch<React.SetStateAction<Node[]>>;
isRunning: boolean;
requestSaveAndRun: () => void;
}
export interface RunnerUIWrapperRef {
openRunnerInput: () => void;
openRunnerOutput: () => void;
runOrOpenInput: () => void;
}
const RunnerUIWrapper = forwardRef<RunnerUIWrapperRef, RunnerUIWrapperProps>(
({ nodes, setNodes, isRunning, requestSaveAndRun }, ref) => {
const [isRunnerInputOpen, setIsRunnerInputOpen] = useState(false);
const [isRunnerOutputOpen, setIsRunnerOutputOpen] = useState(false);
const getBlockInputsAndOutputs = useCallback(() => {
const inputBlocks = filterBlocksByType(
nodes,
(node) => node.data.block_id === "c0a8e994-ebf1-4a9c-a4d8-89d09c86741b",
);
const outputBlocks = filterBlocksByType(
nodes,
(node) => node.data.block_id === "363ae599-353e-4804-937e-b2ee3cef3da4",
);
const inputs = inputBlocks.map((node) => ({
id: node.id,
type: "input" as const,
inputSchema: node.data.inputSchema as BlockIORootSchema,
hardcodedValues: {
name: (node.data.hardcodedValues as any).name || "",
description: (node.data.hardcodedValues as any).description || "",
value: (node.data.hardcodedValues as any).value,
placeholder_values:
(node.data.hardcodedValues as any).placeholder_values || [],
limit_to_placeholder_values:
(node.data.hardcodedValues as any).limit_to_placeholder_values ||
false,
},
}));
const outputs = outputBlocks.map((node) => ({
id: node.id,
type: "output" as const,
outputSchema: node.data.outputSchema as BlockIORootSchema,
hardcodedValues: {
name: (node.data.hardcodedValues as any).name || "Output",
description:
(node.data.hardcodedValues as any).description ||
"Output from the agent",
value: (node.data.hardcodedValues as any).value,
},
result: (node.data.executionResults as any)?.at(-1)?.data?.output,
}));
return { inputs, outputs };
}, [nodes]);
const handleInputChange = useCallback(
(nodeId: string, field: string, value: string) => {
setNodes((nds) =>
nds.map((node) => {
if (node.id === nodeId) {
return {
...node,
data: {
...node.data,
hardcodedValues: {
...(node.data.hardcodedValues as any),
[field]: value,
},
},
};
}
return node;
}),
);
},
[setNodes],
);
const openRunnerInput = () => setIsRunnerInputOpen(true);
const openRunnerOutput = () => setIsRunnerOutputOpen(true);
const runOrOpenInput = () => {
const { inputs } = getBlockInputsAndOutputs();
if (inputs.length > 0) {
openRunnerInput();
} else {
requestSaveAndRun();
}
};
useImperativeHandle(ref, () => ({
openRunnerInput,
openRunnerOutput,
runOrOpenInput,
}));
return (
<>
<RunnerInputUI
isOpen={isRunnerInputOpen}
onClose={() => setIsRunnerInputOpen(false)}
blockInputs={getBlockInputsAndOutputs().inputs}
onInputChange={handleInputChange}
onRun={() => {
setIsRunnerInputOpen(false);
requestSaveAndRun();
}}
isRunning={isRunning}
/>
<RunnerOutputUI
isOpen={isRunnerOutputOpen}
onClose={() => setIsRunnerOutputOpen(false)}
blockOutputs={getBlockInputsAndOutputs().outputs}
/>
</>
);
},
);
RunnerUIWrapper.displayName = "RunnerUIWrapper";
export default RunnerUIWrapper;

View File

@@ -0,0 +1,39 @@
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Info } from "lucide-react";
import ReactMarkdown from "react-markdown";
const SchemaTooltip: React.FC<{ description?: string }> = ({ description }) => {
if (!description) return null;
return (
<TooltipProvider delayDuration={400}>
<Tooltip>
<TooltipTrigger asChild>
<Info className="rounded-full p-1 hover:bg-gray-300" size={24} />
</TooltipTrigger>
<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;

View 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;
};

View 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;

View File

@@ -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>
);
};

View File

@@ -0,0 +1,74 @@
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";
import * as Sentry from "@sentry/nextjs";
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";
return await Sentry.withServerActionInstrumentation(
"removeFeaturedAgent",
{},
async () => {
const all = rows.map((row) => removeFeaturedAgent(row.id));
await Promise.all(all);
revalidatePath("/marketplace");
},
);
},
},
]}
/>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More