diff --git a/.github/workflows/platform-frontend-ci.yml b/.github/workflows/platform-frontend-ci.yml index d0edc7327d..fb7a55055e 100644 --- a/.github/workflows/platform-frontend-ci.yml +++ b/.github/workflows/platform-frontend-ci.yml @@ -235,13 +235,25 @@ jobs: - name: Run Playwright tests run: pnpm test:no-build + continue-on-error: false - - name: Upload Playwright artifacts - if: failure() + - name: Upload Playwright report + if: always() uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report + if-no-files-found: ignore + retention-days: 3 + + - name: Upload Playwright test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-test-results + path: test-results + if-no-files-found: ignore + retention-days: 3 - name: Print Final Docker Compose logs if: always() diff --git a/autogpt_platform/backend/backend/api/features/integrations/router.py b/autogpt_platform/backend/backend/api/features/integrations/router.py index 36585b14b5..00500dc8a8 100644 --- a/autogpt_platform/backend/backend/api/features/integrations/router.py +++ b/autogpt_platform/backend/backend/api/features/integrations/router.py @@ -171,6 +171,7 @@ async def callback( f"Successfully processed OAuth callback for user {user_id} " f"and provider {provider.value}" ) + return CredentialsMetaResponse( id=credentials.id, provider=credentials.provider, @@ -189,6 +190,7 @@ async def list_credentials( user_id: Annotated[str, Security(get_user_id)], ) -> list[CredentialsMetaResponse]: credentials = await creds_manager.store.get_all_creds(user_id) + return [ CredentialsMetaResponse( id=cred.id, @@ -211,6 +213,7 @@ async def list_credentials_by_provider( user_id: Annotated[str, Security(get_user_id)], ) -> list[CredentialsMetaResponse]: credentials = await creds_manager.store.get_creds_by_provider(user_id, provider) + return [ CredentialsMetaResponse( id=cred.id, @@ -826,6 +829,18 @@ async def list_providers() -> List[str]: return all_providers +@router.get("/providers/system", response_model=List[str]) +async def list_system_providers() -> List[str]: + """ + Get a list of providers that have platform credits (system credentials) available. + + These providers can be used without the user providing their own API keys. + """ + from backend.integrations.credentials_store import SYSTEM_PROVIDERS + + return list(SYSTEM_PROVIDERS) + + @router.get("/providers/names", response_model=ProviderNamesResponse) async def get_provider_names() -> ProviderNamesResponse: """ diff --git a/autogpt_platform/backend/backend/integrations/credentials_store.py b/autogpt_platform/backend/backend/integrations/credentials_store.py index 7d805913b2..68fa16b38d 100644 --- a/autogpt_platform/backend/backend/integrations/credentials_store.py +++ b/autogpt_platform/backend/backend/integrations/credentials_store.py @@ -245,6 +245,21 @@ DEFAULT_CREDENTIALS = [ webshare_proxy_credentials, ] +SYSTEM_CREDENTIAL_IDS = {cred.id for cred in DEFAULT_CREDENTIALS} + +# Set of providers that have system credentials available +SYSTEM_PROVIDERS = {cred.provider for cred in DEFAULT_CREDENTIALS} + + +def is_system_credential(credential_id: str) -> bool: + """Check if a credential ID belongs to a system-managed credential.""" + return credential_id in SYSTEM_CREDENTIAL_IDS + + +def is_system_provider(provider: str) -> bool: + """Check if a provider has system-managed credentials available.""" + return provider in SYSTEM_PROVIDERS + class IntegrationCredentialsStore: def __init__(self): diff --git a/autogpt_platform/frontend/CONTRIBUTING.md b/autogpt_platform/frontend/CONTRIBUTING.md index 048c088350..1b2b810986 100644 --- a/autogpt_platform/frontend/CONTRIBUTING.md +++ b/autogpt_platform/frontend/CONTRIBUTING.md @@ -708,10 +708,7 @@ export function CreateButton() { ## πŸ§ͺ Testing & Storybook -- End-to-end: [Playwright](https://playwright.dev/docs/intro) (`pnpm test`, `pnpm test-ui`) -- [Storybook](https://storybook.js.org/docs) for isolated UI development (`pnpm storybook` / `pnpm build-storybook`) -- For Storybook tests in CI, see [`@storybook/test-runner`](https://storybook.js.org/docs/writing-tests/test-runner) (`test-storybook:ci`) -- When changing components in `src/components`, update or add stories and visually verify in Storybook/Chromatic +- See `TESTING.md` for Playwright setup, E2E data seeding, and Storybook usage. --- diff --git a/autogpt_platform/frontend/README.md b/autogpt_platform/frontend/README.md index f4541cdd33..abea810fd2 100644 --- a/autogpt_platform/frontend/README.md +++ b/autogpt_platform/frontend/README.md @@ -5,6 +5,7 @@ This is the frontend for AutoGPT's next generation This project uses [**pnpm**](https://pnpm.io/) as the package manager via **corepack**. [Corepack](https://github.com/nodejs/corepack) is a Node.js tool that automatically manages package managers without requiring global installations. For architecture, conventions, data fetching, feature flags, design system usage, state management, and PR process, see [CONTRIBUTING.md](./CONTRIBUTING.md). +For Playwright and Storybook testing setup, see [TESTING.md](./TESTING.md). ### Prerequisites diff --git a/autogpt_platform/frontend/TESTING.md b/autogpt_platform/frontend/TESTING.md new file mode 100644 index 0000000000..2995295c96 --- /dev/null +++ b/autogpt_platform/frontend/TESTING.md @@ -0,0 +1,57 @@ +# Frontend Testing πŸ§ͺ + +## Quick Start (local) πŸš€ + +1. Start the backend + Supabase stack: + - From `autogpt_platform`: `docker compose --profile local up deps_backend -d` + - Or run the full stack: `docker compose up -d` +2. Seed rich E2E data (creates `test123@gmail.com` with library agents): + - From `autogpt_platform/backend`: `poetry run python test/e2e_test_data.py` +3. Run Playwright: + - From `autogpt_platform/frontend`: `pnpm test` or `pnpm test-ui` + +## How Playwright setup works 🎭 + +- Playwright runs from `frontend/playwright.config.ts` with a global setup step. +- The global setup creates a user pool via the real signup UI and stores it in `frontend/.auth/user-pool.json`. +- Most tests call `getTestUser()` (from `src/tests/utils/auth.ts`) which pulls a random user from that pool. + - these users do not contain library agents, it's user that just "signed up" on the platform, hence some tests to make use of users created via script (see below) with more data + +## Test users πŸ‘€ + +- **User pool (basic users)** + Created automatically by the Playwright global setup through `/signup`. + Used by `getTestUser()` in `src/tests/utils/auth.ts`. + +- **Rich user with library agents** + Created by `backend/test/e2e_test_data.py`. + Accessed via `getTestUserWithLibraryAgents()` in `src/tests/credentials/index.ts`. + +Use the rich user when a test needs existing library agents (e.g. `library.spec.ts`). + +## Resetting or wiping the DB πŸ” + +If you reset the Docker DB and logins start failing: + +1. Delete `frontend/.auth/user-pool.json` so the pool is regenerated. +2. Re-run the E2E data script to recreate the rich user + library agents: + - `poetry run python test/e2e_test_data.py` + +## Storybook πŸ“š + +## Flow diagram πŸ—ΊοΈ + +```mermaid +flowchart TD + A[Start Docker stack] --> B[Run e2e_test_data.py] + B --> C[Run Playwright tests] + C --> D[Global setup creates user pool] + D --> E{Test needs rich data?} + E -->|No| F[getTestUser from user pool] + E -->|Yes| G[getTestUserWithLibraryAgents] +``` + +- `pnpm storybook` – Run Storybook locally +- `pnpm build-storybook` – Build a static Storybook +- CI runner: `pnpm test-storybook` +- When changing components in `src/components`, update or add stories and verify in Storybook/Chromatic. diff --git a/autogpt_platform/frontend/next.config.mjs b/autogpt_platform/frontend/next.config.mjs index e4e4cdf544..bb4410039d 100644 --- a/autogpt_platform/frontend/next.config.mjs +++ b/autogpt_platform/frontend/next.config.mjs @@ -3,6 +3,13 @@ import { withSentryConfig } from "@sentry/nextjs"; /** @type {import('next').NextConfig} */ const nextConfig = { productionBrowserSourceMaps: true, + // Externalize OpenTelemetry packages to fix Turbopack HMR issues + serverExternalPackages: [ + "@opentelemetry/instrumentation", + "@opentelemetry/sdk-node", + "import-in-the-middle", + "require-in-the-middle", + ], experimental: { serverActions: { bodySizeLimit: "256mb", diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json index f881ebaf5b..0823400c87 100644 --- a/autogpt_platform/frontend/package.json +++ b/autogpt_platform/frontend/package.json @@ -32,6 +32,7 @@ "@hookform/resolvers": "5.2.2", "@next/third-parties": "15.4.6", "@phosphor-icons/react": "2.1.10", + "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", @@ -117,6 +118,7 @@ }, "devDependencies": { "@chromatic-com/storybook": "4.1.2", + "@opentelemetry/instrumentation": "0.209.0", "@playwright/test": "1.56.1", "@storybook/addon-a11y": "9.1.5", "@storybook/addon-docs": "9.1.5", @@ -140,6 +142,7 @@ "eslint": "8.57.1", "eslint-config-next": "15.5.7", "eslint-plugin-storybook": "9.1.5", + "import-in-the-middle": "2.0.2", "msw": "2.11.6", "msw-storybook-addon": "2.0.6", "orval": "7.13.0", @@ -147,7 +150,7 @@ "postcss": "8.5.6", "prettier": "3.6.2", "prettier-plugin-tailwindcss": "0.7.1", - "require-in-the-middle": "7.5.2", + "require-in-the-middle": "8.0.1", "storybook": "9.1.5", "tailwindcss": "3.4.17", "typescript": "5.9.3" @@ -157,5 +160,10 @@ "public" ] }, + "pnpm": { + "overrides": { + "@opentelemetry/instrumentation": "0.209.0" + } + }, "packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd" } diff --git a/autogpt_platform/frontend/pnpm-lock.yaml b/autogpt_platform/frontend/pnpm-lock.yaml index 4240d0d155..f503c23ea8 100644 --- a/autogpt_platform/frontend/pnpm-lock.yaml +++ b/autogpt_platform/frontend/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@opentelemetry/instrumentation': 0.209.0 + importers: .: @@ -20,6 +23,9 @@ importers: '@phosphor-icons/react': specifier: 2.1.10 version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-accordion': + specifier: 1.2.12 + version: 1.2.12(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-alert-dialog': specifier: 1.1.15 version: 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -270,6 +276,9 @@ importers: '@chromatic-com/storybook': specifier: 4.1.2 version: 4.1.2(storybook@9.1.5(@testing-library/dom@10.4.1)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(prettier@3.6.2)) + '@opentelemetry/instrumentation': + specifier: 0.209.0 + version: 0.209.0(@opentelemetry/api@1.9.0) '@playwright/test': specifier: 1.56.1 version: 1.56.1 @@ -339,6 +348,9 @@ importers: eslint-plugin-storybook: specifier: 9.1.5 version: 9.1.5(eslint@8.57.1)(storybook@9.1.5(@testing-library/dom@10.4.1)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(prettier@3.6.2))(typescript@5.9.3) + import-in-the-middle: + specifier: 2.0.2 + version: 2.0.2 msw: specifier: 2.11.6 version: 2.11.6(@types/node@24.10.0)(typescript@5.9.3) @@ -361,8 +373,8 @@ importers: specifier: 0.7.1 version: 0.7.1(prettier@3.6.2) require-in-the-middle: - specifier: 7.5.2 - version: 7.5.2 + specifier: 8.0.1 + version: 8.0.1 storybook: specifier: 9.1.5 version: 9.1.5(@testing-library/dom@10.4.1)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(prettier@3.6.2) @@ -1543,8 +1555,8 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@opentelemetry/api-logs@0.208.0': - resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + '@opentelemetry/api-logs@0.209.0': + resolution: {integrity: sha512-xomnUNi7TiAGtOgs0tb54LyrjRZLu9shJGGwkcN7NgtiPYOpNnKLkRJtzZvTjD/w6knSZH9sFZcUSUovYOPg6A==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.0': @@ -1695,8 +1707,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.7.0 - '@opentelemetry/instrumentation@0.208.0': - resolution: {integrity: sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==} + '@opentelemetry/instrumentation@0.209.0': + resolution: {integrity: sha512-Cwe863ojTCnFlxVuuhG7s6ODkAOzKsAEthKAcI4MDRYz1OmGWYnmSl4X2pbyS+hBxVTdvfZePfoEA01IjqcEyw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1810,6 +1822,19 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-alert-dialog@1.1.15': resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} peerDependencies: @@ -2631,7 +2656,7 @@ packages: '@opentelemetry/api': ^1.9.0 '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 || ^2.2.0 '@opentelemetry/core': ^1.30.1 || ^2.1.0 || ^2.2.0 - '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/instrumentation': 0.209.0 '@opentelemetry/resources': ^1.30.1 || ^2.1.0 || ^2.2.0 '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 || ^2.2.0 '@opentelemetry/semantic-conventions': ^1.37.0 @@ -4957,8 +4982,8 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@2.0.1: - resolution: {integrity: sha512-bruMpJ7xz+9jwGzrwEhWgvRrlKRYCRDBrfU+ur3FcasYXLJDxTruJ//8g2Noj+QFyRBeqbpj8Bhn4Fbw6HjvhA==} + import-in-the-middle@2.0.2: + resolution: {integrity: sha512-qet/hkGt3EbNGVtbDfPu0BM+tCqBS8wT1SYrstPaDKoWtshsC6licOemz7DVtpBEyvDNzo8UTBf9/GwWuSDZ9w==} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -6502,10 +6527,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@7.5.2: - resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} - engines: {node: '>=8.6.0'} - require-in-the-middle@8.0.1: resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} @@ -8716,7 +8737,7 @@ snapshots: '@open-draft/until@2.1.0': {} - '@opentelemetry/api-logs@0.208.0': + '@opentelemetry/api-logs@0.209.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -8735,7 +8756,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -8743,7 +8764,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@types/connect': 3.4.38 transitivePeerDependencies: @@ -8752,7 +8773,7 @@ snapshots: '@opentelemetry/instrumentation-dataloader@0.26.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -8760,7 +8781,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -8769,21 +8790,21 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color '@opentelemetry/instrumentation-generic-pool@0.52.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color '@opentelemetry/instrumentation-graphql@0.56.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -8791,7 +8812,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -8800,7 +8821,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 forwarded-parse: 2.1.2 transitivePeerDependencies: @@ -8809,7 +8830,7 @@ snapshots: '@opentelemetry/instrumentation-ioredis@0.56.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.38.2 transitivePeerDependencies: - supports-color @@ -8817,7 +8838,7 @@ snapshots: '@opentelemetry/instrumentation-kafkajs@0.18.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -8825,7 +8846,7 @@ snapshots: '@opentelemetry/instrumentation-knex@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -8834,7 +8855,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -8842,14 +8863,14 @@ snapshots: '@opentelemetry/instrumentation-lru-memoizer@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color '@opentelemetry/instrumentation-mongodb@0.61.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -8857,14 +8878,14 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color '@opentelemetry/instrumentation-mysql2@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: @@ -8873,7 +8894,7 @@ snapshots: '@opentelemetry/instrumentation-mysql@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@types/mysql': 2.15.27 transitivePeerDependencies: - supports-color @@ -8882,7 +8903,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) '@types/pg': 8.15.6 @@ -8893,7 +8914,7 @@ snapshots: '@opentelemetry/instrumentation-redis@0.57.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.38.2 '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -8902,7 +8923,7 @@ snapshots: '@opentelemetry/instrumentation-tedious@0.27.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color @@ -8911,16 +8932,16 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.209.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.208.0 - import-in-the-middle: 2.0.1 + '@opentelemetry/api-logs': 0.209.0 + import-in-the-middle: 2.0.2 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color @@ -9100,7 +9121,7 @@ snapshots: '@prisma/instrumentation@6.19.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -9108,6 +9129,23 @@ snapshots: '@radix-ui/primitive@1.1.3': {} + '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.17)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.17)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.17)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.17)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.17)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.17 + '@types/react-dom': 18.3.5(@types/react@18.3.17) + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -9932,19 +9970,19 @@ snapshots: - supports-color - webpack - '@sentry/node-core@10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)': + '@sentry/node-core@10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.209.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)': dependencies: '@apm-js-collab/tracing-hooks': 0.3.1 '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@sentry/core': 10.27.0 '@sentry/opentelemetry': 10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) - import-in-the-middle: 2.0.1 + import-in-the-middle: 2.0.2 transitivePeerDependencies: - supports-color @@ -9953,7 +9991,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.209.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-amqplib': 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-connect': 0.52.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-dataloader': 0.26.0(@opentelemetry/api@1.9.0) @@ -9981,9 +10019,9 @@ snapshots: '@opentelemetry/semantic-conventions': 1.38.0 '@prisma/instrumentation': 6.19.0(@opentelemetry/api@1.9.0) '@sentry/core': 10.27.0 - '@sentry/node-core': 10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) + '@sentry/node-core': 10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.209.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) '@sentry/opentelemetry': 10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) - import-in-the-middle: 2.0.1 + import-in-the-middle: 2.0.2 minimatch: 9.0.5 transitivePeerDependencies: - supports-color @@ -12792,7 +12830,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@2.0.1: + import-in-the-middle@2.0.2: dependencies: acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) @@ -14631,14 +14669,6 @@ snapshots: require-from-string@2.0.2: {} - require-in-the-middle@7.5.2: - dependencies: - debug: 4.4.3 - module-details-from-path: 1.0.4 - resolve: 1.22.11 - transitivePeerDependencies: - - supports-color - require-in-the-middle@8.0.1: dependencies: debug: 4.4.3 diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/components/AgentOnboardingCredentials/AgentOnboardingCredentials.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/components/AgentOnboardingCredentials/AgentOnboardingCredentials.tsx index 3176ec7f70..72e296fd88 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/components/AgentOnboardingCredentials/AgentOnboardingCredentials.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/components/AgentOnboardingCredentials/AgentOnboardingCredentials.tsx @@ -1,4 +1,4 @@ -import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput"; import { CredentialsMetaInput } from "@/app/api/__generated__/models/credentialsMetaInput"; import { GraphMeta } from "@/app/api/__generated__/models/graphMeta"; import { useState } from "react"; diff --git a/autogpt_platform/frontend/src/app/(platform)/auth/integrations/setup-wizard/page.tsx b/autogpt_platform/frontend/src/app/(platform)/auth/integrations/setup-wizard/page.tsx index 3372772c89..f7d4935907 100644 --- a/autogpt_platform/frontend/src/app/(platform)/auth/integrations/setup-wizard/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/auth/integrations/setup-wizard/page.tsx @@ -1,22 +1,22 @@ "use client"; -import Image from "next/image"; -import Link from "next/link"; -import { useSearchParams } from "next/navigation"; -import { useState, useMemo, useRef } from "react"; -import { AuthCard } from "@/components/auth/AuthCard"; -import { Text } from "@/components/atoms/Text/Text"; +import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput"; +import { useGetOauthGetOauthAppInfo } from "@/app/api/__generated__/endpoints/oauth/oauth"; +import { okData } from "@/app/api/helpers"; import { Button } from "@/components/atoms/Button/Button"; +import { Text } from "@/components/atoms/Text/Text"; +import { AuthCard } from "@/components/auth/AuthCard"; import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard"; -import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs"; import type { BlockIOCredentialsSubSchema, CredentialsMetaInput, CredentialsType, } from "@/lib/autogpt-server-api"; import { CheckIcon, CircleIcon } from "@phosphor-icons/react"; -import { useGetOauthGetOauthAppInfo } from "@/app/api/__generated__/endpoints/oauth/oauth"; -import { okData } from "@/app/api/helpers"; +import Image from "next/image"; +import Link from "next/link"; +import { useSearchParams } from "next/navigation"; +import { useMemo, useRef, useState } from "react"; // All credential types - we accept any type of credential const ALL_CREDENTIAL_TYPES: CredentialsType[] = [ diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeInputs.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeInputs.tsx index ab6ea8b94b..36df180c8c 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeInputs.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeInputs.tsx @@ -3,7 +3,7 @@ import { CustomNodeData, } from "@/app/(platform)/build/components/legacy-builder/CustomNode/CustomNode"; import { NodeTableInput } from "@/app/(platform)/build/components/legacy-builder/NodeTableInput"; -import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput"; import { Button } from "@/components/__legacy__/ui/button"; import { Calendar } from "@/components/__legacy__/ui/calendar"; import { LocalValuedInput } from "@/components/__legacy__/ui/input"; diff --git a/autogpt_platform/frontend/src/app/(platform)/chat/components/ChatCredentialsSetup/ChatCredentialsSetup.tsx b/autogpt_platform/frontend/src/app/(platform)/chat/components/ChatCredentialsSetup/ChatCredentialsSetup.tsx index 1f70f7740f..3868e17a10 100644 --- a/autogpt_platform/frontend/src/app/(platform)/chat/components/ChatCredentialsSetup/ChatCredentialsSetup.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/chat/components/ChatCredentialsSetup/ChatCredentialsSetup.tsx @@ -1,4 +1,4 @@ -import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput"; import { Card } from "@/components/atoms/Card/Card"; import { Text } from "@/components/atoms/Text/Text"; import type { BlockIOCredentialsSubSchema } from "@/lib/autogpt-server-api"; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx index 3768a0d150..e184bc59f1 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx @@ -1,32 +1,31 @@ "use client"; import { Button } from "@/components/atoms/Button/Button"; +import { PublishAgentModal } from "@/components/contextual/PublishAgentModal/PublishAgentModal"; import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs"; import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard"; import { cn } from "@/lib/utils"; import { PlusIcon } from "@phosphor-icons/react"; import { useEffect, useState } from "react"; -import { RunAgentModal } from "./components/modals/RunAgentModal/RunAgentModal"; -import { useMarketplaceUpdate } from "./hooks/useMarketplaceUpdate"; import { AgentVersionChangelog } from "./components/AgentVersionChangelog"; -import { MarketplaceBanners } from "@/components/contextual/MarketplaceBanners/MarketplaceBanners"; -import { PublishAgentModal } from "@/components/contextual/PublishAgentModal/PublishAgentModal"; -import { AgentSettingsButton } from "./components/other/AgentSettingsButton"; +import { AgentSettingsModal } from "./components/modals/AgentSettingsModal/AgentSettingsModal"; +import { RunAgentModal } from "./components/modals/RunAgentModal/RunAgentModal"; import { AgentRunsLoading } from "./components/other/AgentRunsLoading"; import { EmptySchedules } from "./components/other/EmptySchedules"; import { EmptyTasks } from "./components/other/EmptyTasks"; import { EmptyTemplates } from "./components/other/EmptyTemplates"; import { EmptyTriggers } from "./components/other/EmptyTriggers"; +import { MarketplaceBanners } from "./components/other/MarketplaceBanners"; import { SectionWrap } from "./components/other/SectionWrap"; import { LoadingSelectedContent } from "./components/selected-views/LoadingSelectedContent"; import { SelectedRunView } from "./components/selected-views/SelectedRunView/SelectedRunView"; import { SelectedScheduleView } from "./components/selected-views/SelectedScheduleView/SelectedScheduleView"; -import { SelectedSettingsView } from "./components/selected-views/SelectedSettingsView/SelectedSettingsView"; import { SelectedTemplateView } from "./components/selected-views/SelectedTemplateView/SelectedTemplateView"; import { SelectedTriggerView } from "./components/selected-views/SelectedTriggerView/SelectedTriggerView"; import { SelectedViewLayout } from "./components/selected-views/SelectedViewLayout"; import { SidebarRunsList } from "./components/sidebar/SidebarRunsList/SidebarRunsList"; import { AGENT_LIBRARY_SECTION_PADDING_X } from "./helpers"; +import { useMarketplaceUpdate } from "./hooks/useMarketplaceUpdate"; import { useNewAgentLibraryView } from "./useNewAgentLibraryView"; export function NewAgentLibraryView() { @@ -45,7 +44,6 @@ export function NewAgentLibraryView() { handleSelectRun, handleCountsChange, handleClearSelectedRun, - handleSelectSettings, onRunInitiated, onTriggerSetup, onScheduleCreated, @@ -137,13 +135,16 @@ export function NewAgentLibraryView() { return ( <>
-
- +
+
+ + +
-
- - New task - - } - agent={agent} - onRunCreated={onRunInitiated} - onScheduleCreated={onScheduleCreated} - onTriggerSetup={onTriggerSetup} - initialInputValues={activeTemplate?.inputs} - initialInputCredentials={activeTemplate?.credentials} - /> - -
+ + New task + + } + agent={agent} + onRunCreated={onRunInitiated} + onScheduleCreated={onScheduleCreated} + onTriggerSetup={onTriggerSetup} + initialInputValues={activeTemplate?.inputs} + initialInputCredentials={activeTemplate?.credentials} + />
{activeItem ? ( - activeItem === "settings" ? ( - - ) : activeTab === "scheduled" ? ( + activeTab === "scheduled" ? ( ) ) : sidebarLoading ? ( diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/AgentInputsReadOnly/AgentInputsReadOnly.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/AgentInputsReadOnly/AgentInputsReadOnly.tsx index bc9918c2bb..a66c6ecfc0 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/AgentInputsReadOnly/AgentInputsReadOnly.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/AgentInputsReadOnly/AgentInputsReadOnly.tsx @@ -3,7 +3,8 @@ import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { Text } from "@/components/atoms/Text/Text"; import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; -import { CredentialsInput } from "../CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "../CredentialsInputs/CredentialsInput"; +import { isSystemCredential } from "../CredentialsInputs/helpers"; import { RunAgentInputs } from "../RunAgentInputs/RunAgentInputs"; import { getAgentCredentialsFields, getAgentInputFields } from "./helpers"; @@ -71,6 +72,7 @@ export function AgentInputsReadOnly({ {credentialFieldEntries.map(([key, inputSubSchema]) => { const credential = credentialInputs![key]; if (!credential) return null; + if (isSystemCredential(credential)) return null; return ( void; +} + +export function AgentSettingsModal({ + agent, + controlledOpen, + onOpenChange, +}: Props) { + const [internalIsOpen, setInternalIsOpen] = useState(false); + const isOpen = controlledOpen !== undefined ? controlledOpen : internalIsOpen; + + function setIsOpen(open: boolean) { + if (onOpenChange) { + onOpenChange(open); + } else { + setInternalIsOpen(open); + } + } + + const { currentSafeMode, isPending, hasHITLBlocks, handleToggle } = + useAgentSafeMode(agent); + + if (!hasHITLBlocks) return null; + + return ( + + {controlledOpen === undefined && ( + + + + )} + +
+
+
+
+ Require human approval + + The agent will pause and wait for your review before + continuing + +
+ +
+
+
+
+
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput.tsx similarity index 52% rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs.tsx rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput.tsx index a0f9376aa2..7fc88c1488 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput.tsx @@ -1,6 +1,4 @@ -import { Button } from "@/components/atoms/Button/Button"; import { Text } from "@/components/atoms/Text/Text"; -import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip"; import { BlockIOCredentialsSubSchema, CredentialsMetaInput, @@ -8,13 +6,11 @@ import { import { cn } from "@/lib/utils"; import { toDisplayName } from "@/providers/agent-credentials/helper"; import { APIKeyCredentialsModal } from "./components/APIKeyCredentialsModal/APIKeyCredentialsModal"; -import { CredentialRow } from "./components/CredentialRow/CredentialRow"; -import { CredentialsSelect } from "./components/CredentialsSelect/CredentialsSelect"; -import { DeleteConfirmationModal } from "./components/DeleteConfirmationModal/DeleteConfirmationModal"; +import { CredentialsFlatView } from "./components/CredentialsFlatView/CredentialsFlatView"; import { HostScopedCredentialsModal } from "./components/HotScopedCredentialsModal/HotScopedCredentialsModal"; import { OAuthFlowWaitingModal } from "./components/OAuthWaitingModal/OAuthWaitingModal"; import { PasswordCredentialsModal } from "./components/PasswordCredentialsModal/PasswordCredentialsModal"; -import { getCredentialDisplayName } from "./helpers"; +import { isSystemCredential } from "./helpers"; import { CredentialsInputState, useCredentialsInput, @@ -72,115 +68,53 @@ export function CredentialsInput({ supportsOAuth2, supportsUserPassword, supportsHostScoped, - credentialsToShow, + userCredentials, + systemCredentials, oAuthError, isAPICredentialsModalOpen, isUserPasswordCredentialsModalOpen, isHostScopedCredentialsModalOpen, isOAuth2FlowInProgress, oAuthPopupController, - credentialToDelete, - deleteCredentialsMutation, actionButtonText, setAPICredentialsModalOpen, setUserPasswordCredentialsModalOpen, setHostScopedCredentialsModalOpen, - setCredentialToDelete, handleActionButtonClick, handleCredentialSelect, - handleDeleteCredential, - handleDeleteConfirm, } = hookData; const displayName = toDisplayName(provider); - const hasCredentialsToShow = credentialsToShow.length > 0; + const selectedCredentialIsSystem = + selectedCredential && isSystemCredential(selectedCredential); + + const allCredentials = [...userCredentials, ...systemCredentials]; + + if (readOnly && selectedCredentialIsSystem) { + return null; + } return (
- {showTitle && ( -
- - {displayName} credentials - {isOptional && ( - - (optional) - - )} - - {schema.description && ( - - )} -
- )} - - {hasCredentialsToShow ? ( - <> - {(credentialsToShow.length > 1 || isOptional) && !readOnly ? ( - onSelectCredential(undefined)} - readOnly={readOnly} - allowNone={isOptional} - variant={variant} - /> - ) : ( -
- {credentialsToShow.map((credential) => { - return ( - handleCredentialSelect(credential.id)} - onDelete={() => - handleDeleteCredential({ - id: credential.id, - title: getCredentialDisplayName( - credential, - displayName, - ), - }) - } - readOnly={readOnly} - /> - ); - })} -
- )} - {!readOnly && ( - - )} - - ) : ( - !readOnly && ( - - ) - )} + onSelectCredential(undefined)} + onAddCredential={handleActionButtonClick} + actionButtonText={actionButtonText} + isOptional={isOptional} + showTitle={showTitle} + readOnly={readOnly} + variant={variant} + /> {!readOnly && ( <> - {supportsApiKey ? ( + {supportsApiKey && ( - ) : null} - {supportsOAuth2 ? ( + )} + {supportsOAuth2 && ( oAuthPopupController?.abort("canceled")} providerName={providerName} /> - ) : null} - {supportsUserPassword ? ( + )} + {supportsUserPassword && ( - ) : null} - {supportsHostScoped ? ( + )} + {supportsHostScoped && ( - ) : null} + )} - {oAuthError ? ( + {oAuthError && ( Error: {oAuthError} - ) : null} - - setCredentialToDelete(null)} - onConfirm={handleDeleteConfirm} - /> + )} )}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/APIKeyCredentialsModal.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/APIKeyCredentialsModal.tsx index 0180c4ebf9..90f6c0ff70 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/APIKeyCredentialsModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/APIKeyCredentialsModal.tsx @@ -1,11 +1,11 @@ -import { Input } from "@/components/atoms/Input/Input"; -import { Button } from "@/components/atoms/Button/Button"; -import { Dialog } from "@/components/molecules/Dialog/Dialog"; import { Form, FormDescription, FormField, } from "@/components/__legacy__/ui/form"; +import { Button } from "@/components/atoms/Button/Button"; +import { Input } from "@/components/atoms/Input/Input"; +import { Dialog } from "@/components/molecules/Dialog/Dialog"; import { BlockIOCredentialsSubSchema, CredentialsMetaInput, @@ -60,7 +60,23 @@ export function APIKeyCredentialsModal({ )}
- + + ( + + )} + /> @@ -90,20 +105,7 @@ export function APIKeyCredentialsModal({ )} /> - ( - - )} - /> + { + const value = e.target.value; + if (value) { + const dateTime = new Date(value); + dateTime.setHours(0, 0, 0, 0); + const year = dateTime.getFullYear(); + const month = String(dateTime.getMonth() + 1).padStart( + 2, + "0", + ); + const day = String(dateTime.getDate()).padStart(2, "0"); + const normalizedValue = `${year}-${month}-${day}T00:00`; + field.onChange(normalizedValue); + } else { + field.onChange(value); + } + }} + onBlur={field.onBlur} + name={field.name} /> )} /> - diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/useAPIKeyCredentialsModal.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/useAPIKeyCredentialsModal.ts index 391633bed5..72599a2e79 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/useAPIKeyCredentialsModal.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/APIKeyCredentialsModal/useAPIKeyCredentialsModal.ts @@ -1,11 +1,11 @@ -import { z } from "zod"; -import { useForm, type UseFormReturn } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; import useCredentials from "@/hooks/useCredentials"; import { BlockIOCredentialsSubSchema, CredentialsMetaInput, } from "@/lib/autogpt-server-api/types"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm, type UseFormReturn } from "react-hook-form"; +import { z } from "zod"; export type APIKeyFormValues = { apiKey: string; @@ -40,12 +40,24 @@ export function useAPIKeyCredentialsModal({ expiresAt: z.string().optional(), }); + function getDefaultExpirationDate(): string { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setHours(0, 0, 0, 0); + const year = tomorrow.getFullYear(); + const month = String(tomorrow.getMonth() + 1).padStart(2, "0"); + const day = String(tomorrow.getDate()).padStart(2, "0"); + const hours = String(tomorrow.getHours()).padStart(2, "0"); + const minutes = String(tomorrow.getMinutes()).padStart(2, "0"); + return `${year}-${month}-${day}T${hours}:${minutes}`; + } + const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { apiKey: "", title: "", - expiresAt: "", + expiresAt: getDefaultExpirationDate(), }, }); diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialRow/CredentialRow.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialRow/CredentialRow.tsx index 2d0358aacb..dc69c34d93 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialRow/CredentialRow.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialRow/CredentialRow.tsx @@ -7,7 +7,8 @@ import { DropdownMenuTrigger, } from "@/components/molecules/DropdownMenu/DropdownMenu"; import { cn } from "@/lib/utils"; -import { CaretDown, DotsThreeVertical } from "@phosphor-icons/react"; +import { CaretDownIcon, DotsThreeVertical } from "@phosphor-icons/react"; +import { useEffect, useRef, useState } from "react"; import { fallbackIcon, getCredentialDisplayName, @@ -26,7 +27,7 @@ type CredentialRowProps = { provider: string; displayName: string; onSelect: () => void; - onDelete: () => void; + onDelete?: () => void; readOnly?: boolean; showCaret?: boolean; asSelectTrigger?: boolean; @@ -47,11 +48,32 @@ export function CredentialRow({ }: CredentialRowProps) { const ProviderIcon = providerIcons[provider] || fallbackIcon; const isNodeVariant = variant === "node"; + const containerRef = useRef(null); + const [showMaskedKey, setShowMaskedKey] = useState(true); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const width = entry.contentRect.width; + setShowMaskedKey(width >= 360); + } + }); + + resizeObserver.observe(container); + + return () => { + resizeObserver.disconnect(); + }; + }, []); return (
{getCredentialDisplayName(credential, displayName)} - {!(asSelectTrigger && isNodeVariant) && ( + {!(asSelectTrigger && isNodeVariant) && showMaskedKey && ( {"*".repeat(MASKED_KEY_LENGTH)} )}
- {showCaret && !asSelectTrigger && ( - + {(showCaret || (asSelectTrigger && !readOnly)) && ( + )} - {!readOnly && !showCaret && !asSelectTrigger && ( + {!readOnly && !showCaret && !asSelectTrigger && onDelete && ( + + )} + + {hasSystemCredentials && ( + + + +
+ System credentials +
+
+ +
+ {showTitle && ( +
+ + {displayName} credentials + {isOptional && ( + + (optional) + + )} + + {schema.description && ( + + )} +
+ )} + {credentialsInAccordion.length > 0 && ( + + )} + {isSystemProvider && ( + + )} +
+
+
+
+ )} + + {!showUserCredentialsOutsideAccordion && !isSystemProvider && ( + + )} + + ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsFlatView/CredentialsFlatView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsFlatView/CredentialsFlatView.tsx new file mode 100644 index 0000000000..4d220a5359 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsFlatView/CredentialsFlatView.tsx @@ -0,0 +1,134 @@ +import { Button } from "@/components/atoms/Button/Button"; +import { Text } from "@/components/atoms/Text/Text"; +import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip"; +import { + BlockIOCredentialsSubSchema, + CredentialsMetaInput, +} from "@/lib/autogpt-server-api/types"; +import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; +import { CredentialRow } from "../CredentialRow/CredentialRow"; +import { CredentialsSelect } from "../CredentialsSelect/CredentialsSelect"; + +type Credential = { + id: string; + title?: string; + username?: string; + type: string; + provider: string; +}; + +type Props = { + schema: BlockIOCredentialsSubSchema; + provider: string; + displayName: string; + credentials: Credential[]; + selectedCredential?: CredentialsMetaInput; + actionButtonText: string; + isOptional: boolean; + showTitle: boolean; + readOnly: boolean; + variant: "default" | "node"; + onSelectCredential: (credentialId: string) => void; + onClearCredential: () => void; + onAddCredential: () => void; +}; + +export function CredentialsFlatView({ + schema, + provider, + displayName, + credentials, + selectedCredential, + actionButtonText, + isOptional, + showTitle, + readOnly, + variant, + onSelectCredential, + onClearCredential, + onAddCredential, +}: Props) { + const hasCredentials = credentials.length > 0; + + return ( + <> + {showTitle && ( +
+ + + {displayName} credentials + {isOptional && ( + + (optional) + + )} + {!isOptional && !selectedCredential && ( + + + required + + )} + + + {schema.description && ( + + )} +
+ )} + + {hasCredentials ? ( + <> + {(credentials.length > 1 || isOptional) && !readOnly ? ( + + ) : ( +
+ {credentials.map((credential) => ( + onSelectCredential(credential.id)} + readOnly={readOnly} + /> + ))} +
+ )} + {!readOnly && ( + + )} + + ) : ( + !readOnly && ( + + ) + )} + + ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx index 6e1ec2afb1..18e772dd00 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx @@ -1,14 +1,4 @@ -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/__legacy__/ui/select"; -import { Text } from "@/components/atoms/Text/Text"; -import { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; -import { cn } from "@/lib/utils"; -import { useEffect } from "react"; +import { CredentialsMetaInput } from "@/app/api/__generated__/models/credentialsMetaInput"; import { getCredentialDisplayName } from "../../helpers"; import { CredentialRow } from "../CredentialRow/CredentialRow"; @@ -42,76 +32,77 @@ export function CredentialsSelect({ allowNone = true, variant = "default", }: Props) { - // Auto-select first credential if none is selected (only if allowNone is false) - useEffect(() => { - if (!allowNone && !selectedCredentials && credentials.length > 0) { - onSelectCredential(credentials[0].id); - } - }, [allowNone, selectedCredentials, credentials, onSelectCredential]); - - const handleValueChange = (value: string) => { + function handleValueChange(e: React.ChangeEvent) { + const value = e.target.value; if (value === "__none__") { onClearCredential?.(); } else { onSelectCredential(value); } - }; + } + + const selectedCredential = selectedCredentials + ? credentials.find((c) => c.id === selectedCredentials.id) + : null; + + const displayCredential = selectedCredential + ? { + id: selectedCredential.id, + title: selectedCredential.title, + username: selectedCredential.username, + type: selectedCredential.type, + provider: selectedCredential.provider, + } + : allowNone + ? { + id: "__none__", + title: "None (skip this credential)", + type: "none", + provider: provider, + } + : { + id: "__placeholder__", + title: "Select credential", + type: "placeholder", + provider: provider, + }; return (
- - {selectedCredentials ? ( - - {}} - onDelete={() => {}} - readOnly={readOnly} - asSelectTrigger={true} - variant={variant} - /> - + {allowNone ? ( + ) : ( - - )} - - - {allowNone && ( - -
- - None (skip this credential) - -
-
+ )} {credentials.map((credential) => ( - -
- - {getCredentialDisplayName(credential, displayName)} - -
-
+ ))} -
- + +
+ {}} + onDelete={() => {}} + readOnly={readOnly} + asSelectTrigger={true} + variant={variant} + /> +
+
); } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts index 4cca825747..ef965d5382 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts @@ -99,4 +99,30 @@ export function getCredentialDisplayName( } export const OAUTH_TIMEOUT_MS = 5 * 60 * 1000; -export const MASKED_KEY_LENGTH = 30; +export const MASKED_KEY_LENGTH = 15; + +export function isSystemCredential(credential: { + title?: string | null; + is_system?: boolean; +}): boolean { + if (credential.is_system === true) return true; + if (!credential.title) return false; + const titleLower = credential.title.toLowerCase(); + return ( + titleLower.includes("system") || + titleLower.startsWith("use credits for") || + titleLower.includes("use credits") + ); +} + +export function filterSystemCredentials< + T extends { title?: string; is_system?: boolean }, +>(credentials: T[]): T[] { + return credentials.filter((cred) => !isSystemCredential(cred)); +} + +export function getSystemCredentials< + T extends { title?: string; is_system?: boolean }, +>(credentials: T[]): T[] { + return credentials.filter((cred) => isSystemCredential(cred)); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/useCredentialsInput.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/useCredentialsInput.ts index c780ffeffc..8876ddcba9 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/useCredentialsInput.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/useCredentialsInput.ts @@ -6,9 +6,11 @@ import { CredentialsMetaInput, } from "@/lib/autogpt-server-api/types"; import { useQueryClient } from "@tanstack/react-query"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { + filterSystemCredentials, getActionButtonText, + getSystemCredentials, OAUTH_TIMEOUT_MS, OAuthPopupResultMessage, } from "./helpers"; @@ -54,6 +56,7 @@ export function useCredentialsInput({ const api = useBackendAPI(); const queryClient = useQueryClient(); const credentials = useCredentials(schema, siblingInputs); + const hasAttemptedAutoSelect = useRef(false); const deleteCredentialsMutation = useDeleteV1DeleteCredentials({ mutation: { @@ -82,38 +85,51 @@ export function useCredentialsInput({ useEffect(() => { if (readOnly) return; if (!credentials || !("savedCredentials" in credentials)) return; + const availableCreds = credentials.savedCredentials; if ( selectedCredential && - !credentials.savedCredentials.some((c) => c.id === selectedCredential.id) + !availableCreds.some((c) => c.id === selectedCredential.id) ) { onSelectCredential(undefined); + // Reset auto-selection flag so it can run again after unsetting invalid credential + hasAttemptedAutoSelect.current = false; } }, [credentials, selectedCredential, onSelectCredential, readOnly]); - // The available credential, if there is only one - const singleCredential = useMemo(() => { - if (!credentials || !("savedCredentials" in credentials)) { - return null; - } - - return credentials.savedCredentials.length === 1 - ? credentials.savedCredentials[0] - : null; - }, [credentials]); - - // Auto-select the one available credential (only if not optional) + // Auto-select the first available credential on initial mount + // Once a user has made a selection, we don't override it useEffect(() => { if (readOnly) return; - if (isOptional) return; // Don't auto-select when credential is optional - if (singleCredential && !selectedCredential) { - onSelectCredential(singleCredential); + if (!credentials || !("savedCredentials" in credentials)) return; + + // If already selected, don't auto-select + if (selectedCredential?.id) return; + + // Only attempt auto-selection once + if (hasAttemptedAutoSelect.current) return; + hasAttemptedAutoSelect.current = true; + + // If optional, don't auto-select (user can choose "None") + if (isOptional) return; + + const savedCreds = credentials.savedCredentials; + + // Auto-select the first credential if any are available + if (savedCreds.length > 0) { + const cred = savedCreds[0]; + onSelectCredential({ + id: cred.id, + type: cred.type, + provider: credentials.provider, + title: (cred as any).title, + }); } }, [ - singleCredential, - selectedCredential, - onSelectCredential, + credentials, + selectedCredential?.id, readOnly, isOptional, + onSelectCredential, ]); if ( @@ -135,8 +151,13 @@ export function useCredentialsInput({ supportsHostScoped, savedCredentials, oAuthCallback, + isSystemProvider, } = credentials; + // Split credentials into user and system + const userCredentials = filterSystemCredentials(savedCredentials); + const systemCredentials = getSystemCredentials(savedCredentials); + async function handleOAuthLogin() { setOAuthError(null); const { login_url, state_token } = await api.oAuthLogin( @@ -291,7 +312,10 @@ export function useCredentialsInput({ supportsOAuth2, supportsUserPassword, supportsHostScoped, - credentialsToShow: savedCredentials, + isSystemProvider, + userCredentials, + systemCredentials, + allCredentials: savedCredentials, selectedCredential, oAuthError, isAPICredentialsModalOpen, @@ -306,7 +330,7 @@ export function useCredentialsInput({ supportsApiKey, supportsUserPassword, supportsHostScoped, - savedCredentials.length > 0, + userCredentials.length > 0, ), setAPICredentialsModalOpen, setUserPasswordCredentialsModalOpen, diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx index e53f31a349..cd0c666be6 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx @@ -12,7 +12,7 @@ import { TooltipTrigger, } from "@/components/atoms/Tooltip/BaseTooltip"; import { Dialog } from "@/components/molecules/Dialog/Dialog"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { ScheduleAgentModal } from "../ScheduleAgentModal/ScheduleAgentModal"; import { ModalHeader } from "./components/ModalHeader/ModalHeader"; import { ModalRunSection } from "./components/ModalRunSection/ModalRunSection"; @@ -82,6 +82,8 @@ export function RunAgentModal({ }); const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false); + const [hasOverflow, setHasOverflow] = useState(false); + const contentRef = useRef(null); const hasAnySetupFields = Object.keys(agentInputFields || {}).length > 0 || @@ -89,6 +91,43 @@ export function RunAgentModal({ const isTriggerRunType = defaultRunType.includes("trigger"); + useEffect(() => { + if (!isOpen) return; + + function checkOverflow() { + if (!contentRef.current) return; + const scrollableParent = contentRef.current + .closest("[data-dialog-content]") + ?.querySelector('[class*="overflow-y-auto"]'); + if (scrollableParent) { + setHasOverflow( + scrollableParent.scrollHeight > scrollableParent.clientHeight, + ); + } + } + + const timeoutId = setTimeout(checkOverflow, 100); + const resizeObserver = new ResizeObserver(checkOverflow); + if (contentRef.current) { + const scrollableParent = contentRef.current + .closest("[data-dialog-content]") + ?.querySelector('[class*="overflow-y-auto"]'); + if (scrollableParent) { + resizeObserver.observe(scrollableParent); + } + } + + return () => { + clearTimeout(timeoutId); + resizeObserver.disconnect(); + }; + }, [ + isOpen, + hasAnySetupFields, + agentInputFields, + agentCredentialsInputFields, + ]); + function handleInputChange(key: string, value: string) { setInputValues((prev) => ({ ...prev, @@ -134,91 +173,97 @@ export function RunAgentModal({ > {triggerSlot} - {/* Header */} - +
+
+ {/* Header */} + - {/* Content */} - {hasAnySetupFields ? ( -
- - - + {/* Content */} + {hasAnySetupFields ? ( +
+ + + +
+ ) : null}
- ) : null} - -
- {isTriggerRunType ? null : !allRequiredInputsAreSet ? ( - - - - - - - - -

- Please set up all required inputs and credentials before - scheduling -

-
-
-
- ) : ( - - )} - +
+ {isTriggerRunType ? null : !allRequiredInputsAreSet ? ( + + + + + + + + +

+ Please set up all required inputs and credentials + before scheduling +

+
+
+
+ ) : ( + + )} + +
+ -
- -
+ +
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/CredentialsGroupedView/CredentialsGroupedView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/CredentialsGroupedView/CredentialsGroupedView.tsx new file mode 100644 index 0000000000..05b2966af7 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/CredentialsGroupedView/CredentialsGroupedView.tsx @@ -0,0 +1,181 @@ +import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/molecules/Accordion/Accordion"; +import { CredentialsProvidersContext } from "@/providers/agent-credentials/credentials-provider"; +import { SlidersHorizontal } from "@phosphor-icons/react"; +import { useContext, useEffect, useMemo, useRef } from "react"; +import { useRunAgentModalContext } from "../../context"; +import { + areSystemCredentialProvidersLoading, + CredentialField, + findSavedCredentialByProviderAndType, + hasMissingRequiredSystemCredentials, + splitCredentialFieldsBySystem, +} from "../helpers"; + +type Props = { + credentialFields: CredentialField[]; + requiredCredentials: Set; +}; + +export function CredentialsGroupedView({ + credentialFields, + requiredCredentials, +}: Props) { + const allProviders = useContext(CredentialsProvidersContext); + const { inputCredentials, setInputCredentialsValue, inputValues } = + useRunAgentModalContext(); + + const { userCredentialFields, systemCredentialFields } = useMemo( + () => + splitCredentialFieldsBySystem( + credentialFields, + allProviders, + inputCredentials, + ), + [credentialFields, allProviders, inputCredentials], + ); + + const hasSystemCredentials = systemCredentialFields.length > 0; + const hasUserCredentials = userCredentialFields.length > 0; + const hasAttemptedAutoSelect = useRef(false); + + const isLoadingProviders = useMemo( + () => + areSystemCredentialProvidersLoading(systemCredentialFields, allProviders), + [systemCredentialFields, allProviders], + ); + + const hasMissingSystemCredentials = useMemo(() => { + if (isLoadingProviders) return false; + return hasMissingRequiredSystemCredentials( + systemCredentialFields, + requiredCredentials, + inputCredentials, + allProviders, + ); + }, [ + isLoadingProviders, + systemCredentialFields, + requiredCredentials, + inputCredentials, + allProviders, + ]); + + useEffect(() => { + if (hasAttemptedAutoSelect.current) return; + if (!hasSystemCredentials) return; + if (isLoadingProviders) return; + + for (const [key, schema] of systemCredentialFields) { + const alreadySelected = inputCredentials?.[key]; + const isRequired = requiredCredentials.has(key); + if (alreadySelected || !isRequired) continue; + + const providerNames = schema.credentials_provider || []; + const credentialTypes = schema.credentials_types || []; + const requiredScopes = schema.credentials_scopes; + const savedCredential = findSavedCredentialByProviderAndType( + providerNames, + credentialTypes, + requiredScopes, + allProviders, + ); + + if (savedCredential) { + setInputCredentialsValue(key, { + id: savedCredential.id, + provider: savedCredential.provider, + type: savedCredential.type, + title: (savedCredential as { title?: string }).title, + }); + } + } + + hasAttemptedAutoSelect.current = true; + }, [ + allProviders, + hasSystemCredentials, + systemCredentialFields, + requiredCredentials, + inputCredentials, + setInputCredentialsValue, + isLoadingProviders, + ]); + + return ( +
+ {hasUserCredentials && ( + <> + {userCredentialFields.map( + ([key, inputSubSchema]: CredentialField) => { + const selectedCred = inputCredentials?.[key]; + + return ( + { + setInputCredentialsValue(key, value); + }} + siblingInputs={inputValues} + isOptional={!requiredCredentials.has(key)} + /> + ); + }, + )} + + )} + + {hasSystemCredentials && ( + + + +
+ System credentials + {hasMissingSystemCredentials && ( + (missing) + )} +
+
+ +
+ {systemCredentialFields.map( + ([key, inputSubSchema]: CredentialField) => { + const selectedCred = inputCredentials?.[key]; + + return ( + { + setInputCredentialsValue(key, value); + }} + siblingInputs={inputValues} + isOptional={!requiredCredentials.has(key)} + /> + ); + }, + )} +
+
+
+
+ )} +
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/ModalRunSection/ModalRunSection.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/ModalRunSection/ModalRunSection.tsx index aba4caee7a..7660de7c15 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/ModalRunSection/ModalRunSection.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/ModalRunSection/ModalRunSection.tsx @@ -1,8 +1,9 @@ -import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs"; import { Input } from "@/components/atoms/Input/Input"; import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip"; +import { useMemo } from "react"; import { RunAgentInputs } from "../../../RunAgentInputs/RunAgentInputs"; import { useRunAgentModalContext } from "../../context"; +import { CredentialsGroupedView } from "../CredentialsGroupedView/CredentialsGroupedView"; import { ModalSection } from "../ModalSection/ModalSection"; import { WebhookTriggerBanner } from "../WebhookTriggerBanner/WebhookTriggerBanner"; @@ -17,15 +18,16 @@ export function ModalRunSection() { inputValues, setInputValue, agentInputFields, - inputCredentials, - setInputCredentialsValue, agentCredentialsInputFields, } = useRunAgentModalContext(); const inputFields = Object.entries(agentInputFields || {}); - const credentialFields = Object.entries(agentCredentialsInputFields || {}); - // Get the list of required credentials from the schema + const credentialFields = useMemo(() => { + if (!agentCredentialsInputFields) return []; + return Object.entries(agentCredentialsInputFields); + }, [agentCredentialsInputFields]); + const requiredCredentials = new Set( (agent.credentials_input_schema?.required as string[]) || [], ); @@ -97,24 +99,10 @@ export function ModalRunSection() { title="Task Credentials" subtitle="These are the credentials the agent will use to perform this task" > -
- {Object.entries(agentCredentialsInputFields || {}).map( - ([key, inputSubSchema]) => ( - - setInputCredentialsValue(key, value) - } - siblingInputs={inputValues} - isOptional={!requiredCredentials.has(key)} - /> - ), - )} -
+ ) : null}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/helpers.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/helpers.ts new file mode 100644 index 0000000000..61267f733d --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/helpers.ts @@ -0,0 +1,210 @@ +import { CredentialsProvidersContextType } from "@/providers/agent-credentials/credentials-provider"; +import { getSystemCredentials } from "../../CredentialsInputs/helpers"; + +export type CredentialField = [string, any]; + +type SavedCredential = { + id: string; + provider: string; + type: string; + title?: string | null; +}; + +function hasRequiredScopes( + credential: { scopes?: string[]; type: string }, + requiredScopes?: string[], +) { + if (credential.type !== "oauth2") return true; + if (!requiredScopes || requiredScopes.length === 0) return true; + const grantedScopes = new Set(credential.scopes || []); + for (const scope of requiredScopes) { + if (!grantedScopes.has(scope)) return false; + } + return true; +} + +export function splitCredentialFieldsBySystem( + credentialFields: CredentialField[], + allProviders: CredentialsProvidersContextType | null, + inputCredentials?: Record, +) { + if (!allProviders || credentialFields.length === 0) { + return { + userCredentialFields: [] as CredentialField[], + systemCredentialFields: [] as CredentialField[], + }; + } + + const userFields: CredentialField[] = []; + const systemFields: CredentialField[] = []; + + for (const [key, schema] of credentialFields) { + const providerNames = schema.credentials_provider || []; + const isSystemField = providerNames.some((providerName: string) => { + const providerData = allProviders[providerName]; + return providerData?.isSystemProvider === true; + }); + + if (isSystemField) { + systemFields.push([key, schema]); + } else { + userFields.push([key, schema]); + } + } + + const sortByUnsetFirst = (a: CredentialField, b: CredentialField) => { + const aIsSet = Boolean(inputCredentials?.[a[0]]); + const bIsSet = Boolean(inputCredentials?.[b[0]]); + + if (aIsSet === bIsSet) return 0; + return aIsSet ? 1 : -1; + }; + + return { + userCredentialFields: userFields.sort(sortByUnsetFirst), + systemCredentialFields: systemFields.sort(sortByUnsetFirst), + }; +} + +export function areSystemCredentialProvidersLoading( + systemCredentialFields: CredentialField[], + allProviders: CredentialsProvidersContextType | null, +): boolean { + if (!systemCredentialFields.length) return false; + if (allProviders === null) return true; + + for (const [_, schema] of systemCredentialFields) { + const providerNames = schema.credentials_provider || []; + const hasAllProviders = providerNames.every( + (providerName: string) => allProviders?.[providerName] !== undefined, + ); + if (!hasAllProviders) return true; + } + + return false; +} + +export function hasMissingRequiredSystemCredentials( + systemCredentialFields: CredentialField[], + requiredCredentials: Set, + inputCredentials?: Record, + allProviders?: CredentialsProvidersContextType | null, +) { + if (systemCredentialFields.length === 0) return false; + if (allProviders === null) return false; + + return systemCredentialFields.some(([key, schema]) => { + if (!requiredCredentials.has(key)) return false; + if (inputCredentials?.[key]) return false; + + const providerNames = schema.credentials_provider || []; + const credentialTypes = schema.credentials_types || []; + const requiredScopes = schema.credentials_scopes; + + return !hasAvailableSystemCredential( + providerNames, + credentialTypes, + requiredScopes, + allProviders, + ); + }); +} + +function hasAvailableSystemCredential( + providerNames: string[], + credentialTypes: string[], + requiredScopes: string[] | undefined, + allProviders: CredentialsProvidersContextType | null | undefined, +) { + if (!allProviders) return false; + + for (const providerName of providerNames) { + const providerData = allProviders[providerName]; + if (!providerData) continue; + + const systemCredentials = getSystemCredentials( + providerData.savedCredentials ?? [], + ); + + for (const credential of systemCredentials) { + const typeMatches = + credentialTypes.length === 0 || + credentialTypes.includes(credential.type); + const scopesMatch = hasRequiredScopes(credential, requiredScopes); + + if (!typeMatches) continue; + if (!scopesMatch) continue; + + return true; + } + + const allCredentials = providerData.savedCredentials ?? []; + for (const credential of allCredentials) { + const typeMatches = + credentialTypes.length === 0 || + credentialTypes.includes(credential.type); + const scopesMatch = hasRequiredScopes(credential, requiredScopes); + + if (!typeMatches) continue; + if (!scopesMatch) continue; + + return true; + } + } + + return false; +} + +export function findSavedCredentialByProviderAndType( + providerNames: string[], + credentialTypes: string[], + requiredScopes: string[] | undefined, + allProviders: CredentialsProvidersContextType | null, +): SavedCredential | undefined { + for (const providerName of providerNames) { + const providerData = allProviders?.[providerName]; + if (!providerData) continue; + + const systemCredentials = getSystemCredentials( + providerData.savedCredentials ?? [], + ); + + const matchingCredentials: SavedCredential[] = []; + + for (const credential of systemCredentials) { + const typeMatches = + credentialTypes.length === 0 || + credentialTypes.includes(credential.type); + const scopesMatch = hasRequiredScopes(credential, requiredScopes); + + if (!typeMatches) continue; + if (!scopesMatch) continue; + + matchingCredentials.push(credential as SavedCredential); + } + + if (matchingCredentials.length === 0) { + const allCredentials = providerData.savedCredentials ?? []; + for (const credential of allCredentials) { + const typeMatches = + credentialTypes.length === 0 || + credentialTypes.includes(credential.type); + const scopesMatch = hasRequiredScopes(credential, requiredScopes); + + if (!typeMatches) continue; + if (!scopesMatch) continue; + + matchingCredentials.push(credential as SavedCredential); + } + } + + if (matchingCredentials.length === 1) { + return matchingCredentials[0]; + } + if (matchingCredentials.length > 1) { + return undefined; + } + } + + return undefined; +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.tsx index eb32083004..3aafd4be50 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.tsx @@ -11,9 +11,18 @@ import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; import { useToast } from "@/components/molecules/Toast/use-toast"; import { isEmpty } from "@/lib/utils"; +import { CredentialsProvidersContext } from "@/providers/agent-credentials/credentials-provider"; import { analytics } from "@/services/analytics"; import { useQueryClient } from "@tanstack/react-query"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { getSystemCredentials } from "../CredentialsInputs/helpers"; import { showExecutionErrorToast } from "./errorHelpers"; export type RunVariant = @@ -42,8 +51,10 @@ export function useAgentRunModal( const [inputCredentials, setInputCredentials] = useState>( callbacks?.initialInputCredentials || {}, ); + const [presetName, setPresetName] = useState(""); const [presetDescription, setPresetDescription] = useState(""); + const hasInitializedSystemCreds = useRef(false); // Determine the default run type based on agent capabilities const defaultRunType: RunVariant = agent.trigger_setup_info @@ -58,6 +69,91 @@ export function useAgentRunModal( setInputCredentials(callbacks?.initialInputCredentials || {}); }, [callbacks?.initialInputValues, callbacks?.initialInputCredentials]); + const allProviders = useContext(CredentialsProvidersContext); + + // Initialize credentials with default system credentials + useEffect(() => { + if (!allProviders || !agent.credentials_input_schema?.properties) return; + if (callbacks?.initialInputCredentials) { + hasInitializedSystemCreds.current = true; + return; + } + if (hasInitializedSystemCreds.current) return; + + const properties = agent.credentials_input_schema.properties as Record< + string, + any + >; + + setInputCredentials((currentCreds) => { + const credsToAdd: Record = {}; + + for (const [key, schema] of Object.entries(properties)) { + if (currentCreds[key]) continue; + + const providerNames = schema.credentials_provider || []; + const supportedTypes = schema.credentials_types || []; + const requiredScopes = schema.credentials_scopes; + + for (const providerName of providerNames) { + const providerData = allProviders[providerName]; + if (!providerData) continue; + + const systemCreds = getSystemCredentials( + providerData.savedCredentials ?? [], + ); + const matchingSystemCreds = systemCreds.filter((cred) => { + if (!supportedTypes.includes(cred.type)) return false; + + if ( + cred.type === "oauth2" && + requiredScopes && + requiredScopes.length > 0 + ) { + const grantedScopes = new Set(cred.scopes || []); + const hasAllRequiredScopes = requiredScopes.every( + (scope: string) => grantedScopes.has(scope), + ); + if (!hasAllRequiredScopes) return false; + } + + return true; + }); + + if (matchingSystemCreds.length === 1) { + const systemCred = matchingSystemCreds[0]; + credsToAdd[key] = { + id: systemCred.id, + type: systemCred.type, + provider: providerName, + title: systemCred.title, + }; + break; + } + } + } + + if (Object.keys(credsToAdd).length > 0) { + hasInitializedSystemCreds.current = true; + return { + ...currentCreds, + ...credsToAdd, + }; + } + + return currentCreds; + }); + }, [ + allProviders, + agent.credentials_input_schema, + callbacks?.initialInputCredentials, + ]); + + // Reset initialization flag when modal closes/opens or agent changes + useEffect(() => { + hasInitializedSystemCreds.current = false; + }, [isOpen, agent.graph_id]); + // API mutations const executeGraphMutation = usePostV1ExecuteGraphAgent({ mutation: { @@ -66,7 +162,6 @@ export function useAgentRunModal( toast({ title: "Agent execution started", }); - // Invalidate runs list for this graph queryClient.invalidateQueries({ queryKey: getGetV1ListGraphExecutionsQueryKey(agent.graph_id), }); @@ -163,14 +258,10 @@ export function useAgentRunModal( }, [agentInputSchema.required, inputValues]); const [allCredentialsAreSet, missingCredentials] = useMemo(() => { - // Only check required credentials from schema, not all properties - // Credentials marked as optional in node metadata won't be in the required array const requiredCredentials = new Set( (agent.credentials_input_schema?.required as string[]) || [], ); - // Check if required credentials have valid id (not just key existence) - // A credential is valid only if it has an id field set const missing = [...requiredCredentials].filter((key) => { const cred = inputCredentials[key]; return !cred || !cred.id; @@ -184,7 +275,6 @@ export function useAgentRunModal( [agentCredentialsInputFields], ); - // Final readiness flag combining inputs + credentials when credentials are shown const allRequiredInputsAreSet = useMemo( () => allRequiredInputsAreSetRaw && @@ -223,7 +313,6 @@ export function useAgentRunModal( defaultRunType === "automatic-trigger" || defaultRunType === "manual-trigger" ) { - // Setup trigger if (!presetName.trim()) { toast({ title: "⚠️ Trigger name required", @@ -244,9 +333,6 @@ export function useAgentRunModal( }, }); } else { - // Manual execution - // Filter out incomplete credentials (optional ones not selected) - // Only send credentials that have a valid id field const validCredentials = Object.fromEntries( Object.entries(inputCredentials).filter(([_, cred]) => cred && cred.id), ); @@ -280,41 +366,24 @@ export function useAgentRunModal( }, [agentInputFields]); return { - // UI state isOpen, setIsOpen, - - // Run mode defaultRunType: defaultRunType as RunVariant, - - // Form: regular inputs inputValues, setInputValues, - - // Form: credentials inputCredentials, setInputCredentials, - - // Preset/trigger labels presetName, presetDescription, setPresetName, setPresetDescription, - - // Validation/readiness allRequiredInputsAreSet, missingInputs, - - // Schemas for rendering agentInputFields, agentCredentialsInputFields, hasInputFields, - - // Async states isExecuting: executeGraphMutation.isPending, isSettingUpTrigger: setupTriggerMutation.isPending, - - // Actions handleRun, }; } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/AgentSettingsButton.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/AgentSettingsButton.tsx index 11dcbd943f..95fdf826a2 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/AgentSettingsButton.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/AgentSettingsButton.tsx @@ -1,37 +1,17 @@ import { Button } from "@/components/atoms/Button/Button"; +import { Text } from "@/components/atoms/Text/Text"; import { GearIcon } from "@phosphor-icons/react"; -import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; -import { useAgentSafeMode } from "@/hooks/useAgentSafeMode"; - -interface Props { - agent: LibraryAgent; - onSelectSettings: () => void; - selected?: boolean; -} - -export function AgentSettingsButton({ - agent, - onSelectSettings, - selected, -}: Props) { - const { hasHITLBlocks } = useAgentSafeMode(agent); - - if (!hasHITLBlocks) { - return null; - } +export function AgentSettingsButton() { return ( ); } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedules.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedules.tsx index 97492d8a59..4c781b2896 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedules.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedules.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Text } from "@/components/atoms/Text/Text"; export function EmptySchedules() { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTemplates.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTemplates.tsx index c33abe69ad..364b762167 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTemplates.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTemplates.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Text } from "@/components/atoms/Text/Text"; export function EmptyTemplates() { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx index 0d9dc47fff..06d09ff9a0 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Text } from "@/components/atoms/Text/Text"; export function EmptyTriggers() { diff --git a/autogpt_platform/frontend/src/components/contextual/MarketplaceBanners/MarketplaceBanners.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/MarketplaceBanners.tsx similarity index 97% rename from autogpt_platform/frontend/src/components/contextual/MarketplaceBanners/MarketplaceBanners.tsx rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/MarketplaceBanners.tsx index 4f826f6e85..00edcc721f 100644 --- a/autogpt_platform/frontend/src/components/contextual/MarketplaceBanners/MarketplaceBanners.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/MarketplaceBanners.tsx @@ -3,7 +3,7 @@ import { Button } from "@/components/atoms/Button/Button"; import { Text } from "@/components/atoms/Text/Text"; -interface MarketplaceBannersProps { +interface Props { hasUpdate?: boolean; latestVersion?: number; hasUnpublishedChanges?: boolean; @@ -21,7 +21,7 @@ export function MarketplaceBanners({ isUpdating, onUpdate, onPublish, -}: MarketplaceBannersProps) { +}: Props) { const renderUpdateBanner = () => { if (hasUpdate && latestVersion) { return ( diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/SectionWrap.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/SectionWrap.tsx index 75571dd856..f88d91bb0d 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/SectionWrap.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/SectionWrap.tsx @@ -1,3 +1,5 @@ +"use client"; + import { cn } from "@/lib/utils"; type Props = { diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/LoadingSelectedContent.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/LoadingSelectedContent.tsx index dc2bb7cac2..bc5548afd0 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/LoadingSelectedContent.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/LoadingSelectedContent.tsx @@ -1,22 +1,16 @@ +import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { Skeleton } from "@/components/__legacy__/ui/skeleton"; import { cn } from "@/lib/utils"; -import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../helpers"; import { SelectedViewLayout } from "./SelectedViewLayout"; interface Props { agent: LibraryAgent; - onSelectSettings?: () => void; - selectedSettings?: boolean; } export function LoadingSelectedContent(props: Props) { return ( - +
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx index c66f0e9245..05da986583 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx @@ -33,8 +33,6 @@ interface Props { onSelectRun?: (id: string) => void; onClearSelectedRun?: () => void; banner?: React.ReactNode; - onSelectSettings?: () => void; - selectedSettings?: boolean; } export function SelectedRunView({ @@ -43,8 +41,6 @@ export function SelectedRunView({ onSelectRun, onClearSelectedRun, banner, - onSelectSettings, - selectedSettings, }: Props) { const { run, preset, isLoading, responseError, httpError } = useSelectedRunView(agent.graph_id, runId); @@ -84,12 +80,7 @@ export function SelectedRunView({ return (
- +
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx index 445394c44a..e0a81dba5f 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx @@ -21,8 +21,6 @@ interface Props { scheduleId: string; onClearSelectedRun?: () => void; banner?: React.ReactNode; - onSelectSettings?: () => void; - selectedSettings?: boolean; } export function SelectedScheduleView({ @@ -30,8 +28,6 @@ export function SelectedScheduleView({ scheduleId, onClearSelectedRun, banner, - onSelectSettings, - selectedSettings, }: Props) { const { schedule, isLoading, error } = useSelectedScheduleView( agent.graph_id, @@ -76,12 +72,7 @@ export function SelectedScheduleView({ return (
- +
{}}> +
Agent Settings
-
- {!hasHITLBlocks ? ( -
- - This agent doesn't have any human-in-the-loop blocks, so - there are no settings to configure. - -
- ) : ( +
+ {hasHITLBlocks ? (
@@ -59,6 +52,12 @@ export function SelectedSettingsView({ agent, onClearSelectedRun }: Props) { />
+ ) : ( +
+ + This agent doesn't have any configurable settings. + +
)}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx index b5ecb7ae5c..d0c49c2a93 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx @@ -8,7 +8,7 @@ import { getAgentCredentialsFields, getAgentInputFields, } from "../../modals/AgentInputsReadOnly/helpers"; -import { CredentialsInput } from "../../modals/CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "../../modals/CredentialsInputs/CredentialsInput"; import { RunAgentInputs } from "../../modals/RunAgentInputs/RunAgentInputs"; import { LoadingSelectedContent } from "../LoadingSelectedContent"; import { RunDetailCard } from "../RunDetailCard/RunDetailCard"; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx index f92c91112e..0d0cdc95cc 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx @@ -7,7 +7,7 @@ import { getAgentCredentialsFields, getAgentInputFields, } from "../../modals/AgentInputsReadOnly/helpers"; -import { CredentialsInput } from "../../modals/CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "../../modals/CredentialsInputs/CredentialsInput"; import { RunAgentInputs } from "../../modals/RunAgentInputs/RunAgentInputs"; import { LoadingSelectedContent } from "../LoadingSelectedContent"; import { RunDetailCard } from "../RunDetailCard/RunDetailCard"; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedViewLayout.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedViewLayout.tsx index 8a4e46a606..fe824604df 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedViewLayout.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedViewLayout.tsx @@ -1,7 +1,7 @@ -import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs"; -import { AgentSettingsButton } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/AgentSettingsButton"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; +import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs"; import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../helpers"; +import { AgentSettingsModal } from "../modals/AgentSettingsModal/AgentSettingsModal"; import { SectionWrap } from "../other/SectionWrap"; interface Props { @@ -9,8 +9,6 @@ interface Props { children: React.ReactNode; banner?: React.ReactNode; additionalBreadcrumb?: { name: string; link?: string }; - onSelectSettings?: () => void; - selectedSettings?: boolean; } export function SelectedViewLayout(props: Props) { @@ -19,8 +17,8 @@ export function SelectedViewLayout(props: Props) {
- {props.banner &&
{props.banner}
} -
+ {props.banner} +
- {props.agent && props.onSelectSettings && ( -
- -
- )} +
+ +
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx index 5f57032618..1b155543f1 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx @@ -12,7 +12,7 @@ import { } from "@/lib/autogpt-server-api"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; -import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput"; import { RunAgentInputs } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentInputs/RunAgentInputs"; import { ScheduleTaskDialog } from "@/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/cron-scheduler-dialog"; import ActionButtonGroup from "@/components/__legacy__/action-button-group"; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/page.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/page.tsx index 9ada590dd8..147c0aef45 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/page.tsx @@ -1,14 +1,7 @@ "use client"; -import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag"; import { NewAgentLibraryView } from "./components/NewAgentLibraryView/NewAgentLibraryView"; -import { OldAgentLibraryView } from "./components/OldAgentLibraryView/OldAgentLibraryView"; export default function AgentLibraryPage() { - const isNewLibraryPageEnabled = useGetFlag(Flag.NEW_AGENT_RUNS); - return isNewLibraryPageEnabled ? ( - - ) : ( - - ); + return ; } diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index e601be6626..6f9a87216b 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -2870,6 +2870,28 @@ } } }, + "/api/integrations/providers/system": { + "get": { + "tags": ["v1", "integrations"], + "summary": "List System Providers", + "description": "Get a list of providers that have platform credits (system credentials) available.\n\nThese providers can be used without the user providing their own API keys.", + "operationId": "getV1ListSystemProviders", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { "type": "string" }, + "type": "array", + "title": "Response Getv1Listsystemproviders" + } + } + } + } + } + } + }, "/api/integrations/webhooks/{webhook_id}/ping": { "post": { "tags": ["v1", "integrations"], diff --git a/autogpt_platform/frontend/src/components/atoms/Button/Button.tsx b/autogpt_platform/frontend/src/components/atoms/Button/Button.tsx index de1dec2d25..ab7b90e098 100644 --- a/autogpt_platform/frontend/src/components/atoms/Button/Button.tsx +++ b/autogpt_platform/frontend/src/components/atoms/Button/Button.tsx @@ -20,6 +20,7 @@ export function Button(props: ButtonProps) { rightIcon, children, as = "button", + asChild: _asChild, // Destructure to prevent passing to DOM ...restProps } = props; diff --git a/autogpt_platform/frontend/src/components/contextual/GoogleDrivePicker/GoogleDrivePicker.tsx b/autogpt_platform/frontend/src/components/contextual/GoogleDrivePicker/GoogleDrivePicker.tsx index e0a43b8c77..4decd2dbdb 100644 --- a/autogpt_platform/frontend/src/components/contextual/GoogleDrivePicker/GoogleDrivePicker.tsx +++ b/autogpt_platform/frontend/src/components/contextual/GoogleDrivePicker/GoogleDrivePicker.tsx @@ -1,6 +1,6 @@ "use client"; -import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs"; +import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput"; import { Button } from "@/components/atoms/Button/Button"; import { CircleNotchIcon, FolderOpenIcon } from "@phosphor-icons/react"; import { diff --git a/autogpt_platform/frontend/src/components/molecules/Accordion/Accordion.stories.tsx b/autogpt_platform/frontend/src/components/molecules/Accordion/Accordion.stories.tsx new file mode 100644 index 0000000000..d0fce53e0e --- /dev/null +++ b/autogpt_platform/frontend/src/components/molecules/Accordion/Accordion.stories.tsx @@ -0,0 +1,203 @@ +import type { Meta } from "@storybook/nextjs"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "./Accordion"; + +const meta: Meta = { + title: "Molecules/Accordion", + component: Accordion, + parameters: { + layout: "centered", + docs: { + description: { + component: ` +## Accordion Component + +A vertically stacked set of interactive headings that each reveal an associated section of content. + +### ✨ Features + +- **Built on Radix UI** - Uses @radix-ui/react-accordion for accessibility and functionality +- **Single or multiple** - Supports single or multiple items open at once +- **Smooth animations** - Built-in expand/collapse animations +- **Accessible** - Full keyboard navigation and screen reader support +- **Customizable** - Style with Tailwind CSS classes + +### 🎯 Usage + +\`\`\`tsx + + + Is it accessible? + + Yes. It adheres to the WAI-ARIA design pattern. + + + +\`\`\` + +### Props + +**Accordion**: +- **type**: "single" | "multiple" - Whether one or multiple items can be open +- **collapsible**: boolean - When type is "single", allows closing all items +- **defaultValue**: string | string[] - Default open item(s) +- **value**: string | string[] - Controlled open item(s) +- **onValueChange**: (value) => void - Callback when value changes + +**AccordionItem**: +- **value**: string - Unique identifier for the item +- **disabled**: boolean - Whether the item is disabled + +**AccordionTrigger**: +- Standard button props + +**AccordionContent**: +- Standard div props + `, + }, + }, + }, + tags: ["autodocs"], + argTypes: { + type: { + control: "radio", + options: ["single", "multiple"], + description: "Whether one or multiple items can be open at the same time", + table: { + defaultValue: { summary: "single" }, + }, + }, + collapsible: { + control: "boolean", + description: + 'When type is "single", allows closing content when clicking on open trigger', + table: { + defaultValue: { summary: "false" }, + }, + }, + }, +}; + +export default meta; + +export function Default() { + return ( + + + Is it accessible? + + Yes. It adheres to the WAI-ARIA design pattern. + + + + Is it styled? + + Yes. It comes with default styles that match your design system. + + + + Is it animated? + + Yes. It's animated by default with smooth expand/collapse + transitions. + + + + ); +} + +export function Multiple() { + return ( + + + First section + + Multiple items can be open at the same time when type is set to + "multiple". + + + + Second section + + Try opening this one while the first is still open. + + + + Third section + + All three can be open simultaneously. + + + + ); +} + +export function DefaultOpen() { + return ( + + + Closed by default + This item starts closed. + + + Open by default + + This item starts open because defaultValue is set to + "item-2". + + + + Also closed + This item also starts closed. + + + ); +} + +export function WithDisabledItem() { + return ( + + + Available item + This item can be toggled. + + + Disabled item + + This content cannot be accessed because the item is disabled. + + + + Another available item + This item can also be toggled. + + + ); +} + +export function CustomStyled() { + return ( + + + + Custom styled trigger + + + You can customize the styling using className props. + + + + + Blue themed + + + Each item can have different styles. + + + + ); +} diff --git a/autogpt_platform/frontend/src/components/molecules/Accordion/Accordion.tsx b/autogpt_platform/frontend/src/components/molecules/Accordion/Accordion.tsx new file mode 100644 index 0000000000..b071fc1d37 --- /dev/null +++ b/autogpt_platform/frontend/src/components/molecules/Accordion/Accordion.tsx @@ -0,0 +1,8 @@ +"use client"; + +export { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; diff --git a/autogpt_platform/frontend/src/components/molecules/Dialog/components/DrawerWrap.tsx b/autogpt_platform/frontend/src/components/molecules/Dialog/components/DrawerWrap.tsx index d00817bf59..3bfa321538 100644 --- a/autogpt_platform/frontend/src/components/molecules/Dialog/components/DrawerWrap.tsx +++ b/autogpt_platform/frontend/src/components/molecules/Dialog/components/DrawerWrap.tsx @@ -22,6 +22,9 @@ export function DrawerWrap({ handleClose, isForceOpen, }: Props) { + const accessibleTitle = title ?? "Dialog"; + const hasVisibleTitle = Boolean(title); + const closeBtn = (