Refresh welcome screen and composer
Some checks are pending
Build And Publish Docker Images / plan (push) Waiting to run
Build And Publish Docker Images / build (push) Blocked by required conditions

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.
This commit is contained in:
Alessandro 2026-06-22 15:12:20 +02:00
parent 6dcaab2f5c
commit 0e262f7b87
23 changed files with 1238 additions and 984 deletions

View file

@ -26,6 +26,7 @@
- Do not bypass WebSocket origin/auth/CSRF assumptions from frontend code.
- Avoid editing vendored files unless intentionally updating the vendor asset.
- Rubik (`--font-family-main`) is the default WebUI text and control font; use the code/mono font tokens only for code, logs, paths, and fixed-width data.
- Hover, focus, and active border treatments should follow existing neutral border/background patterns; avoid hard-coded blue border highlights unless matching an established specialized surface.
## Work Guidance

View file

@ -17,6 +17,8 @@
- Preserve Store Gating for all store-backed chat components.
- Use shared API, WebSocket, notification, and attachment helpers where available.
- Do not bypass CSRF or WebSocket state-sync expectations.
- The shared composer can be mounted on the Welcome screen with no selected chat; sending from that state must create and select a chat context before dispatch.
- Composer text uses the main UI font by default and switches to the code font while the caret is inside an open triple-backtick fenced block.
## Work Guidance

View file

