feat(telegram): add Quick Start Guide, notification toggle, and UX improvements

keyboardstaff committed Mar 22, 2026 at 08:40 UTC 5dbc1770699bff54cf89232ea5f09ff04f579e49
4 files changed +149 -27
plugins/_telegram_integration/default_config.yaml
+1
@@ -2,6 +2,7 @@ bots: []
2 # Example bot:
3 # - name: my_bot
4 # enabled: true
5 +# notify_messages: false # Show a WebUI notification for each incoming Telegram message.
6 # token: ""
7 # mode: polling # polling or webhook
8 # webhook_url: "" # required if mode=webhook, your A0 base URL e.g. https://yourdomain.com (webhook path appended automatically)
plugins/_telegram_integration/helpers/handler.py
+21 -19
@@ -175,15 +175,16 @@ async def handle_clear(message: TgMessage, bot_name: str, bot_cfg: dict):
175 )
176
177 # Send notification
178 - username_str = f"@{user.username}" if user.username else str(user.id)
179 - NotificationManager.send_notification(
180 - type=NotificationType.INFO,
181 - priority=NotificationPriority.NORMAL,
182 - title="Telegram: chat cleared",
183 - message=f"{username_str} cleared their chat via /clear",
184 - display_time=5,
185 - group="telegram",
186 - )
178 + if bot_cfg.get("notify_messages", False):
179 + username_str = f"@{user.username}" if user.username else str(user.id)
180 + NotificationManager.send_notification(
181 + type=NotificationType.INFO,
182 + priority=NotificationPriority.NORMAL,
183 + title="Telegram: chat cleared",
184 + message=f"{username_str} cleared their chat via /clear",
185 + display_time=5,
186 + group="telegram",
187 + )
188
189
190 async def handle_message(message: TgMessage, bot_name: str, bot_cfg: dict):
@@ -250,16 +251,17 @@ async def handle_message(message: TgMessage, bot_name: str, bot_cfg: dict):
251 save_tmp_chat(context)
252
253 # Send notification
253 - username_str = f"@{user.username}" if user.username else str(user.id)
254 - preview = (text[:80] + "...") if len(text) > 80 else text
255 - NotificationManager.send_notification(
256 - type=NotificationType.INFO,
257 - priority=NotificationPriority.HIGH,
258 - title="Telegram: new message",
259 - message=f"From {username_str}: {preview}",
260 - display_time=10,
261 - group="telegram",
262 - )
254 + if bot_cfg.get("notify_messages", False):
255 + username_str = f"@{user.username}" if user.username else str(user.id)
256 + preview = (text[:80] + "...") if len(text) > 80 else text
257 + NotificationManager.send_notification(
258 + type=NotificationType.INFO,
259 + priority=NotificationPriority.HIGH,
260 + title="Telegram: new message",
261 + message=f"From {username_str}: {preview}",
262 + display_time=10,
263 + group="telegram",
264 + )
265
266
267 async def handle_callback_query(query: CallbackQuery, bot_name: str, bot_cfg: dict):
plugins/_telegram_integration/webui/config.html
+125 -7
@@ -13,9 +13,48 @@
13 <div class="section-title">Telegram Integration</div>
14 <div class="section-description">
15 Configure Telegram bots to communicate with Agent Zero via Telegram.
16 - Each bot connects to a Telegram Bot API token and handles messages from allowed users.
16 </div>
17
18 + <!-- Quick Start Guide (collapsible) -->
19 + <details class="tg-guide">
20 + <summary class="tg-guide-toggle">Quick Start Guide</summary>
21 + <div class="tg-guide-body">
22 + <div class="tg-guide-section tg-guide-section-first">
23 + <div class="tg-guide-row">
24 + <div class="tg-guide-steps">
25 + <div class="tg-guide-section-title">Get Bot Token</div>
26 + <ol>
27 + <li>Click <a href="https://t.me/BotFather" target="_blank" rel="noopener">@BotFather</a> or scan the QR code on your mobile device, or search <strong>@BotFather</strong> in Telegram</li>
28 + <li>Send <strong>/newbot</strong> and follow the prompts to create a bot</li>
29 + <li>Copy the API token provided by BotFather</li>
30 + <li>Click <strong>Add Bot</strong> below, paste the token, and save</li>
31 + </ol>
32 + </div>
33 + <img class="tg-qr" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0AQAAAAB84SuKAAAA50lEQVR4nMWVQWoFMQxDnz+zl2/w73+suYF8Aheni98ux1BqglEgQjOx7ETzM+r1awt/v4+ILCAHLPjqJivLAx7zLwjh7Dxg+T/pKj84/4nr5FHPgxb6bRLjAY/50QE6sED3Uz79HZrr7/ZzPiM/X2B7w/cw263uFZ+2sOb6rMf8iyZNNiqt6lfFG1fembHxzxszKQ1a9a8SO26STf9RU8MUqUX/0DLSmULSpn4T6Kx+zn+d+cMdJrWYP4zvxjy91SeSt6mKjf51cinGgOznsVP2rn7dksaEU8uF/yPAGvsu/B///H59AYlVhAI4J5PTAAAAAElFTkSuQmCC"
34 + alt="Scan to open @BotFather" title="@BotFather" />
35 + </div>
36 + </div>
37 + <div class="tg-guide-section">
38 + <div class="tg-guide-row">
39 + <div class="tg-guide-steps">
40 + <div class="tg-guide-section-title">Set Allowed Users</div>
41 + <ol>
42 + <li>Enter your Telegram handle (e.g. <strong>@yourname</strong>) in the <strong>Allowed Users</strong> field below.</li>
43 + <li>If you don't know your handle, click <a href="https://t.me/userinfobot" target="_blank" rel="noopener">@userinfobot</a> or scan the QR code, then send <strong>/start</strong> to get your numeric ID</li>
44 + </ol>
45 + </div>
46 + <img class="tg-qr" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0AQAAAAB84SuKAAAA5klEQVR4nMWVUWrEQAxDn5f+yzfY+x8rN7BPoOLZhW0/Yyg1A6OBOJbHihLmZ/Tj1xH+/hwRGUEesMiXjSs4YMMnkqaGQG77UVJ14/lPfL331Gtf1HfRVdSA2/nhubkDG3Td5+/XW3TAQj/RmYWahPvzDydUUmEC366P36tkrFroTzkKpHtEuJjfVdEJfmqhX6a0LSGrXKt8kCnDqn9kkSZKG/6qo9waGW74T0zmsLif/zi2czmfJN76D32NhbDqH830zhC84H9CTak6l/5TgccJtPSfLOdYwEq/oDM/1eL7i3/+f30Di2x7PjI9e0cAAAAASUVORK5CYII="
47 + alt="Scan to open @userinfobot" title="@userinfobot" />
48 + </div>
49 + </div>
50 + <div class="tg-guide-note">
51 + <strong>Group chats:</strong> To let the bot respond to all group messages (not just @mentions),
52 + go to @BotFather → <em>/mybots</em> → select your bot → <em>Bot Settings</em> →
53 + <em>Group Privacy</em> → <em>Turn off</em>.
54 + </div>
55 + </div>
56 + </details>
57 +
58 <!-- Bot cards -->
59 <template x-for="(bot, idx) in (config.bots || [])" :key="idx">
60 <div class="bot-card">
@@ -47,6 +86,19 @@
86 </div>
87 </div>
88
89 + <div class="field">
90 + <div class="field-label">
91 + <div class="field-title">Message Notifications</div>
92 + <div class="field-description">Show a WebUI notification for each incoming Telegram message</div>
93 + </div>
94 + <div class="field-control">
95 + <label class="toggle">
96 + <input type="checkbox" x-model="bot.notify_messages" />
97 + <span class="toggler"></span>
98 + </label>
99 + </div>
100 + </div>
101 +
102 <div class="field">
103 <div class="field-label">
104 <div class="field-title">Bot Name</div>
@@ -70,7 +122,7 @@
122 <div class="field">
123 <div class="field-label">
124 <div class="field-title">Mode</div>
73 - <div class="field-description">Polling: no public IP needed. Webhook: requires HTTPS endpoint</div>
125 + <div class="field-description">Polling: no public IP required. Webhook: needs HTTPS URL</div>
126 </div>
127 <div class="field-control">
128 <select x-model="bot.mode">
@@ -83,7 +135,7 @@
135 <div class="field" x-show="bot.mode === 'webhook'">
136 <div class="field-label">
137 <div class="field-title">Webhook URL</div>
86 - <div class="field-description">Your Agent Zero base URL (e.g. https://yourdomain.com). The webhook path is appended automatically</div>
138 + <div class="field-description">Your Agent Zero base URL, e.g. https://yourdomain.com</div>
139 </div>
140 <div class="field-control">
141 <input type="text" x-model="bot.webhook_url" placeholder="https://yourdomain.com" />
@@ -103,7 +155,7 @@
155 <div class="field">
156 <div class="field-label">
157 <div class="field-title">Allowed Users</div>
106 - <div class="field-description">Comma-separated Telegram user IDs or @usernames. Empty = allow all</div>
158 + <div class="field-description">Comma-separated user IDs or @usernames. Empty = anyone can use</div>
159 </div>
160 <div class="field-control">
161 <input type="text"
@@ -129,8 +181,8 @@
181
182 <div class="field">
183 <div class="field-label">
132 - <div class="field-title">User → Project Mapping</div>
133 - <div class="field-description">Map user IDs to projects. Format: user_id=project_name, comma-separated</div>
184 + <div class="field-title">User Project Mapping</div>
185 + <div class="field-description">Map user IDs to projects: user_id=project, separate by comma</div>
186 </div>
187 <div class="field-control">
188 <input type="text"
@@ -159,7 +211,7 @@
211 <div class="field">
212 <div class="field-label">
213 <div class="field-title">Attachment Max Age</div>
162 - <div class="field-description">Auto-delete downloaded attachments older than this many hours. 0 = keep forever</div>
214 + <div class="field-description">Hours before attachments auto-delete. 0 = keep forever</div>
215 </div>
216 <div class="field-control">
217 <input type="number" x-model.number="bot.attachment_max_age_hours" min="0" step="1" placeholder="0" />
@@ -217,6 +269,71 @@
269 flex-direction: column;
270 gap: 8px;
271 }
272 + .tg-guide {
273 + border: 1px solid var(--color-border);
274 + border-radius: 6px;
275 + font-size: 0.85rem;
276 + }
277 + .tg-guide-toggle {
278 + padding: 8px 10px;
279 + cursor: pointer;
280 + opacity: 0.8;
281 + }
282 + .tg-guide-toggle:hover {
283 + opacity: 1;
284 + }
285 + .tg-guide-body {
286 + padding: 4px 12px 12px;
287 + border-top: 1px solid var(--color-border);
288 + }
289 + .tg-guide-section {
290 + margin-top: 10px;
291 + }
292 + .tg-guide-section-first {
293 + margin-top: 15px;
294 + }
295 + .tg-guide-steps {
296 + flex: 1;
297 + }
298 + .tg-guide-section-title {
299 + font-weight: 600;
300 + font-size: 0.85rem;
301 + margin-bottom: 4px;
302 + opacity: 0.9;
303 + }
304 + .tg-guide-body ol {
305 + margin: 4px 0;
306 + padding-left: 20px;
307 + }
308 + .tg-guide-body li {
309 + margin-bottom: 4px;
310 + }
311 + .tg-guide-body a {
312 + color: var(--color-highlight);
313 + }
314 + .tg-guide-note {
315 + margin-top: 15px;
316 + padding: 6px 10px;
317 + background: var(--color-background-hover, rgba(255,255,255,0.04));
318 + border-radius: 4px;
319 + font-size: 0.8rem;
320 + opacity: 0.85;
321 + }
322 + .tg-guide-row {
323 + display: flex;
324 + align-items: flex-start;
325 + gap: 12px;
326 + }
327 + .tg-guide-row ol {
328 + flex: 1;
329 + }
330 + .tg-qr {
331 + width: 88px;
332 + height: 88px;
333 + border-radius: 4px;
334 + flex-shrink: 0;
335 + image-rendering: pixelated;
336 + }
337 .bot-card {
338 border: 1px solid var(--color-border);
339 border-radius: 6px;
@@ -270,6 +387,7 @@
387 }
388 .bot-add-btn {
389 margin-top: 4px;
390 + width: fit-content;
391 }
392 .bot-test-row {
393 margin-top: 12px;
plugins/_telegram_integration/webui/telegram-config-store.js
+2 -1
@@ -24,7 +24,8 @@ export const store = createStore("telegramConfig", {
24 defaultBot() {
25 return {
26 name: "",
27 - enabled: false,
27 + enabled: true,
28 + notify_messages: false,
29 token: "",
30 mode: "polling",
31 webhook_url: "",