Refactor chat module to use unified theme system

Rewrites chat.css to use centralized CSS variables from app.css instead
of maintaining its own theme definitions. Moves all theme variables
(colors, spacing, shadows, transitions) to app.css as the single source
of truth. Improves chat UI consistency, adds better connection status
indicators, and enhances responsive design.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-20 21:09:23 -03:00
parent eb4084fe2d
commit 444b424739
5 changed files with 989 additions and 1040 deletions

File diff suppressed because it is too large Load diff

View file

@ -16,4 +16,5 @@
</div> </div>
</footer> </footer>
<button class="scroll-to-bottom" id="scrollToBottom"></button> <button class="scroll-to-bottom" id="scrollToBottom"></button>
<div class="flash-overlay" id="flashOverlay"></div>
</div> </div>

View file

@ -187,11 +187,15 @@ function chatApp() {
connectionStatus = document.getElementById("connectionStatus"); connectionStatus = document.getElementById("connectionStatus");
flashOverlay = document.getElementById("flashOverlay"); flashOverlay = document.getElementById("flashOverlay");
suggestionsContainer = document.getElementById("suggestions"); suggestionsContainer = document.getElementById("suggestions");
floatLogo = document.getElementById("floatLogo");
sidebar = document.getElementById("sidebar");
themeBtn = document.getElementById("themeBtn");
scrollToBottomBtn = document.getElementById("scrollToBottom"); scrollToBottomBtn = document.getElementById("scrollToBottom");
sidebarTitle = document.getElementById("sidebarTitle");
console.log("Chat DOM elements initialized:", {
messagesDiv: !!messagesDiv,
messageInputEl: !!messageInputEl,
sendBtn: !!sendBtn,
voiceBtn: !!voiceBtn,
connectionStatus: !!connectionStatus,
});
// Theme initialization and focus // Theme initialization and focus
const savedTheme = localStorage.getItem("gb-theme") || "auto"; const savedTheme = localStorage.getItem("gb-theme") || "auto";
@ -231,10 +235,15 @@ function chatApp() {
}); });
} }
if (sendBtn) {
sendBtn.onclick = () => this.sendMessage(); sendBtn.onclick = () => this.sendMessage();
}
if (messageInputEl) {
messageInputEl.addEventListener("keypress", (e) => { messageInputEl.addEventListener("keypress", (e) => {
if (e.key === "Enter") this.sendMessage(); if (e.key === "Enter") this.sendMessage();
}); });
}
// Don't auto-reconnect on focus in browser to prevent multiple connections // Don't auto-reconnect on focus in browser to prevent multiple connections
// Tauri doesn't fire focus events the same way // Tauri doesn't fire focus events the same way
@ -263,7 +272,14 @@ function chatApp() {
}, },
updateConnectionStatus(s) { updateConnectionStatus(s) {
if (!connectionStatus) return;
connectionStatus.className = `connection-status ${s}`; connectionStatus.className = `connection-status ${s}`;
const statusText = {
connected: "Connected",
connecting: "Connecting...",
disconnected: "Disconnected",
};
connectionStatus.innerHTML = `<span>${statusText[s] || s}</span>`;
}, },
getWebSocketUrl() { getWebSocketUrl() {
@ -513,9 +529,6 @@ function chatApp() {
if (d.color2) themeColor2 = d.color2; if (d.color2) themeColor2 = d.color2;
if (d.logo_url) customLogoUrl = d.logo_url; if (d.logo_url) customLogoUrl = d.logo_url;
if (d.title) document.title = d.title; if (d.title) document.title = d.title;
if (d.logo_text) {
sidebarTitle.textContent = d.logo_text;
}
this.applyTheme(); this.applyTheme();
break; break;
} }
@ -526,41 +539,29 @@ function chatApp() {
const t = document.createElement("div"); const t = document.createElement("div");
t.id = "thinking-indicator"; t.id = "thinking-indicator";
t.className = "message-container"; t.className = "message-container";
t.innerHTML = `<div class="assistant-message"><div class="assistant-avatar"></div><div class="thinking-indicator"><div class="typing-dots"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div></div></div>`; t.innerHTML = `<div class="assistant-message"><div class="assistant-avatar"></div><div class="thinking-indicator"><div class="thinking-dot"></div><div class="thinking-dot"></div><div class="thinking-dot"></div></div></div>`;
if (messagesDiv) {
messagesDiv.appendChild(t); messagesDiv.appendChild(t);
gsap.to(t, { opacity: 1, y: 0, duration: 0.3, ease: "power2.out" });
if (!isUserScrolling) { if (!isUserScrolling) {
this.scrollToBottom(); this.scrollToBottom();
} }
}
isThinking = true;
thinkingTimeout = setTimeout(() => { thinkingTimeout = setTimeout(() => {
if (isThinking) { if (isThinking) {
this.hideThinkingIndicator(); this.hideThinkingIndicator();
this.showWarning( this.showWarning("A resposta está demorando mais que o esperado...");
"O servidor pode estar ocupado. A resposta está demorando demais.",
);
} }
}, 60000); }, 30000);
isThinking = true;
}, },
hideThinkingIndicator() { hideThinkingIndicator() {
if (!isThinking) return; if (!isThinking) return;
const t = document.getElementById("thinking-indicator"); const t = document.getElementById("thinking-indicator");
if (t) { if (t && t.parentNode) {
gsap.to(t, {
opacity: 0,
duration: 0.2,
onComplete: () => {
if (t.parentNode) {
t.remove(); t.remove();
} }
},
});
}
if (thinkingTimeout) {
clearTimeout(thinkingTimeout);
thinkingTimeout = null;
}
isThinking = false; isThinking = false;
}, },
@ -568,31 +569,29 @@ function chatApp() {
const w = document.createElement("div"); const w = document.createElement("div");
w.className = "warning-message"; w.className = "warning-message";
w.innerHTML = `⚠️ ${m}`; w.innerHTML = `⚠️ ${m}`;
if (messagesDiv) {
messagesDiv.appendChild(w); messagesDiv.appendChild(w);
gsap.from(w, { opacity: 0, y: 20, duration: 0.4, ease: "power2.out" });
if (!isUserScrolling) { if (!isUserScrolling) {
this.scrollToBottom(); this.scrollToBottom();
} }
setTimeout(() => { setTimeout(() => {
if (w.parentNode) { if (w.parentNode) {
gsap.to(w, { w.remove();
opacity: 0,
duration: 0.3,
onComplete: () => w.remove(),
});
} }
}, 5000); }, 5000);
}
}, },
showContinueButton() { showContinueButton() {
const c = document.createElement("div"); const c = document.createElement("div");
c.className = "message-container"; c.className = "message-container";
c.innerHTML = `<div class="assistant-message"><div class="assistant-avatar"></div><div class="assistant-message-content"><p>A conexão foi interrompida. Clique em "Continuar" para tentar recuperar a resposta.</p><button class="continue-button" onclick="this.parentElement.parentElement.parentElement.remove();">Continuar</button></div></div>`; c.innerHTML = `<div class="assistant-message"><div class="assistant-avatar"></div><div class="assistant-message-content"><p>A conexão foi interrompida. Clique em "Continuar" para tentar recuperar a resposta.</p><button class="continue-button" onclick="this.parentElement.parentElement.parentElement.remove();">Continuar</button></div></div>`;
if (messagesDiv) {
messagesDiv.appendChild(c); messagesDiv.appendChild(c);
gsap.to(c, { opacity: 1, y: 0, duration: 0.5, ease: "power2.out" });
if (!isUserScrolling) { if (!isUserScrolling) {
this.scrollToBottom(); this.scrollToBottom();
} }
}
}, },
continueInterruptedResponse() { continueInterruptedResponse() {
@ -629,11 +628,12 @@ function chatApp() {
} else { } else {
m.innerHTML = `<div class="assistant-message"><div class="assistant-avatar"></div><div class="assistant-message-content">${content}</div></div>`; m.innerHTML = `<div class="assistant-message"><div class="assistant-avatar"></div><div class="assistant-message-content">${content}</div></div>`;
} }
if (messagesDiv) {
messagesDiv.appendChild(m); messagesDiv.appendChild(m);
gsap.to(m, { opacity: 1, y: 0, duration: 0.5, ease: "power2.out" });
if (!isUserScrolling) { if (!isUserScrolling) {
this.scrollToBottom(); this.scrollToBottom();
} }
}
}, },
updateStreamingMessage(c) { updateStreamingMessage(c) {
@ -680,9 +680,13 @@ function chatApp() {
b.className = "suggestion-button"; b.className = "suggestion-button";
b.onclick = () => { b.onclick = () => {
this.setContext(v.context); this.setContext(v.context);
if (messageInputEl) {
messageInputEl.value = ""; messageInputEl.value = "";
}
}; };
if (suggestionsContainer) {
suggestionsContainer.appendChild(b); suggestionsContainer.appendChild(b);
}
}); });
}, },