@ -136,7 +136,10 @@ const model = {
console.log("Setting up paste handler...");
document.addEventListener("paste", (e) => {
// console.log("Paste event detected, target:", e.target.id);
if(e.target.id != "chat-input" && e.target.id != "full-screen-input") return;
if (
e.target.id !== "chat-input" &&
e.target.id !== "full-screen-input"
) return;
const items = e.clipboardData.items;
let imageFound = false;

View file

@ -14,7 +14,7 @@
<div class="chat-bottom-actions-menu">
<input
type="file"
id="chat-file-input"
x-ref="chatFileInput"
class="chat-bottom-menu-file-input"
accept="*"
multiple
@ -23,7 +23,7 @@
<button
type="button"
class="chat-bottom-menu-item"
@click.stop="document.getElementById('chat-file-input')?.click()"
@click.stop="$refs.chatFileInput?.click()"
>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
@ -33,7 +33,7 @@
<input
type="file"
id="chat-folder-input"
x-ref="chatFolderInput"
class="chat-bottom-menu-file-input"
webkitdirectory
directory
@ -43,7 +43,7 @@
<button
type="button"
class="chat-bottom-menu-item"
@click.stop="document.getElementById('chat-folder-input')?.click()"
@click.stop="$refs.chatFolderInput?.click()"
>
<span class="material-symbols-outlined" aria-hidden="true">drive_folder_upload</span>
<span>Attach folder</span>
@ -73,6 +73,7 @@
class="chat-bottom-menu-item"
id="history_inspect"
@click="$store.history.open(); $store.chatInput.closeChatMoreMenu()"
:disabled="!$store.chats.selected"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="5 10 85 85" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="m59.572,57.949c-.41,0-.826-.105-1.207-.325l-9.574-5.528c-.749-.432-1.21-1.231-1.21-2.095v-14.923c0-1.336,1.083-2.419,2.419-2.419s2.419,1.083,2.419,2.419v13.526l8.364,4.829c1.157.668,1.554,2.148.886,3.305-.448.776-1.261,1.21-2.097,1.21Zm30.427-7.947c0,10.684-4.161,20.728-11.716,28.283-6.593,6.59-15.325,10.69-24.59,11.544-1.223.113-2.448.169-3.669.169-7.492,0-14.878-2.102-21.22-6.068l-15.356,5.733c-.888.331-1.887.114-2.557-.556s-.887-1.669-.556-2.557l5.733-15.351c-4.613-7.377-6.704-16.165-5.899-24.891.854-9.266,4.954-17.998,11.544-24.588,7.555-7.555,17.6-11.716,28.285-11.716s20.73,4.161,28.285,11.716c7.555,7.555,11.716,17.599,11.716,28.283Zm-15.137-24.861c-13.71-13.71-36.018-13.71-49.728,0-11.846,11.846-13.682,30.526-4.365,44.417.434.647.53,1.464.257,2.194l-4.303,11.523,11.528-4.304c.274-.102.561-.153.846-.153.474,0,.944.139,1.348.41,13.888,9.315,32.568,7.479,44.417-4.365,13.707-13.708,13.706-36.014,0-49.723Zm-24.861-4.13c-15.989,0-28.996,13.006-28.996,28.992s13.008,28.992,28.996,28.992c1.336,0,2.419-1.083,2.419-2.419s-1.083-2.419-2.419-2.419c-13.32,0-24.157-10.835-24.157-24.153s10.837-24.153,24.157-24.153,24.153,10.835,24.153,24.153c0,1.336,1.083,2.419,2.419,2.419c0-15.986-13.006-28.992-28.992-28.992Zm25.041,33.531c-1.294.347-2.057,1.673-1.71,2.963.343,1.289,1.669,2.057,2.963,1.71,1.289-.343,2.053-1.669,1.71-2.963-.347-1.289-1.673-2.057-2.963-1.71Zm-2.03,6.328c-1.335,0-2.419,1.084-2.419,2.419s1.084,2.419,2.419,2.419,2.419-1.084,2.419-2.419-1.084-2.419-2.419-2.419Zm-3.598,5.587c-1.289-.347-2.615.416-2.963,1.71-.343,1.289.421,2.615,1.71,2.963,1.294.347,2.62-.421,2.963-1.71.347-1.294-.416-2.62-1.71-2.963Zm-4.919,4.462c-1.157-.667-2.638-.27-3.306.887-.667,1.157-.27,2.638.887,3.305,1.157.668,2.638.27,3.306-.887.667-1.157.27-2.638-.887-3.306Zm-9.327,3.04c-.946.946-.946,2.478,0,3.42.942.946,2.473.946,3.42,0,.946-.942.946-2.473,0-3.42-.946-.942-2.478-.946-3.42,0Z"></path>
@ -85,6 +86,7 @@
class="chat-bottom-menu-item"
id="ctx_window"
@click="$store.context.open(); $store.chatInput.closeChatMoreMenu()"
:disabled="!$store.chats.selected"
>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="17 15 70 70" width="18" height="18" fill="currentColor" aria-hidden="true">
<path d="m63 25c1.1016 0 2-0.89844 2-2s-0.89844-2-2-2h-26c-1.1016 0-2 0.89844-2 2s0.89844 2 2 2z"></path>

View file

@ -25,11 +25,16 @@
</div>
<!-- Container for textarea and expand button -->
<div id="chat-input-container" style="position: relative;">
<div id="chat-input-container">
<textarea id="chat-input" :placeholder="$store.chatInput.inputPlaceholder" rows="1"
@keydown.enter="if (!$event.shiftKey && !$event.isComposing && $event.keyCode !== 229) { $event.preventDefault(); $store.chatInput.sendMessage(); }"
@keydown.up="$store.chatInput.historyPrev($event)" @keydown.down="$store.chatInput.historyNext($event)"
@input="$store.chatInput.adjustTextareaHeight()" x-model="$store.chatInput.message"></textarea>
@input="$store.chatInput.handleInput($event)"
@keyup="$store.chatInput._syncCaretFromEvent($event)"
@click="$store.chatInput._syncCaretFromEvent($event)"
@select="$store.chatInput._syncCaretFromEvent($event)"
:class="{ 'progress-active': $store.chatInput.progressActive, 'is-code-context': $store.chatInput.isCodeContext }"
x-model="$store.chatInput.message"></textarea>
<div id="chat-input-progress-placeholder" x-show="$store.chatInput.showProgressPlaceholder"
x-html="$store.chatInput.progressPlaceholderHtml"
:class="{ 'progress-active': $store.chatInput.progressActive }" aria-hidden="true" style="display: none;">
@ -59,9 +64,25 @@
.input-row {
display: flex;
align-items: center;
gap: var(--spacing-xs);
gap: 0.72rem;
width: 100%;
min-height: 4.25rem;
padding: 0.42rem 0.52rem 0.42rem 0.72rem;
border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
border-radius: 16px;
background: color-mix(in srgb, var(--color-panel) 72%, var(--color-background) 28%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.035),
0 18px 46px rgba(0, 0, 0, 0.18);
white-space: nowrap;
transition: border-color 0.16s ease, background-color 0.16s ease, box-shadow 0.16s ease;
}
.input-row:focus-within {
border-color: color-mix(in srgb, var(--color-border) 88%, var(--color-text) 12%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.045),
0 22px 54px rgba(0, 0, 0, 0.22);
}
.attachment-wrapper {
@ -77,21 +98,22 @@
.chat-more-trigger {
cursor: pointer;
color: var(--color-text);
opacity: 0.75;
transition: opacity 0.2s ease;
opacity: 0.82;
transition: opacity 0.16s ease, background-color 0.16s ease, color 0.16s ease;
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
width: 2.5rem;
height: 2.5rem;
padding: 0;
border: none;
border-radius: 50%;
border-radius: 10px;
background: transparent;
}
.chat-more-trigger:hover {
opacity: 1;
background: color-mix(in srgb, var(--color-border) 36%, transparent);
}
.chat-more-trigger:active {
@ -99,7 +121,7 @@
}
.chat-more-trigger .material-symbols-outlined {
font-size: 1.5rem;
font-size: 1.7rem;
line-height: 1;
}
@ -113,12 +135,12 @@
max-height: min(70vh, 20rem);
overflow-x: hidden;
overflow-y: auto;
background-color: var(--color-panel);
background-color: color-mix(in srgb, var(--color-panel) 90%, black 10%);
border: 1px solid var(--color-border);
border-radius: 0.4rem;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
border-radius: 12px;
box-shadow: 0 16px 38px rgba(0, 0, 0, 0.3);
z-index: 1050;
padding: 0.2rem 0;
padding: 0.3rem;
}
.light-mode .chat-more-panel {
@ -129,32 +151,41 @@
#chat-input-container {
position: relative;
width: 100%;
min-width: 0;
display: flex;
align-items: center;
}
#chat-input {
background-color: var(--color-input);
border: 1px solid var(--color-border);
border-radius: 8px;
background-color: transparent;
border: 0;
border-radius: 0;
color: var(--color-text);
flex-grow: 1;
font-family: "Roboto Mono", monospace;
font-family: var(--font-family-main, "Rubik", Arial, Helvetica, sans-serif);
font-optical-sizing: auto;
-webkit-font-optical-sizing: auto;
font-size: 0.9rem;
font-size: 1rem;
line-height: 1.45;
max-height: 30vh;
min-height: 3.05rem;
min-height: 2.8rem;
width: 100%;
padding: 0.48rem 40px var(--spacing-sm) var(--spacing-sm);
margin-right: var(--spacing-xs);
padding: 0.68rem 2.2rem 0.68rem 0.1rem;
margin: 0;
overflow-y: auto;
scroll-behavior: smooth;
resize: none;
align-content: start;
align-content: center;
background-clip: border-box;
border: 6px solid transparent;
outline: 1px solid var(--color-border);
transition: all 0.3s ease;
-webkit-transition: all 0.3s ease;
outline: none;
transition: color 0.16s ease, background-color 0.16s ease;
-webkit-transition: color 0.16s ease, background-color 0.16s ease;
}
#chat-input.is-code-context {
font-family: var(--font-family-code, "Roboto Mono", monospace);
font-size: 0.94rem;
align-content: start;
}
#chat-input::-webkit-scrollbar {
@ -180,16 +211,14 @@
}
#chat-input:focus {
outline: 0.05rem solid rgba(155, 155, 155, 0.5);
font-size: 0.955rem;
padding-top: 0.45rem;
background-color: var(--color-input-focus);
outline: none;
background-color: transparent;
}
#chat-input::placeholder {
color: var(--color-text-muted);
opacity: 0.7;
transition: color 0.3s ease;
color: color-mix(in srgb, var(--color-text) 62%, transparent);
opacity: 0.78;
transition: color 0.16s ease;
}
/* Progress ghost text animation — gentle pulse on placeholder */
@ -216,12 +245,12 @@
#chat-input-progress-placeholder {
position: absolute;
top: calc(6px + 0.48rem);
left: calc(6px + var(--spacing-sm));
top: 0.74rem;
left: 0.12rem;
right: 46px;
color: var(--color-text-muted);
opacity: 0.7;
font-family: "Roboto Mono", monospace;
font-family: var(--font-family-code, "Roboto Mono", monospace);
font-size: 0.9rem;
line-height: 1.45;
white-space: nowrap;
@ -251,7 +280,7 @@
#expand-button {
position: absolute;
top: 12px;
right: 6px;
right: 0;
background: transparent;
border: none;
cursor: pointer;
@ -278,52 +307,60 @@
/* Chat buttons (Send/Mic) */
#chat-buttons-wrapper {
gap: var(--spacing-xs);
padding-left: var(--spacing-xs);
line-height: 0.5rem;
gap: 0.58rem;
padding-left: 0;
line-height: 1;
display: -webkit-flex;
display: flex;
align-items: center;
}
.chat-button {
border: none;
border: 1px solid transparent;
border-radius: 50%;
color: var(--color-background);
color: #fff;
cursor: pointer;
font-size: var(--font-size-normal);
height: 2.525rem;
width: 2.525rem;
margin: 0 0.18rem 0 0 var(--spacing-xs);
height: 3.15rem;
width: 3.15rem;
margin: 0;
display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
flex-grow: 0;
min-width: 2.525rem;
-webkit-transition: background-color var(--transition-speed), box-shadow 0.12s ease-in-out, filter 0.12s ease-in-out;
transition: background-color var(--transition-speed), box-shadow 0.12s ease-in-out, filter 0.12s ease-in-out;
min-width: 3.15rem;
-webkit-transition: background-color var(--transition-speed), border-color 0.16s ease, box-shadow 0.16s ease, filter 0.12s ease-in-out, transform 0.12s ease;
transition: background-color var(--transition-speed), border-color 0.16s ease, box-shadow 0.16s ease, filter 0.12s ease-in-out, transform 0.12s ease;
}
#send-button {
order: 2;
border-radius: 12px;
background-color: #4248f1;
box-shadow: none;
}
#send-button.send-queue {
background-color: #e67e22;
box-shadow: none;
}
#send-button:hover {
background-color: #353bc5;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08), 0 6px 14px rgba(0, 0, 0, 0.2);
filter: none;
box-shadow: none;
}
#send-button.send-queue:hover {
background-color: #d35400;
filter: none;
}
#send-button:active {
background-color: #2b309c;
transform: translateY(1px) scale(0.98);
filter: brightness(0.96);
}
@ -333,25 +370,42 @@
}
#send-button .material-symbols-outlined {
font-size: 1.5rem;
font-size: 1.78rem;
}
/* Responsive tweaks */
@media (max-width: 640px) {
#chat-input {
min-height: 5.3rem;
align-content: start;
min-height: 4.2rem;
align-content: center;
}
#chat-buttons-wrapper {
display: flex;
gap: var(--spacing-xs);
padding: 0 0 0 var(--spacing-sm) !important;
width: 3.5rem;
flex-wrap: wrap;
gap: 0.42rem;
padding: 0 !important;
width: auto;
flex-wrap: nowrap;
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease;
}
.input-row {
gap: 0.42rem;
min-height: 4rem;
padding: 0.36rem 0.42rem 0.36rem 0.48rem;
}
.chat-more-trigger {
width: 2.15rem;
height: 2.15rem;
}
.chat-button {
width: 2.75rem;
height: 2.75rem;
min-width: 2.75rem;
}
}
@media (min-width: 768px) {
@ -364,4 +418,4 @@
</style>
</body>
</html>
</html>

View file

@ -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();

View file

@ -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(){

View file

@ -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

View file

@ -12,534 +12,598 @@
<div x-init="$store.welcomeStore.init(); if ($store.welcomeStore.isVisible) { $store.welcomeStore.onCreate(); } $watch('$store.welcomeStore.isVisible', (visible) => { if (visible) { $store.welcomeStore.onCreate(); } });"
class="welcome-container">
<x-extension id="welcome-screen-start"></x-extension>
<div class="welcome-content">
<!-- Action Cards -->
<div class="welcome-actions">
<x-extension id="welcome-actions-start"></x-extension>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('new-chat')">
<span class="material-symbols-outlined welcome-action-icon">add_circle</span>
<h3 class="welcome-action-title">New Chat</h3>
</div>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('projects')">
<span class="material-symbols-outlined welcome-action-icon">folder</span>
<h3 class="welcome-action-title">Projects</h3>
</div>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('memory')">
<span class="material-symbols-outlined welcome-action-icon">memory</span>
<h3 class="welcome-action-title">Memory</h3>
</div>
<main class="welcome-shell">
<header class="welcome-hero">
<h1>Welcome back <span aria-hidden="true">&#128075;</span></h1>
<p>Agent Zero is ready to help.</p>
</header>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('scheduler')">
<span class="material-symbols-outlined welcome-action-icon">schedule</span>
<h3 class="welcome-action-title">Tasks</h3>
</div>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('settings')">
<span class="material-symbols-outlined welcome-action-icon">settings</span>
<h3 class="welcome-action-title">Settings</h3>
</div>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('plugins')">
<span class="material-symbols-outlined welcome-action-icon">extension</span>
<h3 class="welcome-action-title">Plugins</h3>
</div>
<section class="welcome-composer" aria-label="Start a new chat">
<x-component path="chat/attachments/inputPreview.html"></x-component>
<x-component path="chat/input/chat-bar-input.html"></x-component>
</section>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('files')">
<span class="material-symbols-outlined welcome-action-icon">folder_open</span>
<h3 class="welcome-action-title">Files</h3>
<section class="welcome-alerts"
x-show="$store.welcomeStore.sortedBanners.length > 0 || $store.welcomeStore.hasDismissedBanners">
<div class="welcome-banners" x-show="$store.welcomeStore.sortedBanners.length > 0">
<x-extension id="welcome-banners-start"></x-extension>
<template x-for="banner in $store.welcomeStore.sortedBanners" :key="banner.id">
<article class="welcome-banner" :class="$store.welcomeStore.getBannerClass(banner.type)">
<div class="welcome-banner-icon">
<span class="material-symbols-outlined" x-text="$store.welcomeStore.getBannerIcon(banner.type)"></span>
</div>
<div class="welcome-banner-content">
<div class="welcome-banner-title" x-text="banner.title"></div>
<div class="welcome-banner-html" x-html="banner.html"></div>
</div>
<button class="welcome-banner-dismiss"
type="button"
x-show="banner.dismissible !== false"
@click.stop="$store.welcomeStore.dismissBanner(banner.id)"
title="Dismiss"
aria-label="Dismiss">
<span class="material-symbols-outlined">close</span>
</button>
</article>
</template>
<x-extension id="welcome-banners-end"></x-extension>
</div>
<div class="welcome-action-card" @click="$store.welcomeStore.executeAction('website')">
<span class="material-symbols-outlined welcome-action-icon">language</span>
<h3 class="welcome-action-title">Visit Website</h3>
</div>
<x-extension id="welcome-actions-end"></x-extension>
</div>
<div class="welcome-actions-footer">
<button type="button"
class="welcome-refresh-button"
@click="$store.welcomeStore.refreshBanners(true)"
:disabled="$store.welcomeStore.bannersLoading"
title="Refresh banners">
<span class="material-symbols-outlined" :class="{ 'welcome-spin': $store.welcomeStore.bannersLoading }">refresh</span>
</button>
<button type="button"
class="welcome-refresh-button"
@click="$store.welcomeStore.undismissBanners()"
class="welcome-undismiss-button"
x-show="$store.welcomeStore.hasDismissedBanners"
:disabled="$store.welcomeStore.bannersLoading"
title="Undismiss all banners">
@click="$store.welcomeStore.undismissBanners()">
<span class="material-symbols-outlined">undo</span>
<span>Show dismissed notices</span>
</button>
</div>
</section>
<section class="welcome-quick-section">
<h2>Quick Actions</h2>
<div class="welcome-actions">
<x-extension id="welcome-actions-start"></x-extension>
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('projects')">
<span class="material-symbols-outlined welcome-action-icon">folder</span>
<span class="welcome-action-copy">
<span class="welcome-action-title">Projects</span>
<span class="welcome-action-description">Organize and track your work.</span>
</span>
<span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
</button>
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('memory')">
<span class="material-symbols-outlined welcome-action-icon">memory</span>
<span class="welcome-action-copy">
<span class="welcome-action-title">Memory</span>
<span class="welcome-action-description">Access saved context and knowledge.</span>
</span>
<span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
</button>
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('scheduler')">
<span class="material-symbols-outlined welcome-action-icon">schedule</span>
<span class="welcome-action-copy">
<span class="welcome-action-title">Tasks</span>
<span class="welcome-action-description">View and manage your tasks.</span>
</span>
<span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
</button>
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('files')">
<span class="material-symbols-outlined welcome-action-icon">folder_open</span>
<span class="welcome-action-copy">
<span class="welcome-action-title">Files</span>
<span class="welcome-action-description">Search and manage your files.</span>
</span>
<span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
</button>
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('settings')">
<span class="material-symbols-outlined welcome-action-icon">settings</span>
<span class="welcome-action-copy">
<span class="welcome-action-title">Settings</span>
<span class="welcome-action-description">Configure Agent Zero to your preferences.</span>
</span>
<span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
</button>
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('plugins')">
<span class="material-symbols-outlined welcome-action-icon">extension</span>
<span class="welcome-action-copy">
<span class="welcome-action-title">Plugins</span>
<span class="welcome-action-description">Extend Agent Zero with integrations.</span>
</span>
<span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
</button>
<button type="button" class="welcome-action-card is-wide" @click="$store.welcomeStore.executeAction('website')">
<span class="material-symbols-outlined welcome-action-icon">language</span>
<span class="welcome-action-copy">
<span class="welcome-action-title">Visit Website</span>
<span class="welcome-action-description">Explore Agent Zero online and resources.</span>
</span>
<span class="material-symbols-outlined welcome-action-arrow">open_in_new</span>
</button>
</div>
</section>
<section class="welcome-lower-grid">
<x-extension id="welcome-actions-end"></x-extension>
<article class="welcome-system-card"
x-show="$store.welcomeStore.systemResourceBanner"
x-transition.opacity>
<header class="welcome-panel-header">
<h2 x-text="$store.welcomeStore.systemResourceBanner?.title || 'System Resources'"></h2>
<span class="material-symbols-outlined">monitoring</span>
</header>
<div class="welcome-system-body"
x-html="$store.welcomeStore.systemResourceBanner?.html || ''"></div>
</article>
</section>
</main>
<!-- Banner Section -->
<div class="welcome-banners" x-show="$store.welcomeStore.banners && $store.welcomeStore.banners.length > 0">
<x-extension id="welcome-banners-start"></x-extension>
<template x-for="banner in $store.welcomeStore.sortedBanners" :key="banner.id">
<div class="welcome-banner" :class="$store.welcomeStore.getBannerClass(banner.type)">
<!-- Banner Icon -->
<div class="welcome-banner-icon">
<span class="material-symbols-outlined" x-text="$store.welcomeStore.getBannerIcon(banner.type)"></span>
</div>
<!-- Banner Content -->
<div class="welcome-banner-content">
<div class="welcome-banner-title" x-text="banner.title"></div>
<div class="welcome-banner-html" x-html="banner.html"></div>
</div>
<!-- Dismiss Button -->
<button class="welcome-banner-dismiss"
x-show="banner.dismissible !== false"
@click.stop="$store.welcomeStore.dismissBanner(banner.id)"
title="Dismiss">
<span class="material-symbols-outlined">close</span>
</button>
</div>
</template>
<x-extension id="welcome-banners-end"></x-extension>
</div>
</div>
<x-extension id="welcome-screen-end"></x-extension>
</div>
</template>
</div>
<style>
/* Welcome Screen Styles */
.welcome-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3%;
text-align: center;
position: relative;
min-height: 100vh;
overflow: hidden;
padding: clamp(4.8rem, 8vh, 6.4rem) clamp(1.25rem, 5.4vw, 6.5rem) 3.5rem;
background: var(--color-background);
color: var(--color-text);
min-height: 100vh;
position: relative;
overflow: hidden;
font-family: var(--font-family-main, "Rubik", Arial, Helvetica, sans-serif);
}
.welcome-container > x-extension,
.welcome-actions > x-extension,
.welcome-banners > x-extension {
.welcome-banners > x-extension,
.welcome-lower-grid > x-extension,
.welcome-lower-grid > x-extension > x-component,
.welcome-lower-grid > x-extension > x-component > div[x-data] {
display: contents;
}
.welcome-container::before {
content: "";
position: absolute;
inset: 0;
inset: -6% -8% -10% 10%;
background-image: url("./public/darkSymbol.svg");
background-repeat: no-repeat;
background-position: center;
background-size: 80% auto;
opacity: 0.07;
background-position: center top;
background-size: min(78vw, 980px) auto;
opacity: 0.105;
pointer-events: none;
}
.light-mode .welcome-container::before {
filter: invert(0.55);
opacity: 0.06;
opacity: 0.055;
}
.dark-mode .welcome-container::before {
filter: invert(0.85);
opacity: 0.08;
}
.welcome-content {
.welcome-shell {
position: relative;
z-index: 1;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.55rem;
width: min(100%, 1220px);
margin: 0 auto;
}
/* Welcome container styles */
.welcome-logo-container {
display: flex;
justify-content: center;
align-items: center;
}
.welcome-logo {
width: 7rem;
height: 7rem;
}
/* Themes adjustments for logo */
.light-mode .welcome-logo {
filter: invert(0.5);
}
.dark-mode .welcome-logo {
filter: invert(0.8);
}
.welcome-title {
font-size: 2rem;
font-weight: 400;
margin: var(--spacing-xs) 0 0 0;
.welcome-hero h1 {
margin: 0;
color: var(--color-text);
font-size: clamp(2rem, 3.4vw, 2.75rem);
font-weight: 440;
letter-spacing: 0;
line-height: 1.08;
}
.welcome-action-title {
.welcome-hero p {
margin: 0.5rem 0 0;
color: color-mix(in srgb, var(--color-text) 66%, transparent);
font-size: clamp(1.08rem, 1.8vw, 1.35rem);
line-height: 1.35;
}
.welcome-composer {
width: 100%;
}
.welcome-composer > x-component,
.welcome-composer > x-component > div[x-data],
.welcome-composer > x-component > div[x-data] > template + div {
display: contents;
}
.welcome-composer .input-row {
min-height: 4.9rem;
padding: 0.58rem 0.7rem 0.58rem 0.86rem;
border-radius: 16px;
background: color-mix(in srgb, var(--color-panel) 64%, var(--color-background) 36%);
}
.welcome-composer #chat-input {
min-height: 3.2rem;
font-size: 1.03rem;
}
.welcome-composer #expand-button {
display: none;
}
.welcome-composer .chat-more-panel {
top: calc(100% + 0.55rem);
bottom: auto;
margin-top: 0;
margin-bottom: 0;
}
.welcome-subtitle {
font-size: 1rem;
margin-bottom: 2rem;
color: var(--color-secondary);
max-width: 600px;
line-height: 1.5;
}
.welcome-actions {
.welcome-alerts {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
max-width: 640px;
width: 100%;
margin: 1.5rem 0;
gap: 0.7rem;
}
.welcome-actions-footer {
max-width: 640px;
width: 100%;
display: flex;
justify-content: flex-start;
gap: 0.5rem;
margin-top: -0.75rem;
}
.welcome-refresh-button {
width: 38px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--color-border);
border-radius: 10px;
color: var(--color-secondary);
cursor: pointer;
transition: all 0.2s ease;
}
.welcome-refresh-button:hover {
border-color: var(--color-primary);
background: var(--color-message-bg);
color: var(--color-text);
}
.welcome-refresh-button:disabled {
opacity: 0.6;
cursor: default;
}
.welcome-refresh-button .material-symbols-outlined {
font-size: 1.25rem;
}
.welcome-spin {
animation: welcome-spin 0.9s linear infinite;
}
@keyframes welcome-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.welcome-action-card {
background: var(--color-panel);
border: 1px solid var(--color-border);
border-radius: 12px;
padding: 0.6rem;
transition: all 0.3s ease;
cursor: pointer;
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
justify-content: center;
aspect-ratio: 1 / 1;
}
.welcome-action-card:hover {
border-color: var(--color-primary);
background: var(--color-message-bg);
}
.welcome-action-icon {
font-size: 2.1rem;
margin-bottom: 0.25rem;
color: var(--color-highlight-dark);
}
.welcome-action-title {
font-size: 0.9rem;
}
/* Banner Styles */
.welcome-banners {
display: flex;
flex-direction: column;
display: grid;
gap: 0.75rem;
max-width: 680px;
width: 100%;
margin-top: 1.5rem;
}
.welcome-banner,
.welcome-action-card,
.welcome-panel-card,
.welcome-system-card {
border: 1px solid color-mix(in srgb, var(--color-border) 66%, transparent);
background: color-mix(in srgb, var(--color-panel) 78%, var(--color-background) 22%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.026);
backdrop-filter: blur(18px);
}
.welcome-banner {
position: relative;
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
background: var(--color-panel);
border: 1px solid var(--color-border);
border-radius: 8px;
gap: 0.78rem;
padding: 0.9rem 1rem;
border-radius: 12px;
text-align: left;
position: relative;
transition: all 0.2s ease;
}
.welcome-banner:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* Banner Type Styles */
.welcome-banner.banner-info {
border-left: 4px solid #2196F3;
border-left: 4px solid #2f6bff;
}
.welcome-banner.banner-warning {
border-left: 4px solid #FF9800;
border-left: 4px solid #f59e0b;
}
.welcome-banner.banner-error {
border-left: 4px solid #F44336;
border-left: 4px solid #ef4444;
}
/* Banner Icon */
.welcome-banner-icon {
flex-shrink: 0;
display: flex;
width: 1.6rem;
height: 1.6rem;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-top: 2px;
color: #2f6bff;
}
.banner-warning .welcome-banner-icon {
color: #f59e0b;
}
.banner-error .welcome-banner-icon {
color: #ef4444;
}
.welcome-banner-icon .material-symbols-outlined {
font-size: 1.25rem;
}
.banner-info .welcome-banner-icon {
color: #2196F3;
}
.banner-warning .welcome-banner-icon {
color: #FF9800;
}
.banner-error .welcome-banner-icon {
color: #F44336;
}
/* Banner Content */
.welcome-banner-content {
flex: 1;
min-width: 0;
}
.welcome-banner-title {
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 0.25rem;
color: var(--color-text);
margin-bottom: 4px;
line-height: 1.3;
font-size: 0.92rem;
font-weight: 650;
line-height: 1.25;
}
.welcome-banner-html {
font-size: 0.85rem;
color: var(--color-secondary);
line-height: 1.5;
color: color-mix(in srgb, var(--color-text) 68%, transparent);
font-size: 0.84rem;
line-height: 1.45;
}
.welcome-banner-html a {
color: var(--color-primary);
text-decoration: underline;
cursor: pointer;
color: #5f83ff;
text-decoration: none;
}
.welcome-banner-html a:hover {
color: var(--color-highlight-dark);
text-decoration: underline;
}
.welcome-banner-html strong {
color: var(--color-text);
font-weight: 600;
}
/* Banner Dismiss Button */
.welcome-banner-dismiss {
flex-shrink: 0;
background: transparent;
border: none;
border-radius: 4px;
color: var(--color-secondary);
cursor: pointer;
padding: 4px;
display: flex;
.welcome-banner-dismiss,
.welcome-undismiss-button {
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
opacity: 0.6;
gap: 0.4rem;
border: 0;
background: transparent;
color: color-mix(in srgb, var(--color-text) 64%, transparent);
cursor: pointer;
font-family: var(--font-family-main, "Rubik", Arial, Helvetica, sans-serif);
}
.welcome-banner-dismiss:hover {
background: var(--color-border);
.welcome-banner-dismiss {
width: 2rem;
height: 2rem;
border-radius: 8px;
}
.welcome-banner-dismiss:hover,
.welcome-undismiss-button:hover {
color: var(--color-text);
opacity: 1;
background: color-mix(in srgb, var(--color-border) 45%, transparent);
}
.welcome-banner-dismiss .material-symbols-outlined {
font-size: 1.1rem;
.welcome-undismiss-button {
justify-self: start;
border-radius: 9px;
padding: 0.45rem 0.55rem;
font-size: 0.82rem;
}
/* Light mode adjustments */
.light-mode .welcome-title {
color: #2d2d2d;
.welcome-quick-section {
display: grid;
gap: 0.82rem;
}
.light-mode .welcome-subtitle {
color: #666;
}
.light-mode .welcome-action-title {
color: #2d2d2d;
}
.light-mode .welcome-action-description {
color: #666;
}
.light-mode .welcome-footer {
color: #666;
}
.light-mode .welcome-banner {
background: var(--color-panel);
}
.light-mode .welcome-banner-title {
.welcome-quick-section h2,
.welcome-panel-header h2 {
margin: 0;
color: var(--color-text);
font-size: 1.05rem;
font-weight: 520;
line-height: 1.2;
}
.light-mode .welcome-banner-html {
color: var(--color-primary);
.welcome-actions {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1rem;
width: 100%;
}
/* Responsive design */
@media (max-width: 768px) {
.welcome-action-card {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 1rem;
min-height: 6.1rem;
border-radius: 8px;
padding: 1.05rem 1rem;
color: inherit;
cursor: pointer;
font-family: var(--font-family-main, "Rubik", Arial, Helvetica, sans-serif);
text-align: left;
transition:
border-color 0.18s ease,
background-color 0.18s ease,
box-shadow 0.18s ease,
transform 0.18s ease;
}
.welcome-action-card.is-wide {
grid-column: span 2;
}
.welcome-action-card:hover,
.welcome-action-card:focus-visible {
border-color: color-mix(in srgb, var(--color-border) 88%, var(--color-text) 12%);
background: color-mix(in srgb, var(--color-panel) 86%, var(--color-background) 14%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.036),
0 16px 34px rgba(0, 0, 0, 0.18);
outline: none;
transform: translateY(-1px);
}
.welcome-action-icon {
color: #2f6bff;
font-size: 2.35rem;
line-height: 1;
}
.welcome-action-copy {
display: grid;
min-width: 0;
gap: 0.28rem;
}
.welcome-action-title {
color: var(--color-text);
font-size: 1rem;
font-weight: 520;
line-height: 1.25;
}
.welcome-action-description {
color: color-mix(in srgb, var(--color-text) 58%, transparent);
font-size: 0.92rem;
line-height: 1.42;
}
.welcome-action-arrow {
color: var(--color-text);
font-size: 1.35rem;
opacity: 0.8;
}
.welcome-lower-grid {
display: grid;
grid-template-columns: minmax(0, 1.06fr) minmax(320px, 0.94fr);
gap: 1rem;
align-items: stretch;
}
.welcome-lower-grid .discovery-slot {
grid-column: auto;
min-width: 0;
width: 100%;
}
.welcome-system-card {
min-width: 0;
border-radius: 8px;
padding: 1.6rem 1.7rem;
text-align: left;
}
.welcome-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.welcome-panel-header .material-symbols-outlined {
color: var(--color-text);
font-size: 1.35rem;
opacity: 0.86;
}
.system-resource-body {
display: grid;
gap: 1.22rem;
}
.system-resource-meter {
display: grid;
grid-template-columns: 8.2rem minmax(0, 1fr);
gap: 1rem;
align-items: center;
}
.system-resource-label {
display: grid;
gap: 0.38rem;
min-width: 0;
}
.system-resource-name,
.system-resource-detail-title {
color: color-mix(in srgb, var(--color-text) 58%, transparent);
font-size: 0.82rem;
font-weight: 430;
letter-spacing: 0.02em;
}
.system-resource-value,
.system-resource-detail-value {
color: color-mix(in srgb, var(--color-text) 76%, transparent);
font-size: 0.96rem;
font-variant-numeric: tabular-nums;
line-height: 1.2;
white-space: nowrap;
}
.system-resource-track {
height: 0.64rem;
overflow: hidden;
border-radius: 999px;
background: color-mix(in srgb, var(--color-border) 70%, transparent);
}
.system-resource-fill {
display: block;
width: var(--resource-value, 0%);
height: 100%;
border-radius: inherit;
background: var(--resource-color, #22c55e);
transition: width 0.22s ease;
}
.system-resource-footer {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem 1.5rem;
margin-top: 0.15rem;
padding-top: 1.02rem;
border-top: 1px solid color-mix(in srgb, var(--color-border) 55%, transparent);
}
.system-resource-detail-value {
margin-top: 0.48rem;
font-family: var(--font-family-code, "Roboto Mono", monospace);
font-size: 0.88rem;
}
@media (max-width: 1180px) {
.welcome-container {
padding: 1rem;
padding-top: 3rem;
}
.welcome-logo {
width: 5rem;
height: 5rem;
}
.welcome-logo-container {
margin-top: 2rem;
}
.welcome-title {
font-size: 1.5rem;
margin: 0 !important;
}
.welcome-subtitle {
font-size: 1rem;
margin-top: var(--spacing-xs);
margin-bottom: 0;
padding-inline: clamp(1.25rem, 4vw, 3rem);
}
.welcome-actions {
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
margin-bottom: 1.5rem;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.welcome-action-card {
aspect-ratio: 1 / 1;
padding: 0.5rem;
}
.welcome-action-icon {
font-size: 1.6rem;
}
.welcome-action-title {
font-size: 0.85rem;
}
.welcome-banners {
max-width: 100%;
}
.welcome-banner {
padding: 12px 14px;
}
.welcome-banner-title {
font-size: 0.85rem;
}
.welcome-banner-html {
font-size: 0.8rem;
.welcome-lower-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
@media (max-width: 720px) {
.welcome-container {
min-height: 100%;
padding: 4.5rem 1rem 2rem;
}
.welcome-shell {
gap: 1.2rem;
}
.welcome-actions {
grid-template-columns: 1fr;
gap: 0.5rem;
gap: 0.75rem;
}
.welcome-action-card {
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
flex-direction: row;
justify-content: flex-start;
align-items: center;
text-align: left;
border-radius: 10px;
min-height: 44px;
.welcome-action-card,
.welcome-action-card.is-wide {
grid-column: auto;
min-height: 5.25rem;
padding: 0.85rem;
}
.welcome-action-icon {
margin-bottom: 0;
margin-right: 0.5rem;
font-size: 1.35rem;
line-height: 1;
display: flex;
align-items: center;
font-size: 2rem;
}
.welcome-action-title {
font-size: 0.95rem;
margin: 0;
line-height: 1.2;
.welcome-composer .input-row {
min-height: 4.35rem;
}
.welcome-system-card {
padding: 1.15rem;
}
.system-resource-meter {
grid-template-columns: 1fr;
gap: 0.55rem;
}
.system-resource-footer {
grid-template-columns: 1fr;
}
}
</style>

View file

@ -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.
*

View file

@ -96,8 +96,12 @@
<x-component path="chat/top-section/chat-top.html"></x-component>
<!-- Welcome Screen Component -->
<div x-data x-show="$store.welcomeStore && $store.welcomeStore.isVisible">
<x-component path="welcome/welcome-screen.html"></x-component>
<div x-data>
<template x-if="$store.welcomeStore && $store.welcomeStore.isVisible">
<div class="welcome-host">
<x-component path="welcome/welcome-screen.html"></x-component>
</div>
</template>
</div>
<!-- Message History (actual messages) -->
<div id="chat-area-wrapper" x-data x-show="!$store.welcomeStore || !$store.welcomeStore.isVisible">
@ -135,11 +139,15 @@
<button class="toast__close">Close</button>
</div>
<!-- Chat Input Area -->
<div x-show="!$store.welcomeStore || !$store.welcomeStore.isVisible">
<!-- Progress Bar -->
<x-component path="chat/input/progress.html"></x-component>
<!-- Input Section -->
<x-component path="chat/input/chat-bar.html"></x-component>
<div x-data>
<template x-if="!$store.welcomeStore || !$store.welcomeStore.isVisible">
<div>
<!-- Progress Bar -->
<x-component path="chat/input/progress.html"></x-component>
<!-- Input Section -->
<x-component path="chat/input/chat-bar.html"></x-component>
</div>
</template>
</div>
</div>
<x-component path="canvas/right-canvas.html"></x-component>

View file

@ -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;