Add ChatGPT Codex OAuth discovery banner
Add a discovery hero for the OAuth plugin below the Telegram, Email, and WhatsApp integration cards. The banner uses the supplied OpenAI artwork, opens the OAuth plugin settings, and adapts its copy when a ChatGPT/Codex account is already connected.
Alessandro committed
Apr 28, 2026 at 15:38 UTC
332ffdcf4d59f0017bbac88d03099d34963993cd
4 files changed
+77
-3
plugins/_discovery/extensions/python/banners/10_discovery_cards.py
+29
-1
@@ -1,16 +1,26 @@
1
from helpers.extension import Extension
2
from helpers import plugins
3
4
+
5
class DiscoveryCardsExtension(Extension):
6
"""Injects discovery cards into the banners list."""
7
8
+ def _codex_oauth_connected(self) -> bool:
9
+ try:
10
+ from plugins._oauth.helpers import codex
11
+
12
+ return bool(codex.status().get("connected"))
13
+ except Exception:
14
+ return False
15
+
16
async def execute(self, banners: list = [], frontend_context: dict = {}, **kwargs):
17
# Optional logic: only show specific cards if plugins aren't already configured.
18
# Telegram, Email, Whatsapp are built-in, so we only need to check if they've been configured.
10
-
19
+
20
telegram_config = plugins.get_plugin_config("_telegram_integration") or {}
21
email_config = plugins.get_plugin_config("_email_integration") or {}
22
whatsapp_config = plugins.get_plugin_config("_whatsapp_integration") or {}
23
+ codex_oauth_connected = self._codex_oauth_connected()
24
25
# 1. Plugin Hub Hero
26
banners.append({
@@ -76,3 +86,21 @@ class DiscoveryCardsExtension(Extension):
86
"priority": 50,
87
"show_in_onboarding": True
88
})
89
+
90
+ # 5. Codex/ChatGPT OAuth
91
+ banners.append({
92
+ "id": "discovery-codex-oauth",
93
+ "type": "hero",
94
+ "placement": "after-features",
95
+ "title": "Connect ChatGPT/Codex" if not codex_oauth_connected else "ChatGPT/Codex Connected",
96
+ "description": "Link your account through the OAuth plugin to unlock account-backed Codex models locally."
97
+ if not codex_oauth_connected
98
+ else "Manage your OAuth connection and account-backed Codex models.",
99
+ "thumbnail": "/plugins/_discovery/webui/assets/hero-openai-oauth.png",
100
+ "icon": "key",
101
+ "cta_text": "Connect Account" if not codex_oauth_connected else "Manage OAuth",
102
+ "cta_action": "open-plugin-config:_oauth",
103
+ "dismissible": True,
104
+ "priority": 40,
105
+ "show_in_onboarding": True
106
+ })
plugins/_discovery/extensions/webui/welcome-actions-end/discovery-cards.html
+39
-1
@@ -10,7 +10,7 @@
10
x-show="!($store.welcomeStore?.banners || []).some(b => b.id === 'missing-api-key') && ($store.discoveryStore.cards.length > 0 || $store.discoveryStore.hasDismissedCards)">
11
12
<div class="discovery-section">
13
- <template x-for="card in $store.discoveryStore.heroCards" :key="card.id">
13
+ <template x-for="card in $store.discoveryStore.topHeroCards" :key="card.id">
14
<article class="discovery-hero"
15
role="button"
16
tabindex="0"
@@ -89,6 +89,44 @@
89
</template>
90
</div>
91
92
+ <template x-for="card in $store.discoveryStore.bottomHeroCards" :key="card.id">
93
+ <article class="discovery-hero"
94
+ role="button"
95
+ tabindex="0"
96
+ @click="$store.discoveryStore.executeCta(card.cta_action)"
97
+ @keydown.enter.prevent="$store.discoveryStore.executeCta(card.cta_action)"
98
+ @keydown.space.prevent="$store.discoveryStore.executeCta(card.cta_action)">
99
+ <button class="discovery-dismiss"
100
+ type="button"
101
+ x-show="card.dismissible"
102
+ @click.stop="$store.discoveryStore.dismissCard(card.id)"
103
+ :aria-label="`Dismiss ${card.title}`"
104
+ title="Dismiss">
105
+ <span class="material-symbols-outlined">close</span>
106
+ </button>
107
+
108
+ <div class="discovery-hero-content">
109
+ <h3 class="discovery-hero-title" x-text="card.title"></h3>
110
+ <p class="discovery-hero-desc" x-text="card.description"></p>
111
+ <button class="btn btn-ok"
112
+ type="button"
113
+ @click.stop="$store.discoveryStore.executeCta(card.cta_action)">
114
+ <span x-text="card.cta_text"></span>
115
+ <span class="material-symbols-outlined">arrow_forward</span>
116
+ </button>
117
+ </div>
118
+
119
+ <div class="discovery-hero-thumb">
120
+ <template x-if="card.thumbnail">
121
+ <img :src="card.thumbnail" :alt="card.title" loading="lazy">
122
+ </template>
123
+ <template x-if="!card.thumbnail && card.icon">
124
+ <span class="material-symbols-outlined discovery-hero-icon" x-text="card.icon"></span>
125
+ </template>
126
+ </div>
127
+ </article>
128
+ </template>
129
+
130
<div class="discovery-undismiss" x-show="$store.discoveryStore.hasDismissedCards">
131
<button type="button"
132
class="discovery-undismiss-btn"
plugins/_discovery/webui/assets/hero-openai-oauth.png
Binary files /dev/null and b/plugins/_discovery/webui/assets/hero-openai-oauth.png differ
plugins/_discovery/webui/discovery-store.js
+9
-1
@@ -116,8 +116,16 @@ const model = {
116
117
// --- Computed ---
118
119
+ get topHeroCards() {
120
+ return this.cards.filter((card) => card.type === "hero" && card.placement !== "after-features");
121
+ },
122
+
123
+ get bottomHeroCards() {
124
+ return this.cards.filter((card) => card.type === "hero" && card.placement === "after-features");
125
+ },
126
+
127
get heroCards() {
120
- return this.cards.filter((card) => card.type === "hero");
128
+ return [...this.topHeroCards, ...this.bottomHeroCards];
129
},
130
131
get featureCards() {