From 0e262f7b8782bc9b2060ffa6ef645b8a9994cd41 Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:12:20 +0200 Subject: [PATCH] Refresh welcome screen and composer Rework the welcome screen around the shared new-chat composer, renewed quick actions, discovery cards, OAuth accounts, and dedicated system resources panel. Restore static button colors and neutral surfaces, move microphone state feedback onto the icon, and keep Rubik/code-font composer behavior. Add focused static regressions for the welcome composer, OAuth discovery panel, and speech button contract. --- .../python/banners/_30_system_resources.py | 44 +- plugins/_discovery/AGENTS.md | 1 + .../python/banners/10_discovery_cards.py | 14 +- .../discovery-cards.html | 12 +- .../welcome-actions-end/discovery-cards.html | 770 +++++++--------- plugins/_discovery/webui/discovery-store.js | 4 + .../chat-input-box-end/microphone-button.html | 49 +- plugins/_whisper_stt/webui/whisper-stt.css | 49 +- tests/test_oauth_static.py | 13 +- tests/test_speech_plugin_split.py | 18 +- tests/test_welcome_composer_static.py | 93 ++ webui/AGENTS.md | 1 + webui/components/chat/AGENTS.md | 2 + .../chat/attachments/attachmentsStore.js | 5 +- .../components/chat/input/bottom-actions.html | 10 +- .../components/chat/input/chat-bar-input.html | 168 ++-- webui/components/chat/input/input-store.js | 71 +- webui/components/sidebar/chats/chats-store.js | 3 +- webui/components/welcome/AGENTS.md | 3 + webui/components/welcome/welcome-screen.html | 852 ++++++++++-------- webui/components/welcome/welcome-store.js | 6 +- webui/index.html | 22 +- webui/index.js | 12 +- 23 files changed, 1238 insertions(+), 984 deletions(-) create mode 100644 tests/test_welcome_composer_static.py diff --git a/extensions/python/banners/_30_system_resources.py b/extensions/python/banners/_30_system_resources.py index 25c65a670..3c1949f4f 100644 --- a/extensions/python/banners/_30_system_resources.py +++ b/extensions/python/banners/_30_system_resources.py @@ -69,34 +69,37 @@ class SystemResourcesCheck(Extension): "priority": 10, "title": "System Resources", "html": ( - "
" - "
" - "
" - "
" - "
CPU
" - f"
{cpu_value}
" + "
" + "
" + "
" + "
CPU
" + f"
{cpu_value}
" "
" f"{cpu_bar}" "
" - "
" - "
" - "
RAM
" - f"
{ram_value}
" + "
" + "
" + "
RAM
" + f"
{ram_value}
" "
" f"{ram_bar}" "
" - "
" - "
" - "
Disk
" - f"
{disk_value}
" + "
" + "
" + "
Disk
" + f"
{disk_value}
" "
" f"{disk_bar}" "
" + "
" + "
" + "
Load (1/5/15)
" + f"
{load_value}
" + "
" + "
" + "
Net (since boot)
" + f"
{net_sent} sent / {net_recv} recv
" "
" - "
" - "
" - f"
Load (1/5/15)
{load_value}
" - f"
Net (since boot)
{net_sent} sent / {net_recv} recv
" "
" "
" ), @@ -117,9 +120,8 @@ class SystemResourcesCheck(Extension): color = "#22c55e" return ( - "
" - f"
" + "
" + f"" "
" ) diff --git a/plugins/_discovery/AGENTS.md b/plugins/_discovery/AGENTS.md index d2440d5ab..0a41f079b 100644 --- a/plugins/_discovery/AGENTS.md +++ b/plugins/_discovery/AGENTS.md @@ -15,6 +15,7 @@ - Discovery cards must be accurate, dismissible where appropriate, and avoid advertising already-complete setup. - CTA actions must match supported welcome-screen action contracts. - Keep card IDs unique and plugin-prefixed. +- The Welcome screen `welcome-actions-end` surface renders feature channel cards plus the compact OAuth account-provider card; other hero discovery cards stay out of the lower welcome grid. ## Work Guidance diff --git a/plugins/_discovery/extensions/python/banners/10_discovery_cards.py b/plugins/_discovery/extensions/python/banners/10_discovery_cards.py index c0bc5cdee..bba6c1c85 100644 --- a/plugins/_discovery/extensions/python/banners/10_discovery_cards.py +++ b/plugins/_discovery/extensions/python/banners/10_discovery_cards.py @@ -87,11 +87,11 @@ class DiscoveryCardsExtension(Extension): banners.append({ "id": "discovery-telegram", "type": "feature", - "title": "Connect Telegram", - "description": "Chat with Agent Zero from Telegram wherever you are.", + "title": "Telegram", + "description": "Chat on Telegram wherever you are.", "thumbnail": "/plugins/_discovery/webui/assets/thumb-telegram.png", "icon": "send", - "cta_text": "Setup", + "cta_text": "Connect", "cta_action": "open-plugin-config:_telegram_integration", "dismissible": True, "priority": 50, @@ -105,11 +105,11 @@ class DiscoveryCardsExtension(Extension): banners.append({ "id": "discovery-email", "type": "feature", - "title": "Setup Email", + "title": "Email", "description": "Let Agent Zero read and send emails on your behalf.", "thumbnail": "/plugins/_discovery/webui/assets/thumb-email.png", "icon": "mail", - "cta_text": "Open Setup", + "cta_text": "Connect", "cta_action": "open-plugin-config:_email_integration", "dismissible": True, "priority": 50, @@ -121,11 +121,11 @@ class DiscoveryCardsExtension(Extension): banners.append({ "id": "discovery-whatsapp", "type": "feature", - "title": "Connect WhatsApp", + "title": "WhatsApp", "description": "Send and receive WhatsApp messages through A0.", "thumbnail": "/plugins/_discovery/webui/assets/thumb-whatsapp.png", "icon": "chat", - "cta_text": "Setup", + "cta_text": "Connect", "cta_action": "open-plugin-config:_whatsapp_integration", "dismissible": True, "priority": 50, diff --git a/plugins/_discovery/extensions/webui/onboarding-success-end/discovery-cards.html b/plugins/_discovery/extensions/webui/onboarding-success-end/discovery-cards.html index fd68a9dc4..cf54f176b 100644 --- a/plugins/_discovery/extensions/webui/onboarding-success-end/discovery-cards.html +++ b/plugins/_discovery/extensions/webui/onboarding-success-end/discovery-cards.html @@ -19,14 +19,6 @@ @click="$store.discoveryStore.executeCta(card.cta_action)" @keydown.enter.prevent="$store.discoveryStore.executeCta(card.cta_action)" @keydown.space.prevent="$store.discoveryStore.executeCta(card.cta_action)"> -
@@ -40,8 +32,6 @@

-

-
diff --git a/plugins/_discovery/webui/discovery-store.js b/plugins/_discovery/webui/discovery-store.js index 520ba3ec4..8dbb52919 100644 --- a/plugins/_discovery/webui/discovery-store.js +++ b/plugins/_discovery/webui/discovery-store.js @@ -154,6 +154,10 @@ const model = { return this.cards.filter((card) => card.type === "hero" && card.placement === "after-features"); }, + get oauthAccountCards() { + return this.bottomHeroCards.filter((card) => card.id === "discovery-oauth-accounts"); + }, + get heroCards() { return [...this.topHeroCards, ...this.bottomHeroCards]; }, diff --git a/plugins/_whisper_stt/extensions/webui/chat-input-box-end/microphone-button.html b/plugins/_whisper_stt/extensions/webui/chat-input-box-end/microphone-button.html index c6fb47f3e..aed23ba3a 100644 --- a/plugins/_whisper_stt/extensions/webui/chat-input-box-end/microphone-button.html +++ b/plugins/_whisper_stt/extensions/webui/chat-input-box-end/microphone-button.html @@ -1,58 +1,75 @@
- \ No newline at end of file + diff --git a/webui/components/chat/input/input-store.js b/webui/components/chat/input/input-store.js index cc860420e..0ace742bf 100644 --- a/webui/components/chat/input/input-store.js +++ b/webui/components/chat/input/input-store.js @@ -56,6 +56,7 @@ const model = { chatMoreMenuOpen: false, progressText: "", progressActive: false, + _caretIndex: 0, toggleChatMoreMenu() { this.chatMoreMenuOpen = !this.chatMoreMenuOpen; @@ -76,12 +77,17 @@ const model = { }, get inputPlaceholder() { + if (!chatsStore.selected) return "Ask anything to start a new chat"; const state = this._getSendState(); if (state === "all") return "Press Enter to send queued messages"; if (this.showProgressPlaceholder) return ""; return "Type your message here..."; }, + get isCodeContext() { + return this._isCaretInsideFence(this.message, this._caretIndex); + }, + get showProgressPlaceholder() { return ( this._getSendState() !== "all" && @@ -102,7 +108,7 @@ const model = { const state = this._getSendState(); if (state === "all") return "send_and_archive"; if (state === "queue") return "schedule_send"; - return "send"; + return "arrow_forward"; }, // Computed: send button CSS class @@ -129,15 +135,59 @@ const model = { async sendMessage() { // Capture sent prompt to per-chat history (bash-style) try { this._pushHistory(this.message); } catch (_e) { /* ignore */ } + + if (!chatsStore.selected && (this.message.trim() || attachmentsStore?.attachments?.length > 0)) { + const ctxid = await chatsStore.newChat(); + if (!ctxid && !chatsStore.selected) return; + } + // Delegate to the global function if (globalThis.sendMessage) { await globalThis.sendMessage(); } }, - adjustTextareaHeight() { - const chatInput = document.getElementById("chat-input"); - if (chatInput) { + _composerTextareas(target = null) { + if (target?.tagName === "TEXTAREA") return [target]; + return ["chat-input"] + .map((id) => document.getElementById(id)) + .filter(Boolean); + }, + + _activeTextarea() { + const active = document.activeElement; + if (active?.id === "chat-input") { + return active; + } + return document.getElementById("chat-input"); + }, + + _syncCaretFromEvent($event = null) { + const ta = ($event && $event.target?.tagName === "TEXTAREA") + ? $event.target + : this._activeTextarea(); + if (!ta) { + this._caretIndex = this.message.length; + return; + } + this._caretIndex = Number.isFinite(ta.selectionStart) ? ta.selectionStart : this.message.length; + }, + + _isCaretInsideFence(text, index) { + const source = String(text || ""); + const caret = Math.max(0, Math.min(source.length, Number(index) || 0)); + const matches = source.slice(0, caret).match(/```/g); + return Boolean(matches && matches.length % 2 === 1); + }, + + handleInput($event) { + this._syncCaretFromEvent($event); + this.adjustTextareaHeight($event); + }, + + adjustTextareaHeight($event = null) { + const target = $event?.target || null; + for (const chatInput of this._composerTextareas(target)) { if (!this.message) chatInput.value = ""; chatInput.style.height = "auto"; chatInput.style.height = chatInput.scrollHeight + "px"; @@ -284,7 +334,7 @@ const model = { }, focus() { - const chatInput = document.getElementById("chat-input"); + const chatInput = this._activeTextarea(); if (chatInput) { chatInput.focus(); } @@ -379,10 +429,11 @@ const model = { _setCaretStart() { queueMicrotask(() => { - const ta = document.getElementById("chat-input"); + const ta = this._activeTextarea(); if (ta) { try { ta.setSelectionRange(0, 0); } catch (_e) { /* ignore */ } try { ta.scrollTop = 0; } catch (_e) { /* ignore */ } + this._syncCaretFromEvent({ target: ta }); } this.adjustTextareaHeight(); }); @@ -390,11 +441,12 @@ const model = { _setCaretEnd() { queueMicrotask(() => { - const ta = document.getElementById("chat-input"); + const ta = this._activeTextarea(); if (ta) { const end = ta.value.length; try { ta.setSelectionRange(end, end); } catch (_e) { /* ignore */ } try { ta.scrollTop = ta.scrollHeight; } catch (_e) { /* ignore */ } + this._syncCaretFromEvent({ target: ta }); } this.adjustTextareaHeight(); }); @@ -402,7 +454,7 @@ const model = { historyPrev($event) { if ($event && ($event.isComposing || $event.keyCode === 229)) return; - const ta = ($event && $event.target) ? $event.target : document.getElementById("chat-input"); + const ta = ($event && $event.target) ? $event.target : this._activeTextarea(); if (!ta) return; const start = ta.selectionStart; const end = ta.selectionEnd; @@ -424,7 +476,7 @@ const model = { historyNext($event) { if ($event && ($event.isComposing || $event.keyCode === 229)) return; - const ta = ($event && $event.target) ? $event.target : document.getElementById("chat-input"); + const ta = ($event && $event.target) ? $event.target : this._activeTextarea(); if (!ta) return; const value = ta.value; const start = ta.selectionStart; @@ -447,6 +499,7 @@ const model = { this.message = ""; attachmentsStore.clearAttachments(); this.chatMoreMenuOpen = false; + this._caretIndex = 0; this._historyIndex = null; this._draft = ""; this.adjustTextareaHeight(); diff --git a/webui/components/sidebar/chats/chats-store.js b/webui/components/sidebar/chats/chats-store.js index 1469c08d2..4dc563ac8 100644 --- a/webui/components/sidebar/chats/chats-store.js +++ b/webui/components/sidebar/chats/chats-store.js @@ -210,12 +210,13 @@ const model = { if (response.ok) { await this.selectChat(response.ctxid); document.dispatchEvent(new CustomEvent("chat-created", { detail: { ctxid: response.ctxid } })); - return; + return response.ctxid; } } catch (e) { toastFetchError("Error creating new chat", e); } + return null; }, deselectChat(){ diff --git a/webui/components/welcome/AGENTS.md b/webui/components/welcome/AGENTS.md index 4237418aa..624209b3b 100644 --- a/webui/components/welcome/AGENTS.md +++ b/webui/components/welcome/AGENTS.md @@ -14,10 +14,13 @@ - Keep banner and discovery card behavior compatible with Python `banners` extensions. - Supported banner/card CTA actions must stay synchronized with plugin discovery contracts. - Do not show setup prompts for already configured plugins when backend status can prevent it. +- The welcome screen mounts the shared chat composer to start a new chat; keep it mutually exclusive with the normal chat input DOM. +- Render `system-resources` as the dedicated System Resources panel, not as a generic alert banner. ## Work Guidance - Coordinate visual card or CTA changes with plugin discovery and onboarding surfaces. +- Keep the lower welcome grid focused on Connect Channels, OAuth accounts, and System Resources unless the target design changes. ## Verification diff --git a/webui/components/welcome/welcome-screen.html b/webui/components/welcome/welcome-screen.html index 95a4192c3..d27472373 100644 --- a/webui/components/welcome/welcome-screen.html +++ b/webui/components/welcome/welcome-screen.html @@ -12,534 +12,598 @@
-
- -
- -
- add_circle -

New Chat

-
-
- folder -

Projects

-
-
- memory -

Memory

-
+
+
+

Welcome back

+

Agent Zero is ready to help.

+
-
- schedule -

Tasks

-
-
- settings -

Settings

-
-
- extension -

Plugins

-
+
+ + +
-
- folder_open -

Files

+
+
+ + +
-
- language -

Visit Website

-
- -
- - + + +
+

Quick Actions

+
+ + + + + + + + +
+
+ +
+ + +
+
+

+ monitoring +
+
+
+
+
- -
- - - -
-
diff --git a/webui/components/welcome/welcome-store.js b/webui/components/welcome/welcome-store.js index 13fc5d730..521b94dbf 100644 --- a/webui/components/welcome/welcome-store.js +++ b/webui/components/welcome/welcome-store.js @@ -1,5 +1,4 @@ import { createStore } from "/js/AlpineStore.js"; -import { getContext } from "/index.js"; import { store as chatsStore } from "/components/sidebar/chats/chats-store.js"; import { store as memoryStore } from "/plugins/_memory/webui/memory-dashboard-store.js"; import { store as projectsStore } from "/components/projects/projects-store.js"; @@ -143,10 +142,15 @@ const model = { get sortedBanners() { return [...this.banners] + .filter((b) => b.id !== "system-resources") .filter((b) => b.type !== "hero" && b.type !== "feature") .sort((a, b) => (b.priority || 0) - (a.priority || 0)); }, + get systemResourceBanner() { + return this.banners.find((b) => b.id === "system-resources") || null; + }, + /** * Dismiss a banner by ID. * diff --git a/webui/index.html b/webui/index.html index 5a7889211..d7a2ee70c 100644 --- a/webui/index.html +++ b/webui/index.html @@ -96,8 +96,12 @@ -
- +
+
@@ -135,11 +139,15 @@
-
- - - - +
+
diff --git a/webui/index.js b/webui/index.js index 9320ad064..de5b64bc3 100644 --- a/webui/index.js +++ b/webui/index.js @@ -14,6 +14,7 @@ import { store as chatTopStore } from "/components/chat/top-section/chat-top-sto import { store as _tooltipsStore } from "/components/tooltips/tooltip-store.js"; import { store as messageQueueStore } from "/components/chat/message-queue/message-queue-store.js"; import { store as syncStore } from "/components/sync/sync-store.js" +import { store as welcomeStore } from "/components/welcome/welcome-store.js"; import { getUserHour12, getUserTimezone } from "/js/time-utils.js"; globalThis.fetchApi = api.fetchApi; // TODO - backward compatibility for non-modular scripts, remove once refactored to alpine @@ -58,7 +59,7 @@ export async function sendMessage() { if (message || hasAttachments) { // Check if agent is busy - queue instead of sending - if (chatsStore.selectedContext.running || messageQueueStore.hasQueue) { + if (chatsStore.selectedContext?.running || messageQueueStore.hasQueue) { const success = messageQueueStore.addToQueue(message, attachmentsWithUrls); // no await for the queue // if (success) { @@ -231,12 +232,7 @@ globalThis.loadKnowledge = async function () { }; function adjustTextareaHeight() { - const chatInputEl = document.getElementById("chat-input"); - if (chatInputEl) { - if (!inputStore.message) chatInputEl.value = ""; - chatInputEl.style.height = "auto"; - chatInputEl.style.height = chatInputEl.scrollHeight + "px"; - } + inputStore.adjustTextareaHeight(); } export const sendJsonData = async function (url, data) { @@ -593,7 +589,7 @@ export const deselectChat = function () { sessionStorage.removeItem("lastSelectedTask"); // Clear the chat history - chatHistory.innerHTML = ""; + if (chatHistory) chatHistory.innerHTML = ""; }; globalThis.deselectChat = deselectChat;