mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-15 10:08:17 -05:00
Compare commits
1 Commits
feat/backf
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375d33cca9 |
16
.github/workflows/platform-frontend-ci.yml
vendored
16
.github/workflows/platform-frontend-ci.yml
vendored
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
57
autogpt_platform/frontend/TESTING.md
Normal file
57
autogpt_platform/frontend/TESTING.md
Normal file
@@ -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.
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
140
autogpt_platform/frontend/pnpm-lock.yaml
generated
140
autogpt_platform/frontend/pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="mx-6 pt-4">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ name: "My Library", link: "/library" },
|
||||
{ name: agent.name, link: `/library/agents/${agentId}` },
|
||||
]}
|
||||
/>
|
||||
<div className="mx-6 flex flex-col gap-4 pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ name: "My Library", link: "/library" },
|
||||
{ name: agent.name, link: `/library/agents/${agentId}` },
|
||||
]}
|
||||
/>
|
||||
<AgentSettingsModal agent={agent} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-1">
|
||||
<EmptyTasks
|
||||
@@ -170,31 +171,24 @@ export function NewAgentLibraryView() {
|
||||
AGENT_LIBRARY_SECTION_PADDING_X,
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RunAgentModal
|
||||
triggerSlot={
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="flex-1"
|
||||
disabled={isTemplateLoading && activeTab === "templates"}
|
||||
>
|
||||
<PlusIcon size={20} /> New task
|
||||
</Button>
|
||||
}
|
||||
agent={agent}
|
||||
onRunCreated={onRunInitiated}
|
||||
onScheduleCreated={onScheduleCreated}
|
||||
onTriggerSetup={onTriggerSetup}
|
||||
initialInputValues={activeTemplate?.inputs}
|
||||
initialInputCredentials={activeTemplate?.credentials}
|
||||
/>
|
||||
<AgentSettingsButton
|
||||
agent={agent}
|
||||
onSelectSettings={handleSelectSettings}
|
||||
selected={activeItem === "settings"}
|
||||
/>
|
||||
</div>
|
||||
<RunAgentModal
|
||||
triggerSlot={
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="w-full"
|
||||
disabled={isTemplateLoading && activeTab === "templates"}
|
||||
>
|
||||
<PlusIcon size={20} /> New task
|
||||
</Button>
|
||||
}
|
||||
agent={agent}
|
||||
onRunCreated={onRunInitiated}
|
||||
onScheduleCreated={onScheduleCreated}
|
||||
onTriggerSetup={onTriggerSetup}
|
||||
initialInputValues={activeTemplate?.inputs}
|
||||
initialInputCredentials={activeTemplate?.credentials}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SidebarRunsList
|
||||
@@ -208,12 +202,7 @@ export function NewAgentLibraryView() {
|
||||
</SectionWrap>
|
||||
|
||||
{activeItem ? (
|
||||
activeItem === "settings" ? (
|
||||
<SelectedSettingsView
|
||||
agent={agent}
|
||||
onClearSelectedRun={handleClearSelectedRun}
|
||||
/>
|
||||
) : activeTab === "scheduled" ? (
|
||||
activeTab === "scheduled" ? (
|
||||
<SelectedScheduleView
|
||||
agent={agent}
|
||||
scheduleId={activeItem}
|
||||
@@ -246,8 +235,6 @@ export function NewAgentLibraryView() {
|
||||
onSelectRun={handleSelectRun}
|
||||
onClearSelectedRun={handleClearSelectedRun}
|
||||
banner={renderMarketplaceUpdateBanner()}
|
||||
onSelectSettings={handleSelectSettings}
|
||||
selectedSettings={activeItem === "settings"}
|
||||
/>
|
||||
)
|
||||
) : sidebarLoading ? (
|
||||
|
||||
@@ -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 (
|
||||
<CredentialsInput
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
import { useAgentSafeMode } from "@/hooks/useAgentSafeMode";
|
||||
import { GearIcon } from "@phosphor-icons/react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props {
|
||||
agent: LibraryAgent;
|
||||
controlledOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => 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 (
|
||||
<Dialog
|
||||
controlled={{ isOpen, set: setIsOpen }}
|
||||
styling={{ maxWidth: "600px", maxHeight: "90vh" }}
|
||||
title="Agent Settings"
|
||||
>
|
||||
{controlledOpen === undefined && (
|
||||
<Dialog.Trigger>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="m-0 min-w-0 rounded-full p-0 px-1"
|
||||
aria-label="Agent Settings"
|
||||
>
|
||||
<GearIcon size={18} className="text-zinc-600" />
|
||||
<Text variant="small">Agent Settings</Text>
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
)}
|
||||
<Dialog.Content>
|
||||
<div className="space-y-6">
|
||||
<div className="flex w-full flex-col items-start gap-4 rounded-xl border border-zinc-100 bg-white p-6">
|
||||
<div className="flex w-full items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<Text variant="large-semibold">Require human approval</Text>
|
||||
<Text variant="large" className="mt-1 text-zinc-900">
|
||||
The agent will pause and wait for your review before
|
||||
continuing
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
checked={currentSafeMode || false}
|
||||
onCheckedChange={handleToggle}
|
||||
disabled={isPending}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className={cn("mb-6", className)}>
|
||||
{showTitle && (
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<Text variant="large-medium">
|
||||
{displayName} credentials
|
||||
{isOptional && (
|
||||
<span className="ml-1 text-sm font-normal text-gray-500">
|
||||
(optional)
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
{schema.description && (
|
||||
<InformationTooltip description={schema.description} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasCredentialsToShow ? (
|
||||
<>
|
||||
{(credentialsToShow.length > 1 || isOptional) && !readOnly ? (
|
||||
<CredentialsSelect
|
||||
credentials={credentialsToShow}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
selectedCredentials={selectedCredential}
|
||||
onSelectCredential={handleCredentialSelect}
|
||||
onClearCredential={() => onSelectCredential(undefined)}
|
||||
readOnly={readOnly}
|
||||
allowNone={isOptional}
|
||||
variant={variant}
|
||||
/>
|
||||
) : (
|
||||
<div className="mb-4 space-y-2">
|
||||
{credentialsToShow.map((credential) => {
|
||||
return (
|
||||
<CredentialRow
|
||||
key={credential.id}
|
||||
credential={credential}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
onSelect={() => handleCredentialSelect(credential.id)}
|
||||
onDelete={() =>
|
||||
handleDeleteCredential({
|
||||
id: credential.id,
|
||||
title: getCredentialDisplayName(
|
||||
credential,
|
||||
displayName,
|
||||
),
|
||||
})
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{!readOnly && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleActionButtonClick}
|
||||
className="w-fit"
|
||||
type="button"
|
||||
>
|
||||
{actionButtonText}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
!readOnly && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleActionButtonClick}
|
||||
className="w-fit"
|
||||
type="button"
|
||||
>
|
||||
{actionButtonText}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
<CredentialsFlatView
|
||||
schema={schema}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
credentials={allCredentials}
|
||||
selectedCredential={selectedCredential}
|
||||
onSelectCredential={handleCredentialSelect}
|
||||
onClearCredential={() => onSelectCredential(undefined)}
|
||||
onAddCredential={handleActionButtonClick}
|
||||
actionButtonText={actionButtonText}
|
||||
isOptional={isOptional}
|
||||
showTitle={showTitle}
|
||||
readOnly={readOnly}
|
||||
variant={variant}
|
||||
/>
|
||||
|
||||
{!readOnly && (
|
||||
<>
|
||||
{supportsApiKey ? (
|
||||
{supportsApiKey && (
|
||||
<APIKeyCredentialsModal
|
||||
schema={schema}
|
||||
open={isAPICredentialsModalOpen}
|
||||
@@ -191,15 +125,15 @@ export function CredentialsInput({
|
||||
}}
|
||||
siblingInputs={siblingInputs}
|
||||
/>
|
||||
) : null}
|
||||
{supportsOAuth2 ? (
|
||||
)}
|
||||
{supportsOAuth2 && (
|
||||
<OAuthFlowWaitingModal
|
||||
open={isOAuth2FlowInProgress}
|
||||
onClose={() => oAuthPopupController?.abort("canceled")}
|
||||
providerName={providerName}
|
||||
/>
|
||||
) : null}
|
||||
{supportsUserPassword ? (
|
||||
)}
|
||||
{supportsUserPassword && (
|
||||
<PasswordCredentialsModal
|
||||
schema={schema}
|
||||
open={isUserPasswordCredentialsModalOpen}
|
||||
@@ -210,8 +144,8 @@ export function CredentialsInput({
|
||||
}}
|
||||
siblingInputs={siblingInputs}
|
||||
/>
|
||||
) : null}
|
||||
{supportsHostScoped ? (
|
||||
)}
|
||||
{supportsHostScoped && (
|
||||
<HostScopedCredentialsModal
|
||||
schema={schema}
|
||||
open={isHostScopedCredentialsModalOpen}
|
||||
@@ -222,20 +156,13 @@ export function CredentialsInput({
|
||||
}}
|
||||
siblingInputs={siblingInputs}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
|
||||
{oAuthError ? (
|
||||
{oAuthError && (
|
||||
<Text variant="body" className="mt-2 text-red-500">
|
||||
Error: {oAuthError}
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
<DeleteConfirmationModal
|
||||
credentialToDelete={credentialToDelete}
|
||||
isDeleting={deleteCredentialsMutation.isPending}
|
||||
onClose={() => setCredentialToDelete(null)}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -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({
|
||||
)}
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2">
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-2 px-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="title"
|
||||
label="Name"
|
||||
type="text"
|
||||
placeholder="Enter a name for this API Key..."
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apiKey"
|
||||
@@ -70,8 +86,7 @@ export function APIKeyCredentialsModal({
|
||||
id="apiKey"
|
||||
label="API Key"
|
||||
type="password"
|
||||
placeholder="Enter API key..."
|
||||
size="small"
|
||||
placeholder="Enter API Key..."
|
||||
hint={
|
||||
schema.credentials_scopes ? (
|
||||
<FormDescription>
|
||||
@@ -90,20 +105,7 @@ export function APIKeyCredentialsModal({
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="title"
|
||||
label="Name"
|
||||
type="text"
|
||||
placeholder="Enter a name for this API key..."
|
||||
size="small"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="expiresAt"
|
||||
@@ -113,13 +115,31 @@ export function APIKeyCredentialsModal({
|
||||
label="Expiration Date"
|
||||
type="datetime-local"
|
||||
placeholder="Select expiration date..."
|
||||
size="small"
|
||||
{...field}
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" size="small" className="min-w-68">
|
||||
Save & use this API key
|
||||
<Button type="submit" className="min-w-68">
|
||||
Add API Key
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -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<APIKeyFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
apiKey: "",
|
||||
title: "",
|
||||
expiresAt: "",
|
||||
expiresAt: getDefaultExpirationDate(),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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<HTMLDivElement>(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 (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-medium border border-zinc-200 bg-white p-3 transition-colors",
|
||||
"flex min-w-[20rem] items-center gap-3 rounded-medium border border-zinc-200 bg-white p-3 transition-colors",
|
||||
asSelectTrigger && isNodeVariant
|
||||
? "min-w-0 flex-1 overflow-hidden border-0 bg-transparent"
|
||||
: asSelectTrigger
|
||||
@@ -73,34 +95,35 @@ export function CredentialRow({
|
||||
<IconKey className="h-5 w-5 shrink-0 text-zinc-800" />
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-w-0 flex-1 flex-nowrap items-center gap-4",
|
||||
"relative flex min-w-0 flex-1 flex-nowrap items-center gap-4",
|
||||
isNodeVariant && "overflow-hidden",
|
||||
)}
|
||||
>
|
||||
<Text
|
||||
variant="body"
|
||||
className={cn(
|
||||
"tracking-tight",
|
||||
isNodeVariant
|
||||
? "truncate"
|
||||
: "line-clamp-1 flex-[0_0_50%] text-ellipsis",
|
||||
"min-w-0 flex-1 tracking-tight",
|
||||
isNodeVariant ? "truncate" : "line-clamp-1 text-ellipsis",
|
||||
)}
|
||||
>
|
||||
{getCredentialDisplayName(credential, displayName)}
|
||||
</Text>
|
||||
{!(asSelectTrigger && isNodeVariant) && (
|
||||
{!(asSelectTrigger && isNodeVariant) && showMaskedKey && (
|
||||
<Text
|
||||
variant="large"
|
||||
className="relative top-1 hidden overflow-hidden whitespace-nowrap font-mono tracking-tight md:block"
|
||||
className={cn(
|
||||
"absolute top-[65%] -translate-y-1/2 overflow-hidden whitespace-nowrap font-mono tracking-tight",
|
||||
asSelectTrigger ? "right-0" : "right-6",
|
||||
)}
|
||||
>
|
||||
{"*".repeat(MASKED_KEY_LENGTH)}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
{showCaret && !asSelectTrigger && (
|
||||
<CaretDown className="h-4 w-4 shrink-0 text-gray-400" />
|
||||
{(showCaret || (asSelectTrigger && !readOnly)) && (
|
||||
<CaretDownIcon className="h-4 w-4 shrink-0 text-gray-400" />
|
||||
)}
|
||||
{!readOnly && !showCaret && !asSelectTrigger && (
|
||||
{!readOnly && !showCaret && !asSelectTrigger && onDelete && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/molecules/Accordion/Accordion";
|
||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||
import {
|
||||
BlockIOCredentialsSubSchema,
|
||||
CredentialsMetaInput,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { SlidersHorizontalIcon } from "lucide-react";
|
||||
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;
|
||||
userCredentials: Credential[];
|
||||
systemCredentials: Credential[];
|
||||
isSystemProvider: boolean;
|
||||
selectedCredential?: CredentialsMetaInput;
|
||||
onSelectCredential: (credentialId: string) => void;
|
||||
onClearCredential: () => void;
|
||||
onAddCredential: () => void;
|
||||
actionButtonText: string;
|
||||
isOptional: boolean;
|
||||
showTitle: boolean;
|
||||
variant: "default" | "node";
|
||||
};
|
||||
|
||||
export function CredentialsAccordionView({
|
||||
schema,
|
||||
provider,
|
||||
displayName,
|
||||
userCredentials,
|
||||
systemCredentials,
|
||||
isSystemProvider,
|
||||
selectedCredential,
|
||||
onSelectCredential,
|
||||
onClearCredential,
|
||||
onAddCredential,
|
||||
actionButtonText,
|
||||
isOptional,
|
||||
showTitle,
|
||||
variant,
|
||||
}: Props) {
|
||||
const allCredentials = [...userCredentials, ...systemCredentials];
|
||||
const hasSystemCredentials = systemCredentials.length > 0;
|
||||
const hasUserCredentials = userCredentials.length > 0;
|
||||
|
||||
const credentialsInAccordion = isSystemProvider
|
||||
? allCredentials
|
||||
: systemCredentials;
|
||||
|
||||
const shouldOpenAccordionByDefault =
|
||||
hasSystemCredentials && !isOptional && !selectedCredential;
|
||||
|
||||
const showUserCredentialsOutsideAccordion =
|
||||
!isSystemProvider && hasUserCredentials;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showTitle && showUserCredentialsOutsideAccordion && (
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<Text variant="large-medium">
|
||||
{displayName} credentials
|
||||
{isOptional && (
|
||||
<span className="ml-1 text-sm font-normal text-gray-500">
|
||||
(optional)
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
{schema.description && (
|
||||
<InformationTooltip description={schema.description} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showUserCredentialsOutsideAccordion && (
|
||||
<>
|
||||
{userCredentials.length > 1 || isOptional ? (
|
||||
<CredentialsSelect
|
||||
credentials={userCredentials}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
selectedCredentials={selectedCredential}
|
||||
onSelectCredential={onSelectCredential}
|
||||
onClearCredential={onClearCredential}
|
||||
allowNone={isOptional}
|
||||
variant={variant}
|
||||
/>
|
||||
) : (
|
||||
<div className="mb-4 space-y-2">
|
||||
{userCredentials.map((credential) => (
|
||||
<CredentialRow
|
||||
key={credential.id}
|
||||
credential={credential}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
onSelect={() => onSelectCredential(credential.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onAddCredential}
|
||||
className="w-fit"
|
||||
type="button"
|
||||
>
|
||||
{actionButtonText}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{hasSystemCredentials && (
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
defaultValue={
|
||||
shouldOpenAccordionByDefault ? "system-credentials" : undefined
|
||||
}
|
||||
className={showUserCredentialsOutsideAccordion ? "mt-4" : ""}
|
||||
>
|
||||
<AccordionItem value="system-credentials" className="border-none">
|
||||
<AccordionTrigger className="py-2 text-sm text-muted-foreground hover:no-underline">
|
||||
<div className="flex items-center gap-1">
|
||||
<SlidersHorizontalIcon className="size-4" /> System credentials
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-4 px-1 pt-2">
|
||||
{showTitle && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Text variant="large-medium">
|
||||
{displayName} credentials
|
||||
{isOptional && (
|
||||
<span className="ml-1 text-sm font-normal text-gray-500">
|
||||
(optional)
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
{schema.description && (
|
||||
<InformationTooltip description={schema.description} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{credentialsInAccordion.length > 0 && (
|
||||
<CredentialsSelect
|
||||
credentials={credentialsInAccordion}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
selectedCredentials={selectedCredential}
|
||||
onSelectCredential={onSelectCredential}
|
||||
onClearCredential={onClearCredential}
|
||||
allowNone={isOptional}
|
||||
variant={variant}
|
||||
/>
|
||||
)}
|
||||
{isSystemProvider && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onAddCredential}
|
||||
className="w-fit"
|
||||
type="button"
|
||||
>
|
||||
{actionButtonText}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{!showUserCredentialsOutsideAccordion && !isSystemProvider && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onAddCredential}
|
||||
className="w-fit"
|
||||
type="button"
|
||||
>
|
||||
{actionButtonText}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 && (
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<Text variant="large-medium" className="flex items-center gap-2">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{displayName} credentials
|
||||
{isOptional && (
|
||||
<span className="text-sm font-normal text-gray-500">
|
||||
(optional)
|
||||
</span>
|
||||
)}
|
||||
{!isOptional && !selectedCredential && (
|
||||
<span className="inline-flex items-center gap-1 text-red-600">
|
||||
<ExclamationTriangleIcon className="size-4" />
|
||||
<span className="text-sm font-normal">required</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</Text>
|
||||
{schema.description && (
|
||||
<InformationTooltip description={schema.description} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasCredentials ? (
|
||||
<>
|
||||
{(credentials.length > 1 || isOptional) && !readOnly ? (
|
||||
<CredentialsSelect
|
||||
credentials={credentials}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
selectedCredentials={selectedCredential}
|
||||
onSelectCredential={onSelectCredential}
|
||||
onClearCredential={onClearCredential}
|
||||
readOnly={readOnly}
|
||||
allowNone={isOptional}
|
||||
variant={variant}
|
||||
/>
|
||||
) : (
|
||||
<div className="mb-4 space-y-2">
|
||||
{credentials.map((credential) => (
|
||||
<CredentialRow
|
||||
key={credential.id}
|
||||
credential={credential}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
onSelect={() => onSelectCredential(credential.id)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!readOnly && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onAddCredential}
|
||||
className="w-fit"
|
||||
type="button"
|
||||
>
|
||||
{actionButtonText}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
!readOnly && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onAddCredential}
|
||||
className="w-fit"
|
||||
type="button"
|
||||
>
|
||||
{actionButtonText}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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<HTMLSelectElement>) {
|
||||
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 (
|
||||
<div className="mb-4 w-full">
|
||||
<Select
|
||||
value={selectedCredentials?.id || (allowNone ? "__none__" : "")}
|
||||
onValueChange={handleValueChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
"h-auto min-h-12 w-full rounded-medium border-zinc-200 p-0 pr-4 shadow-none",
|
||||
variant === "node" && "overflow-hidden",
|
||||
)}
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedCredentials?.id ?? "__none__"}
|
||||
onChange={handleValueChange}
|
||||
disabled={readOnly}
|
||||
className="absolute inset-0 z-10 cursor-pointer opacity-0"
|
||||
aria-label={`Select ${displayName} credential`}
|
||||
>
|
||||
{selectedCredentials ? (
|
||||
<SelectValue key={selectedCredentials.id} asChild>
|
||||
<CredentialRow
|
||||
credential={{
|
||||
id: selectedCredentials.id,
|
||||
title: selectedCredentials.title || undefined,
|
||||
type: selectedCredentials.type,
|
||||
provider: selectedCredentials.provider,
|
||||
}}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
onSelect={() => {}}
|
||||
onDelete={() => {}}
|
||||
readOnly={readOnly}
|
||||
asSelectTrigger={true}
|
||||
variant={variant}
|
||||
/>
|
||||
</SelectValue>
|
||||
{allowNone ? (
|
||||
<option value="__none__">None (skip this credential)</option>
|
||||
) : (
|
||||
<SelectValue key="placeholder" placeholder="Select credential" />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{allowNone && (
|
||||
<SelectItem key="__none__" value="__none__">
|
||||
<div className="flex items-center gap-2">
|
||||
<Text variant="body" className="tracking-tight text-gray-500">
|
||||
None (skip this credential)
|
||||
</Text>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<option value="__none__" disabled hidden>
|
||||
Select a credential
|
||||
</option>
|
||||
)}
|
||||
{credentials.map((credential) => (
|
||||
<SelectItem key={credential.id} value={credential.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Text variant="body" className="tracking-tight">
|
||||
{getCredentialDisplayName(credential, displayName)}
|
||||
</Text>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<option key={credential.id} value={credential.id}>
|
||||
{getCredentialDisplayName(credential, displayName)}
|
||||
</option>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</select>
|
||||
<div className="rounded-medium border border-zinc-200 bg-white">
|
||||
<CredentialRow
|
||||
credential={displayCredential}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
onSelect={() => {}}
|
||||
onDelete={() => {}}
|
||||
readOnly={readOnly}
|
||||
asSelectTrigger={true}
|
||||
variant={variant}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<HTMLDivElement>(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({
|
||||
>
|
||||
<Dialog.Trigger>{triggerSlot}</Dialog.Trigger>
|
||||
<Dialog.Content>
|
||||
{/* Header */}
|
||||
<ModalHeader agent={agent} />
|
||||
<div ref={contentRef} className="flex min-h-full flex-col">
|
||||
<div className="flex-1">
|
||||
{/* Header */}
|
||||
<ModalHeader agent={agent} />
|
||||
|
||||
{/* Content */}
|
||||
{hasAnySetupFields ? (
|
||||
<div className="mt-10">
|
||||
<RunAgentModalContextProvider
|
||||
value={{
|
||||
agent,
|
||||
defaultRunType,
|
||||
presetName,
|
||||
setPresetName,
|
||||
presetDescription,
|
||||
setPresetDescription,
|
||||
inputValues,
|
||||
setInputValue: handleInputChange,
|
||||
agentInputFields,
|
||||
inputCredentials,
|
||||
setInputCredentialsValue: handleCredentialsChange,
|
||||
agentCredentialsInputFields,
|
||||
}}
|
||||
>
|
||||
<ModalRunSection />
|
||||
</RunAgentModalContextProvider>
|
||||
{/* Content */}
|
||||
{hasAnySetupFields ? (
|
||||
<div className="mt-10 pb-32">
|
||||
<RunAgentModalContextProvider
|
||||
value={{
|
||||
agent,
|
||||
defaultRunType,
|
||||
presetName,
|
||||
setPresetName,
|
||||
presetDescription,
|
||||
setPresetDescription,
|
||||
inputValues,
|
||||
setInputValue: handleInputChange,
|
||||
agentInputFields,
|
||||
inputCredentials,
|
||||
setInputCredentialsValue: handleCredentialsChange,
|
||||
agentCredentialsInputFields,
|
||||
}}
|
||||
>
|
||||
<ModalRunSection />
|
||||
</RunAgentModalContextProvider>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Dialog.Footer className="mt-6 bg-white pt-4">
|
||||
<div className="flex items-center justify-end gap-3">
|
||||
{isTriggerRunType ? null : !allRequiredInputsAreSet ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleOpenScheduleModal}
|
||||
disabled={
|
||||
isExecuting ||
|
||||
isSettingUpTrigger ||
|
||||
!allRequiredInputsAreSet
|
||||
}
|
||||
>
|
||||
Schedule Task
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Please set up all required inputs and credentials before
|
||||
scheduling
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleOpenScheduleModal}
|
||||
disabled={
|
||||
isExecuting ||
|
||||
isSettingUpTrigger ||
|
||||
!allRequiredInputsAreSet
|
||||
}
|
||||
>
|
||||
Schedule Task
|
||||
</Button>
|
||||
)}
|
||||
<RunActions
|
||||
defaultRunType={defaultRunType}
|
||||
onRun={handleRun}
|
||||
isExecuting={isExecuting}
|
||||
isSettingUpTrigger={isSettingUpTrigger}
|
||||
isRunReady={allRequiredInputsAreSet}
|
||||
<Dialog.Footer
|
||||
className={`sticky bottom-0 z-10 bg-white pt-4 ${
|
||||
hasOverflow
|
||||
? "border-t border-neutral-100 shadow-[0_-2px_8px_rgba(0,0,0,0.04)]"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-end gap-3">
|
||||
{isTriggerRunType ? null : !allRequiredInputsAreSet ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleOpenScheduleModal}
|
||||
disabled={
|
||||
isExecuting ||
|
||||
isSettingUpTrigger ||
|
||||
!allRequiredInputsAreSet
|
||||
}
|
||||
>
|
||||
Schedule Task
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Please set up all required inputs and credentials
|
||||
before scheduling
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleOpenScheduleModal}
|
||||
disabled={isExecuting || isSettingUpTrigger}
|
||||
>
|
||||
Schedule Task
|
||||
</Button>
|
||||
)}
|
||||
<RunActions
|
||||
defaultRunType={defaultRunType}
|
||||
onRun={handleRun}
|
||||
isExecuting={isExecuting}
|
||||
isSettingUpTrigger={isSettingUpTrigger}
|
||||
isRunReady={allRequiredInputsAreSet}
|
||||
/>
|
||||
</div>
|
||||
<ScheduleAgentModal
|
||||
isOpen={isScheduleModalOpen}
|
||||
onClose={handleCloseScheduleModal}
|
||||
agent={agent}
|
||||
inputValues={inputValues}
|
||||
inputCredentials={inputCredentials}
|
||||
onScheduleCreated={handleScheduleCreated}
|
||||
/>
|
||||
</div>
|
||||
<ScheduleAgentModal
|
||||
isOpen={isScheduleModalOpen}
|
||||
onClose={handleCloseScheduleModal}
|
||||
agent={agent}
|
||||
inputValues={inputValues}
|
||||
inputCredentials={inputCredentials}
|
||||
onScheduleCreated={handleScheduleCreated}
|
||||
/>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Footer>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
</>
|
||||
|
||||
@@ -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<string>;
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
{hasUserCredentials && (
|
||||
<>
|
||||
{userCredentialFields.map(
|
||||
([key, inputSubSchema]: CredentialField) => {
|
||||
const selectedCred = inputCredentials?.[key];
|
||||
|
||||
return (
|
||||
<CredentialsInput
|
||||
key={key}
|
||||
schema={
|
||||
{ ...inputSubSchema, discriminator: undefined } as any
|
||||
}
|
||||
selectedCredentials={selectedCred}
|
||||
onSelectCredentials={(value) => {
|
||||
setInputCredentialsValue(key, value);
|
||||
}}
|
||||
siblingInputs={inputValues}
|
||||
isOptional={!requiredCredentials.has(key)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{hasSystemCredentials && (
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className={hasUserCredentials ? "mt-4" : ""}
|
||||
>
|
||||
<AccordionItem value="system-credentials" className="border-none">
|
||||
<AccordionTrigger className="py-2 text-sm text-muted-foreground hover:no-underline">
|
||||
<div className="flex items-center gap-1">
|
||||
<SlidersHorizontal size={16} weight="bold" /> System credentials
|
||||
{hasMissingSystemCredentials && (
|
||||
<span className="text-destructive">(missing)</span>
|
||||
)}
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-6 px-1 pt-2">
|
||||
{systemCredentialFields.map(
|
||||
([key, inputSubSchema]: CredentialField) => {
|
||||
const selectedCred = inputCredentials?.[key];
|
||||
|
||||
return (
|
||||
<CredentialsInput
|
||||
key={key}
|
||||
schema={
|
||||
{ ...inputSubSchema, discriminator: undefined } as any
|
||||
}
|
||||
selectedCredentials={selectedCred}
|
||||
onSelectCredentials={(value) => {
|
||||
setInputCredentialsValue(key, value);
|
||||
}}
|
||||
siblingInputs={inputValues}
|
||||
isOptional={!requiredCredentials.has(key)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{Object.entries(agentCredentialsInputFields || {}).map(
|
||||
([key, inputSubSchema]) => (
|
||||
<CredentialsInput
|
||||
key={key}
|
||||
schema={
|
||||
{ ...inputSubSchema, discriminator: undefined } as any
|
||||
}
|
||||
selectedCredentials={inputCredentials?.[key]}
|
||||
onSelectCredentials={(value) =>
|
||||
setInputCredentialsValue(key, value)
|
||||
}
|
||||
siblingInputs={inputValues}
|
||||
isOptional={!requiredCredentials.has(key)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<CredentialsGroupedView
|
||||
credentialFields={credentialFields}
|
||||
requiredCredentials={requiredCredentials}
|
||||
/>
|
||||
</ModalSection>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -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<string, unknown>,
|
||||
) {
|
||||
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<string>,
|
||||
inputCredentials?: Record<string, unknown>,
|
||||
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;
|
||||
}
|
||||
@@ -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<Record<string, any>>(
|
||||
callbacks?.initialInputCredentials || {},
|
||||
);
|
||||
|
||||
const [presetName, setPresetName] = useState<string>("");
|
||||
const [presetDescription, setPresetDescription] = useState<string>("");
|
||||
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<string, any> = {};
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<Button
|
||||
variant={selected ? "secondary" : "ghost"}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="m-0 min-w-0 rounded-full p-0 px-1"
|
||||
onClick={onSelectSettings}
|
||||
aria-label="Agent Settings"
|
||||
>
|
||||
<GearIcon
|
||||
size={18}
|
||||
className={selected ? "text-zinc-900" : "text-zinc-600"}
|
||||
/>
|
||||
<GearIcon size={18} className="text-zinc-600" />
|
||||
<Text variant="small">Agent Settings</Text>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
|
||||
export function EmptySchedules() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
|
||||
export function EmptyTemplates() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
|
||||
export function EmptyTriggers() {
|
||||
|
||||
@@ -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 (
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -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 (
|
||||
<SelectedViewLayout
|
||||
agent={props.agent}
|
||||
onSelectSettings={props.onSelectSettings}
|
||||
selectedSettings={props.selectedSettings}
|
||||
>
|
||||
<SelectedViewLayout agent={props.agent}>
|
||||
<div
|
||||
className={cn("flex flex-col gap-4", AGENT_LIBRARY_SECTION_PADDING_X)}
|
||||
>
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex h-full w-full gap-4">
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<SelectedViewLayout
|
||||
agent={agent}
|
||||
banner={banner}
|
||||
onSelectSettings={onSelectSettings}
|
||||
selectedSettings={selectedSettings}
|
||||
>
|
||||
<SelectedViewLayout agent={agent} banner={banner}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<RunDetailHeader agent={agent} run={run} />
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex h-full w-full gap-4">
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<SelectedViewLayout
|
||||
agent={agent}
|
||||
banner={banner}
|
||||
onSelectSettings={onSelectSettings}
|
||||
selectedSettings={selectedSettings}
|
||||
>
|
||||
<SelectedViewLayout agent={agent} banner={banner}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex w-full flex-col gap-0">
|
||||
<RunDetailHeader
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { ArrowLeftIcon } from "@phosphor-icons/react";
|
||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { useAgentSafeMode } from "@/hooks/useAgentSafeMode";
|
||||
import { SelectedViewLayout } from "../SelectedViewLayout";
|
||||
import { ArrowLeftIcon } from "@phosphor-icons/react";
|
||||
import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../../helpers";
|
||||
import { SelectedViewLayout } from "../SelectedViewLayout";
|
||||
|
||||
interface Props {
|
||||
agent: LibraryAgent;
|
||||
@@ -17,7 +17,7 @@ export function SelectedSettingsView({ agent, onClearSelectedRun }: Props) {
|
||||
useAgentSafeMode(agent);
|
||||
|
||||
return (
|
||||
<SelectedViewLayout agent={agent} onSelectSettings={() => {}}>
|
||||
<SelectedViewLayout agent={agent}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
className={`${AGENT_LIBRARY_SECTION_PADDING_X} mb-8 flex items-center gap-3`}
|
||||
@@ -33,15 +33,8 @@ export function SelectedSettingsView({ agent, onClearSelectedRun }: Props) {
|
||||
<Text variant="h2">Agent Settings</Text>
|
||||
</div>
|
||||
|
||||
<div className={AGENT_LIBRARY_SECTION_PADDING_X}>
|
||||
{!hasHITLBlocks ? (
|
||||
<div className="rounded-xl border border-zinc-100 bg-white p-6">
|
||||
<Text variant="body" className="text-muted-foreground">
|
||||
This agent doesn't have any human-in-the-loop blocks, so
|
||||
there are no settings to configure.
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`${AGENT_LIBRARY_SECTION_PADDING_X} space-y-6`}>
|
||||
{hasHITLBlocks ? (
|
||||
<div className="flex w-full max-w-2xl flex-col items-start gap-4 rounded-xl border border-zinc-100 bg-white p-6">
|
||||
<div className="flex w-full items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
@@ -59,6 +52,12 @@ export function SelectedSettingsView({ agent, onClearSelectedRun }: Props) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-xl border border-zinc-100 bg-white p-6">
|
||||
<Text variant="body" className="text-muted-foreground">
|
||||
This agent doesn't have any configurable settings.
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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) {
|
||||
<div
|
||||
className={`${AGENT_LIBRARY_SECTION_PADDING_X} flex-shrink-0 border-b border-zinc-100 pb-0 lg:pb-4`}
|
||||
>
|
||||
{props.banner && <div className="mb-4">{props.banner}</div>}
|
||||
<div className="relative flex w-fit items-center gap-2">
|
||||
{props.banner}
|
||||
<div className="relative flex w-full items-center justify-between">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ name: "My Library", link: "/library" },
|
||||
@@ -33,15 +31,9 @@ export function SelectedViewLayout(props: Props) {
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
{props.agent && props.onSelectSettings && (
|
||||
<div className="absolute -right-8">
|
||||
<AgentSettingsButton
|
||||
agent={props.agent}
|
||||
onSelectSettings={props.onSelectSettings}
|
||||
selected={props.selectedSettings}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute right-0">
|
||||
<AgentSettingsModal agent={props.agent} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-visible">
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 ? (
|
||||
<NewAgentLibraryView />
|
||||
) : (
|
||||
<OldAgentLibraryView />
|
||||
);
|
||||
return <NewAgentLibraryView />;
|
||||
}
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -20,6 +20,7 @@ export function Button(props: ButtonProps) {
|
||||
rightIcon,
|
||||
children,
|
||||
as = "button",
|
||||
asChild: _asChild, // Destructure to prevent passing to DOM
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import type { Meta } from "@storybook/nextjs";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "./Accordion";
|
||||
|
||||
const meta: Meta<typeof Accordion> = {
|
||||
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
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
\`\`\`
|
||||
|
||||
### 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 (
|
||||
<Accordion type="single" collapsible className="w-96">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It comes with default styles that match your design system.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It's animated by default with smooth expand/collapse
|
||||
transitions.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export function Multiple() {
|
||||
return (
|
||||
<Accordion type="multiple" className="w-96">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>First section</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Multiple items can be open at the same time when type is set to
|
||||
"multiple".
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Second section</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Try opening this one while the first is still open.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Third section</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
All three can be open simultaneously.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export function DefaultOpen() {
|
||||
return (
|
||||
<Accordion type="single" collapsible defaultValue="item-2" className="w-96">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Closed by default</AccordionTrigger>
|
||||
<AccordionContent>This item starts closed.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Open by default</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
This item starts open because defaultValue is set to
|
||||
"item-2".
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Also closed</AccordionTrigger>
|
||||
<AccordionContent>This item also starts closed.</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export function WithDisabledItem() {
|
||||
return (
|
||||
<Accordion type="single" collapsible className="w-96">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Available item</AccordionTrigger>
|
||||
<AccordionContent>This item can be toggled.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2" disabled>
|
||||
<AccordionTrigger>Disabled item</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
This content cannot be accessed because the item is disabled.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Another available item</AccordionTrigger>
|
||||
<AccordionContent>This item can also be toggled.</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export function CustomStyled() {
|
||||
return (
|
||||
<Accordion type="single" collapsible className="w-96">
|
||||
<AccordionItem value="item-1" className="border-none">
|
||||
<AccordionTrigger className="rounded-lg bg-zinc-100 px-4 hover:bg-zinc-200 hover:no-underline">
|
||||
Custom styled trigger
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-4 pt-2">
|
||||
You can customize the styling using className props.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2" className="mt-2 border-none">
|
||||
<AccordionTrigger className="rounded-lg bg-blue-50 px-4 text-blue-700 hover:bg-blue-100 hover:no-underline">
|
||||
Blue themed
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-4 pt-2 text-blue-600">
|
||||
Each item can have different styles.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
"use client";
|
||||
|
||||
export {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
@@ -22,6 +22,9 @@ export function DrawerWrap({
|
||||
handleClose,
|
||||
isForceOpen,
|
||||
}: Props) {
|
||||
const accessibleTitle = title ?? "Dialog";
|
||||
const hasVisibleTitle = Boolean(title);
|
||||
|
||||
const closeBtn = (
|
||||
<Button
|
||||
variant="link"
|
||||
@@ -44,15 +47,19 @@ export function DrawerWrap({
|
||||
>
|
||||
<div
|
||||
className={`flex w-full shrink-0 items-center justify-between ${
|
||||
title ? "pb-6" : "pb-0"
|
||||
hasVisibleTitle ? "pb-6" : "pb-0"
|
||||
}`}
|
||||
>
|
||||
{title ? (
|
||||
<Drawer.Title className={drawerStyles.title}>{title}</Drawer.Title>
|
||||
) : null}
|
||||
{hasVisibleTitle ? (
|
||||
<Drawer.Title className={drawerStyles.title}>
|
||||
{accessibleTitle}
|
||||
</Drawer.Title>
|
||||
) : (
|
||||
<Drawer.Title className="sr-only">{accessibleTitle}</Drawer.Title>
|
||||
)}
|
||||
|
||||
{!isForceOpen ? (
|
||||
title ? (
|
||||
hasVisibleTitle ? (
|
||||
closeBtn
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { FieldProps, getUiOptions } from "@rjsf/utils";
|
||||
import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
|
||||
import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInput";
|
||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||
import {
|
||||
BlockIOCredentialsSubSchema,
|
||||
CredentialsMetaInput,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs";
|
||||
import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
|
||||
import { FieldProps, getUiOptions } from "@rjsf/utils";
|
||||
import { useMemo } from "react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { CredentialFieldTitle } from "./components/CredentialFieldTitle";
|
||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||
|
||||
export const CredentialsField = (props: FieldProps) => {
|
||||
const { formData, onChange, schema, registry, fieldPathId, required } = props;
|
||||
|
||||
57
autogpt_platform/frontend/src/components/ui/accordion.tsx
Normal file
57
autogpt_platform/frontend/src/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn("border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-neutral-500 transition-transform duration-200 dark:text-neutral-400" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
||||
@@ -352,6 +352,10 @@ export default class BackendAPI {
|
||||
return this._get("/integrations/providers");
|
||||
}
|
||||
|
||||
listSystemProviders(): Promise<string[]> {
|
||||
return this._get("/integrations/providers/system");
|
||||
}
|
||||
|
||||
listCredentials(provider?: string): Promise<CredentialsMetaResponse[]> {
|
||||
return this._get(
|
||||
provider
|
||||
|
||||
@@ -625,6 +625,7 @@ export type CredentialsMetaResponse = {
|
||||
scopes?: Array<string>;
|
||||
username?: string;
|
||||
host?: string;
|
||||
is_system?: boolean;
|
||||
};
|
||||
|
||||
/* Mirror of backend/server/integrations/router.py:CredentialsDeletionResponse */
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createContext, useCallback, useEffect, useState } from "react";
|
||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||
import { useToastOnFail } from "@/components/molecules/Toast/use-toast";
|
||||
import {
|
||||
APIKeyCredentials,
|
||||
CredentialsDeleteNeedConfirmationResponse,
|
||||
@@ -10,8 +9,9 @@ import {
|
||||
UserPasswordCredentials,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { useToastOnFail } from "@/components/molecules/Toast/use-toast";
|
||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||
import { toDisplayName } from "@/providers/agent-credentials/helper";
|
||||
import { createContext, useCallback, useEffect, useState } from "react";
|
||||
|
||||
type APIKeyCredentialsCreatable = Omit<
|
||||
APIKeyCredentials,
|
||||
@@ -32,6 +32,8 @@ export type CredentialsProviderData = {
|
||||
provider: CredentialsProviderName;
|
||||
providerName: string;
|
||||
savedCredentials: CredentialsMetaResponse[];
|
||||
/** Whether this provider has platform credits available (system credentials) */
|
||||
isSystemProvider: boolean;
|
||||
oAuthCallback: (
|
||||
code: string,
|
||||
state_token: string,
|
||||
@@ -68,6 +70,9 @@ export default function CredentialsProvider({
|
||||
const [providers, setProviders] =
|
||||
useState<CredentialsProvidersContextType | null>(null);
|
||||
const [providerNames, setProviderNames] = useState<string[]>([]);
|
||||
const [systemProviders, setSystemProviders] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
const { isLoggedIn } = useSupabase();
|
||||
const api = useBackendAPI();
|
||||
const onFailToast = useToastOnFail();
|
||||
@@ -218,17 +223,7 @@ export default function CredentialsProvider({
|
||||
[api, onFailToast],
|
||||
);
|
||||
|
||||
// Fetch provider names on mount
|
||||
useEffect(() => {
|
||||
api
|
||||
.listProviders()
|
||||
.then((names) => {
|
||||
setProviderNames(names);
|
||||
})
|
||||
.catch(onFailToast("load provider names"));
|
||||
}, [api, onFailToast]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadCredentials = useCallback(() => {
|
||||
if (!isLoggedIn || providerNames.length === 0) {
|
||||
if (isLoggedIn == false) setProviders({});
|
||||
return;
|
||||
@@ -251,27 +246,32 @@ export default function CredentialsProvider({
|
||||
setProviders((prev) => ({
|
||||
...prev,
|
||||
...Object.fromEntries(
|
||||
providerNames.map((provider) => [
|
||||
provider,
|
||||
{
|
||||
providerNames.map((provider) => {
|
||||
const providerCredentials = credentialsByProvider[provider] ?? [];
|
||||
|
||||
return [
|
||||
provider,
|
||||
providerName: toDisplayName(provider as string),
|
||||
savedCredentials: credentialsByProvider[provider] ?? [],
|
||||
oAuthCallback: (code: string, state_token: string) =>
|
||||
oAuthCallback(provider, code, state_token),
|
||||
createAPIKeyCredentials: (
|
||||
credentials: APIKeyCredentialsCreatable,
|
||||
) => createAPIKeyCredentials(provider, credentials),
|
||||
createUserPasswordCredentials: (
|
||||
credentials: UserPasswordCredentialsCreatable,
|
||||
) => createUserPasswordCredentials(provider, credentials),
|
||||
createHostScopedCredentials: (
|
||||
credentials: HostScopedCredentialsCreatable,
|
||||
) => createHostScopedCredentials(provider, credentials),
|
||||
deleteCredentials: (id: string, force: boolean = false) =>
|
||||
deleteCredentials(provider, id, force),
|
||||
} satisfies CredentialsProviderData,
|
||||
]),
|
||||
{
|
||||
provider,
|
||||
providerName: toDisplayName(provider as string),
|
||||
savedCredentials: providerCredentials,
|
||||
isSystemProvider: systemProviders.has(provider),
|
||||
oAuthCallback: (code: string, state_token: string) =>
|
||||
oAuthCallback(provider, code, state_token),
|
||||
createAPIKeyCredentials: (
|
||||
credentials: APIKeyCredentialsCreatable,
|
||||
) => createAPIKeyCredentials(provider, credentials),
|
||||
createUserPasswordCredentials: (
|
||||
credentials: UserPasswordCredentialsCreatable,
|
||||
) => createUserPasswordCredentials(provider, credentials),
|
||||
createHostScopedCredentials: (
|
||||
credentials: HostScopedCredentialsCreatable,
|
||||
) => createHostScopedCredentials(provider, credentials),
|
||||
deleteCredentials: (id: string, force: boolean = false) =>
|
||||
deleteCredentials(provider, id, force),
|
||||
} satisfies CredentialsProviderData,
|
||||
];
|
||||
}),
|
||||
),
|
||||
}));
|
||||
})
|
||||
@@ -280,6 +280,7 @@ export default function CredentialsProvider({
|
||||
api,
|
||||
isLoggedIn,
|
||||
providerNames,
|
||||
systemProviders,
|
||||
createAPIKeyCredentials,
|
||||
createUserPasswordCredentials,
|
||||
createHostScopedCredentials,
|
||||
@@ -288,6 +289,20 @@ export default function CredentialsProvider({
|
||||
onFailToast,
|
||||
]);
|
||||
|
||||
// Fetch provider names and system providers on mount
|
||||
useEffect(() => {
|
||||
Promise.all([api.listProviders(), api.listSystemProviders()])
|
||||
.then(([names, systemList]) => {
|
||||
setProviderNames(names);
|
||||
setSystemProviders(new Set(systemList));
|
||||
})
|
||||
.catch(onFailToast("Load provider names"));
|
||||
}, [api, onFailToast]);
|
||||
|
||||
useEffect(() => {
|
||||
loadCredentials();
|
||||
}, [loadCredentials]);
|
||||
|
||||
return (
|
||||
<CredentialsProvidersContext.Provider value={providers}>
|
||||
{children}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
import { hasTextContent, hasUrl, isVisible } from "./utils/assertion";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
import { getTestUser } from "./utils/auth";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { BuildPage } from "./pages/build.page";
|
||||
import * as LibraryPage from "./pages/library.page";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { hasTextContent, hasUrl, isVisible } from "./utils/assertion";
|
||||
import { getTestUser } from "./utils/auth";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const buildPage = new BuildPage(page);
|
||||
const testUser = await getTestUser();
|
||||
|
||||
const { getId, getText } = getSelectors(page);
|
||||
const { getId } = getSelectors(page);
|
||||
|
||||
await page.goto("/login");
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
@@ -41,7 +41,8 @@ test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/library");
|
||||
await LibraryPage.clickFirstAgent(page);
|
||||
await LibraryPage.waitForAgentPageLoad(page);
|
||||
await isVisible(getText("Test Agent"), 8000);
|
||||
const { getRole } = getSelectors(page);
|
||||
await isVisible(getRole("heading", "Test Agent"), 8000);
|
||||
});
|
||||
|
||||
test("shows badge with count when agent is running", async ({ page }) => {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import test, { expect } from "@playwright/test";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { hasUrl, isHidden } from "./utils/assertion";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto("/login");
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
import { hasUrl } from "./utils/assertion";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
|
||||
@@ -8,7 +8,8 @@ test.describe("API Keys Page", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto("/login");
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ export const TEST_CREDENTIALS = {
|
||||
password: "testpassword123",
|
||||
} as const;
|
||||
|
||||
export function getTestUserWithLibraryAgents() {
|
||||
return TEST_CREDENTIALS;
|
||||
}
|
||||
|
||||
// Dummy constant to help developers identify agents that don't need input
|
||||
export const DummyInput = "DummyInput";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
import path from "path";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LibraryPage } from "./pages/library.page";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { hasUrl } from "./utils/assertion";
|
||||
@@ -14,7 +14,8 @@ test.describe("Library", () => {
|
||||
|
||||
await page.goto("/login");
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { MarketplacePage } from "./pages/marketplace.page";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { isVisible, hasUrl, matchesUrl } from "./utils/assertion";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
import { MarketplacePage } from "./pages/marketplace.page";
|
||||
import { hasUrl, isVisible, matchesUrl } from "./utils/assertion";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
|
||||
function escapeRegExp(value: string) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
test.describe("Marketplace Agent Page - Basic Functionality", () => {
|
||||
test("User can access agent page when logged out", async ({ page }) => {
|
||||
const marketplacePage = new MarketplacePage(page);
|
||||
@@ -24,7 +28,8 @@ test.describe("Marketplace Agent Page - Basic Functionality", () => {
|
||||
const marketplacePage = new MarketplacePage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
await marketplacePage.goto(page);
|
||||
await hasUrl(page, "/marketplace");
|
||||
@@ -85,7 +90,8 @@ test.describe("Marketplace Agent Page - Basic Functionality", () => {
|
||||
const marketplacePage = new MarketplacePage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
await marketplacePage.goto(page);
|
||||
|
||||
@@ -93,6 +99,12 @@ test.describe("Marketplace Agent Page - Basic Functionality", () => {
|
||||
await firstStoreCard.click();
|
||||
await page.waitForURL("**/marketplace/agent/**");
|
||||
|
||||
const agentTitle = await getId("agent-title").textContent();
|
||||
if (!agentTitle || !agentTitle.trim()) {
|
||||
throw new Error("Agent title not found on marketplace agent page");
|
||||
}
|
||||
const agentName = agentTitle.trim();
|
||||
|
||||
const addToLibraryButton = getId("agent-add-library-button");
|
||||
await isVisible(addToLibraryButton);
|
||||
await addToLibraryButton.click();
|
||||
@@ -101,9 +113,8 @@ test.describe("Marketplace Agent Page - Basic Functionality", () => {
|
||||
await isVisible(addSuccessMessage);
|
||||
|
||||
await page.waitForURL("**/library/agents/**");
|
||||
const agentNameOnLibrary = await getId("agent-title").textContent();
|
||||
expect(
|
||||
agentNameOnLibrary && agentNameOnLibrary.trim().length,
|
||||
).toBeGreaterThan(0);
|
||||
await expect(page).toHaveTitle(
|
||||
new RegExp(`${escapeRegExp(agentName)} - Library - AutoGPT Platform`),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { test } from "@playwright/test";
|
||||
import { MarketplacePage } from "./pages/marketplace.page";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { isVisible, hasUrl, matchesUrl } from "./utils/assertion";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
import { MarketplacePage } from "./pages/marketplace.page";
|
||||
import { hasUrl, isVisible, matchesUrl } from "./utils/assertion";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
|
||||
test.describe("Marketplace Creator Page – Basic Functionality", () => {
|
||||
@@ -25,7 +25,8 @@ test.describe("Marketplace Creator Page – Basic Functionality", () => {
|
||||
const marketplacePage = new MarketplacePage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
|
||||
await marketplacePage.goto(page);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { MarketplacePage } from "./pages/marketplace.page";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { isVisible, hasUrl, hasMinCount, matchesUrl } from "./utils/assertion";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
import { MarketplacePage } from "./pages/marketplace.page";
|
||||
import { hasMinCount, hasUrl, isVisible, matchesUrl } from "./utils/assertion";
|
||||
|
||||
test.describe("Marketplace – Basic Functionality", () => {
|
||||
test("User can access marketplace page when logged out", async ({ page }) => {
|
||||
@@ -24,7 +24,8 @@ test.describe("Marketplace – Basic Functionality", () => {
|
||||
const marketplacePage = new MarketplacePage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
|
||||
await marketplacePage.goto(page);
|
||||
|
||||
@@ -455,6 +455,30 @@ export async function navigateToAgentByName(
|
||||
|
||||
export async function clickRunButton(page: Page): Promise<void> {
|
||||
const { getId } = getSelectors(page);
|
||||
|
||||
const setupTaskButton = page.getByRole("button", {
|
||||
name: /Setup your task/i,
|
||||
});
|
||||
|
||||
if (await setupTaskButton.isVisible()) {
|
||||
await setupTaskButton.click();
|
||||
const startTaskButton = page
|
||||
.getByRole("button", { name: /Start Task/i })
|
||||
.first();
|
||||
await startTaskButton.click();
|
||||
return;
|
||||
}
|
||||
|
||||
const newTaskButton = page.getByRole("button", { name: /New task/i });
|
||||
if (await newTaskButton.isVisible()) {
|
||||
await newTaskButton.click();
|
||||
const startTaskButton = page
|
||||
.getByRole("button", { name: /Start Task/i })
|
||||
.first();
|
||||
await startTaskButton.click();
|
||||
return;
|
||||
}
|
||||
|
||||
const runButton = getId("agent-run-button");
|
||||
const runAgainButton = getId("run-again-button");
|
||||
|
||||
@@ -463,7 +487,7 @@ export async function clickRunButton(page: Page): Promise<void> {
|
||||
} else if (await runAgainButton.isVisible()) {
|
||||
await runAgainButton.click();
|
||||
} else {
|
||||
throw new Error("Neither run button nor run again button is visible");
|
||||
throw new Error("Could not find run/start task button");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
import { ProfileFormPage } from "./pages/profile-form.page";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { ProfileFormPage } from "./pages/profile-form.page";
|
||||
import { hasUrl } from "./utils/assertion";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
|
||||
test.describe("Profile Form", () => {
|
||||
let profileFormPage: ProfileFormPage;
|
||||
@@ -12,7 +12,8 @@ test.describe("Profile Form", () => {
|
||||
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import test from "@playwright/test";
|
||||
import { getTestUserWithLibraryAgents } from "./credentials";
|
||||
import { LoginPage } from "./pages/login.page";
|
||||
import { TEST_CREDENTIALS } from "./credentials";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
import {
|
||||
hasUrl,
|
||||
isDisabled,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
isHidden,
|
||||
isVisible,
|
||||
} from "./utils/assertion";
|
||||
import { getSelectors } from "./utils/selectors";
|
||||
|
||||
test("user can publish an agent through the complete flow", async ({
|
||||
page,
|
||||
@@ -17,7 +17,8 @@ test("user can publish an agent through the complete flow", async ({
|
||||
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto("/login");
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
|
||||
await page.goto("/marketplace");
|
||||
@@ -95,7 +96,8 @@ test("should validate all form fields in publish agent form", async ({
|
||||
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto("/login");
|
||||
await loginPage.login(TEST_CREDENTIALS.email, TEST_CREDENTIALS.password);
|
||||
const richUser = getTestUserWithLibraryAgents();
|
||||
await loginPage.login(richUser.email, richUser.password);
|
||||
await hasUrl(page, "/marketplace");
|
||||
|
||||
await page.goto("/marketplace");
|
||||
|
||||
@@ -22,13 +22,7 @@ const config = {
|
||||
poppins: ["var(--font-poppins)"],
|
||||
},
|
||||
colors: {
|
||||
// *** APPROVED DESIGN SYSTEM COLORS ***
|
||||
// These are the ONLY colors that should be used in our app
|
||||
...colors,
|
||||
|
||||
// Legacy colors - DO NOT USE THESE IN NEW CODE
|
||||
// These are kept only to prevent breaking existing styles
|
||||
// Use the approved design system colors above instead
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
@@ -63,70 +57,66 @@ const config = {
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
customGray: {
|
||||
100: "#d9d9d9",
|
||||
200: "#a8a8a8",
|
||||
300: "#878787",
|
||||
400: "#646464",
|
||||
500: "#474747",
|
||||
600: "#282828",
|
||||
700: "#272727",
|
||||
"100": "#d9d9d9",
|
||||
"200": "#a8a8a8",
|
||||
"300": "#878787",
|
||||
"400": "#646464",
|
||||
"500": "#474747",
|
||||
"600": "#282828",
|
||||
"700": "#272727",
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
// Tailwind spacing + custom sizes
|
||||
0: "0rem", // 0px
|
||||
0.5: "0.125rem", // 2px
|
||||
1: "0.25rem", // 4px
|
||||
1.5: "0.375rem", // 6px
|
||||
2: "0.5rem", // 8px
|
||||
2.5: "0.625rem", // 10px
|
||||
3: "0.75rem", // 12px
|
||||
3.5: "0.875rem", // 14px
|
||||
4: "1rem", // 16px
|
||||
5: "1.25rem", // 20px
|
||||
6: "1.5rem", // 24px
|
||||
7: "1.75rem", // 28px
|
||||
7.5: "1.875rem", // 30px
|
||||
8: "2rem", // 32px
|
||||
8.5: "2.125rem", // 34px
|
||||
9: "2.25rem", // 36px
|
||||
10: "2.5rem", // 40px
|
||||
11: "2.75rem", // 44px
|
||||
12: "3rem", // 48px
|
||||
14: "3.5rem", // 56px
|
||||
16: "4rem", // 64px
|
||||
18: "4.5rem", // 72px
|
||||
20: "5rem", // 80px
|
||||
24: "6rem", // 96px
|
||||
28: "7rem", // 112px
|
||||
32: "8rem", // 128px
|
||||
36: "9rem", // 144px
|
||||
40: "10rem", // 160px
|
||||
44: "11rem", // 176px
|
||||
48: "12rem", // 192px
|
||||
52: "13rem", // 208px
|
||||
56: "14rem", // 224px
|
||||
60: "15rem", // 240px
|
||||
64: "16rem", // 256px
|
||||
68: "17rem", // 272px
|
||||
70: "17.5rem", // 280px
|
||||
71: "17.75rem", // 284px
|
||||
72: "18rem", // 288px
|
||||
76: "19rem", // 304px
|
||||
80: "20rem", // 320px
|
||||
96: "24rem", // 384px
|
||||
"0": "0rem",
|
||||
"1": "0.25rem",
|
||||
"2": "0.5rem",
|
||||
"3": "0.75rem",
|
||||
"4": "1rem",
|
||||
"5": "1.25rem",
|
||||
"6": "1.5rem",
|
||||
"7": "1.75rem",
|
||||
"8": "2rem",
|
||||
"9": "2.25rem",
|
||||
"10": "2.5rem",
|
||||
"11": "2.75rem",
|
||||
"12": "3rem",
|
||||
"14": "3.5rem",
|
||||
"16": "4rem",
|
||||
"18": "4.5rem",
|
||||
"20": "5rem",
|
||||
"24": "6rem",
|
||||
"28": "7rem",
|
||||
"32": "8rem",
|
||||
"36": "9rem",
|
||||
"40": "10rem",
|
||||
"44": "11rem",
|
||||
"48": "12rem",
|
||||
"52": "13rem",
|
||||
"56": "14rem",
|
||||
"60": "15rem",
|
||||
"64": "16rem",
|
||||
"68": "17rem",
|
||||
"70": "17.5rem",
|
||||
"71": "17.75rem",
|
||||
"72": "18rem",
|
||||
"76": "19rem",
|
||||
"80": "20rem",
|
||||
"96": "24rem",
|
||||
"0.5": "0.125rem",
|
||||
"1.5": "0.375rem",
|
||||
"2.5": "0.625rem",
|
||||
"3.5": "0.875rem",
|
||||
"7.5": "1.875rem",
|
||||
"8.5": "2.125rem",
|
||||
},
|
||||
borderRadius: {
|
||||
// Design system border radius tokens from Figma
|
||||
xsmall: "0.25rem", // 4px
|
||||
small: "0.5rem", // 8px
|
||||
medium: "0.75rem", // 12px
|
||||
large: "1rem", // 16px
|
||||
xlarge: "1.25rem", // 20px
|
||||
"2xlarge": "1.5rem", // 24px
|
||||
full: "9999px", // For pill buttons
|
||||
|
||||
// Legacy values - kept for backward compatibility
|
||||
xsmall: "0.25rem",
|
||||
small: "0.5rem",
|
||||
medium: "0.75rem",
|
||||
large: "1rem",
|
||||
xlarge: "1.25rem",
|
||||
"2xlarge": "1.5rem",
|
||||
full: "9999px",
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
@@ -136,16 +126,28 @@ const config = {
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
from: {
|
||||
height: "0",
|
||||
},
|
||||
to: {
|
||||
height: "var(--radix-accordion-content-height)",
|
||||
},
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
from: {
|
||||
height: "var(--radix-accordion-content-height)",
|
||||
},
|
||||
to: {
|
||||
height: "0",
|
||||
},
|
||||
},
|
||||
"fade-in": {
|
||||
"0%": { opacity: "0" }, // Start with opacity 0
|
||||
"100%": { opacity: "1" }, // End with opacity 1
|
||||
"0%": {
|
||||
opacity: "0",
|
||||
},
|
||||
"100%": {
|
||||
opacity: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
|
||||
Reference in New Issue
Block a user