File diff suppressed because it is too large Load diff

View file

@ -16,8 +16,8 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif; Helvetica, Arial, sans-serif;
background: #ffffff; background: var(--primary-bg);
color: #202124; color: var(--primary-fg);
overflow: hidden; overflow: hidden;
} }
@ -55,14 +55,14 @@
padding: 8px; padding: 8px;
border-radius: 12px; border-radius: 12px;
transition: all 0.3s; transition: all 0.3s;
background: rgba(255, 255, 255, 0.9); background: var(--glass-bg);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-sm);
} }
.logo-wrapper:hover { .logo-wrapper:hover {
background: rgba(255, 255, 255, 1); background: var(--bg-hover);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: var(--shadow-md);
} }
.logo-icon { .logo-icon {
@ -75,7 +75,7 @@
.logo-text { .logo-text {
font-size: 18px; font-size: 18px;
font-weight: 500; font-weight: 500;
color: #202124; color: var(--text-primary);
} }
/* Right side - Apps menu and avatar */ /* Right side - Apps menu and avatar */
@ -97,20 +97,20 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.3s; transition: all 0.3s;
color: #5f6368; color: var(--text-secondary);
position: relative; position: relative;
background: rgba(255, 255, 255, 0.9); background: var(--glass-bg);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-sm);
} }
.apps-menu-btn:hover { .apps-menu-btn:hover {
background: rgba(255, 255, 255, 1); background: var(--bg-hover);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: var(--shadow-md);
} }
.apps-menu-btn:active { .apps-menu-btn:active {
background: rgba(255, 255, 255, 0.95); background: var(--bg-active);
transform: scale(0.95); transform: scale(0.95);
} }
@ -120,11 +120,9 @@
top: 60px; top: 60px;
right: 80px; right: 80px;
width: 320px; width: 320px;
background: white; background: var(--glass-bg);
border-radius: 16px; border-radius: 16px;
box-shadow: box-shadow: var(--shadow-xl);
0 1px 2px 0 rgba(60, 64, 67, 0.3),
0 2px 6px 2px rgba(60, 64, 67, 0.15);
padding: 20px; padding: 20px;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;