Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Tech Stack: Python 3.12+ | Flask | Alpine.js | LiteLLM | WebSocket (Socket.io)
Dev Server: python run_ui.py (runs on http://localhost:50001 by default)
Run Tests: pytest (standard) or pytest tests/test_name.py (file-scoped)
Documentation: README.md | docs/
Frontend Deep Dives: [Component System](docs/agents/AGENTS.components.md) | [Modal System](docs/agents/AGENTS.modals.md) | [Plugin Architecture](docs/agents/AGENTS.plugins.md)
Frontend Deep Dives: [Component System](docs/agents/AGENTS.components.md) | [Modal System](docs/agents/AGENTS.modals.md) | [Plugin Architecture](docs/agents/AGENTS.plugins.md) | [Banners & Discovery](docs/agents/AGENTS.banners.md)

---

Expand Down
95 changes: 95 additions & 0 deletions docs/agents/AGENTS.banners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Creating Discovery Cards and Banners

Agent Zero allows plugin developers to surface UI elements using the `banners` extension point. This allows your plugin to present information, prompts, or actionable "discovery cards" directly on the Welcome Screen without needing to inject arbitrary HTML into the frontend.

## The `banners` Extension Point

Banners are collected on the backend and sent to the frontend UI as an array of dictionaries. By appending to the `banners` array inside a Python extension, you can easily surface your plugin to the user.

To inject a banner, you create a Python extension script hooking into `banners`.

### Where to put your extension script

Create a python file in your plugin's extensions folder:
`plugins/<your_plugin>/extensions/python/banners/10_my_plugin_banner.py`

*(Note: the `10_` prefix is for ordering; extensions run in alphabetical order).*

## Banner Types

The UI distinguishes banners primarily by the `type` property.

### 1. Alert Banners (`info`, `warning`, `error`)
These are standard top-level alerts displayed on the welcome screen.

```python
banners.append({
"id": "my-plugin-warning",
"type": "warning",
"priority": 90,
"title": "My Plugin Issue",
"html": "<strong>Action required:</strong> Please configure your settings.",
"dismissible": True,
})
```

### 2. Discovery Cards (`hero`, `feature`)
These are rich, interactive cards displayed in the Discovery section. They are designed to prompt the user to try new plugins or features.

* `hero`: A wide, prominent card. Usually reserved for core system features (e.g., the Plugin Hub).
* `feature`: A smaller card in a grid layout. This is the **recommended type** for plugin contributors to showcase their plugin.

### Anatomy of a Discovery Card

Here is an example of injecting a `feature` card for a custom plugin:

```python
from helpers.extension import Extension
from helpers import plugins

class MyPluginDiscoveryCard(Extension):
"""Injects a discovery card for My Custom Plugin."""

async def execute(self, banners: list = [], frontend_context: dict = {}, **kwargs):
# 1. Condition Check
# Only show the discovery card if the user hasn't configured the plugin yet.
config = plugins.get_plugin_config("my_custom_plugin") or {}

# If the API key is already set, we don't need to advertise the setup!
if config.get("api_key"):
return

# 2. Add the Card
banners.append({
"id": "discovery-my-custom-plugin",
"type": "feature", # 'feature' or 'hero'
"title": "Connect My Service", # Card title
"description": "Unlock amazing capabilities by linking your account.",

# Visuals (use either thumbnail OR icon)
"thumbnail": "/plugins/my_custom_plugin/assets/thumb.png", # Path to image
"icon": "bolt", # Or a Material Symbol icon name

# Call To Action (CTA)
"cta_text": "Setup Now",
"cta_action": "open-plugin-config:my_custom_plugin", # Opens your plugin's config modal

# Behavior
"dismissible": True, # Let the user hide it
"priority": 40, # Higher numbers appear first
})
```

## Call To Action (CTA) Actions

When a user clicks the button on a discovery card, the `cta_action` string determines what happens. The frontend currently supports the following actions:

* `open-plugin-config:<plugin_folder_name>`: Automatically opens the settings modal for the specified plugin. (e.g., `open-plugin-config:_telegram_integration`).
* `open-plugin-hub`: Opens the main Plugin Hub UI.
* `open-url:<url>`: Opens a web link in a new browser tab. (e.g., `open-url:https://example.com/docs`).

## Best Practices

1. **Check Configuration First**: Always check your plugin's configuration before injecting a card. If the user has already set up your plugin, they shouldn't keep seeing a discovery card asking them to set it up.
2. **Unique IDs**: Ensure your banner `id` is highly unique (e.g., prefix it with your plugin name) to avoid collisions with other plugins.
3. **Use `feature` type**: Community plugins should stick to the `feature` type rather than `hero` to ensure a clean grid layout for users.
77 changes: 77 additions & 0 deletions plugins/_discovery/extensions/python/banners/10_discovery_cards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from helpers.extension import Extension
from helpers import plugins

class DiscoveryCardsExtension(Extension):
"""Injects discovery cards into the banners list."""

async def execute(self, banners: list = [], frontend_context: dict = {}, **kwargs):
# Optional logic: only show specific cards if plugins aren't already configured.
# Telegram, Email, Whatsapp are built-in, so we only need to check if they've been configured.

telegram_config = plugins.get_plugin_config("_telegram_integration") or {}
email_config = plugins.get_plugin_config("_email_integration") or {}
whatsapp_config = plugins.get_plugin_config("_whatsapp_integration") or {}

# 1. Plugin Hub Hero
banners.append({
"id": "discovery-plugin-hub",
"type": "hero",
"title": "Discover the Plugin Hub",
"description": "Extend Agent Zero with integrations, tools, and automations from the community.",
"thumbnail": "/plugins/_discovery/webui/assets/hero-plugin-hub.png",
"icon": "extension",
"cta_text": "Explore Plugins",
"cta_action": "open-plugin-hub",
"dismissible": True,
"priority": 100,
"show_in_onboarding": True
})

# 2. Telegram
if not telegram_config.get("bot_token"):
banners.append({
"id": "discovery-telegram",
"type": "feature",
"title": "Connect Telegram",
"description": "Chat with Agent Zero from Telegram wherever you are.",
"thumbnail": "/plugins/_discovery/webui/assets/thumb-telegram.png",
"icon": "send",
"cta_text": "Setup",
"cta_action": "open-plugin-config:_telegram_integration",
"dismissible": True,
"priority": 50,
"show_in_onboarding": True
})

# 3. Email
if not email_config.get("imap_username") and not email_config.get("smtp_username"):
banners.append({
"id": "discovery-email",
"type": "feature",
"title": "Setup Email",
"description": "Let Agent Zero read and send emails on your behalf.",
"thumbnail": "/plugins/_discovery/webui/assets/thumb-email.png",
"icon": "mail",
"cta_text": "Setup",
"cta_action": "open-plugin-config:_email_integration",
"dismissible": True,
"priority": 50,
"show_in_onboarding": True
})

# 4. WhatsApp
if not whatsapp_config.get("phone_number_id"):
banners.append({
"id": "discovery-whatsapp",
"type": "feature",
"title": "Connect WhatsApp",
"description": "Send and receive WhatsApp messages through A0.",
"thumbnail": "/plugins/_discovery/webui/assets/thumb-whatsapp.png",
"icon": "chat",
"cta_text": "Setup",
"cta_action": "open-plugin-config:_whatsapp_integration",
"dismissible": True,
"priority": 50,
"show_in_onboarding": True
})

Loading