. Adjust chat layout dimensions and remove context indicator

Reduced footer height from 90px to 75px and adjusted related spacing
throughout
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-20 16:29:10 -03:00
parent 8c2b17c615
commit 564ad32417
5 changed files with 76 additions and 99 deletions

View file

@ -199,7 +199,7 @@ body::before {
top: 0;
left: 50%;
transform: translateX(-50%);
bottom: 100px;
bottom: 75px;
overflow-y: auto;
overflow-x: hidden;
padding: 80px 20px 40px;
@ -208,6 +208,7 @@ body::before {
z-index: 1;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
height: calc(100vh - 75px);
}
.message-container {
margin-bottom: 24px;
@ -322,7 +323,7 @@ footer {
z-index: 100;
transition: all 0.3s;
backdrop-filter: blur(20px);
height: 90px;
height: 75px;
}
.suggestions-container {
display: flex;
@ -424,7 +425,7 @@ footer {
}
.scroll-to-bottom {
position: fixed;
bottom: 80px;
bottom: 85px;
right: 20px;
width: 40px;
height: 40px;
@ -476,37 +477,6 @@ footer {
color: var(--bg);
transform: translateY(-2px);
}
.context-indicator {
position: fixed;
bottom: 130px;
right: 20px;
width: 120px;
border-radius: 12px;
padding: 10px;
font-size: 10px;
text-align: center;
z-index: 90;
background: var(--bg);
border: 1px solid var(--border);
display: none;
backdrop-filter: blur(10px);
}
.context-indicator.visible {
display: block;
}
.context-progress {
height: 3px;
background: var(--glass);
border-radius: 2px;
margin-top: 6px;
overflow: hidden;
}
.context-progress-bar {
height: 100%;
background: var(--accent);
border-radius: 2px;
transition: width 0.3s;
}
.connection-status {
position: fixed;
top: 20px;
@ -706,7 +676,8 @@ footer {
#messages {
padding: 80px 16px 40px;
top: 0;
bottom: 100px;
bottom: 75px;
height: calc(100vh - 75px);
}
.float-menu {
left: 12px;
@ -721,12 +692,7 @@ footer {
.scroll-to-bottom {
width: 36px;
height: 36px;
bottom: 70px;
bottom: 85px;
right: 12px;
}
.context-indicator {
bottom: 120px;
right: 12px;
width: 100px;
}
}

View file

@ -16,15 +16,4 @@
</div>
</footer>
<button class="scroll-to-bottom" id="scrollToBottom"></button>
<div class="context-indicator" id="contextIndicator">
<div>Context</div></div>
<div id="contextPercentage">0%</div>
<div class="context-progress">
<div
class="context-progress-bar"
id="contextProgressBar"
style="width: 0%"
></div>
</div>
</div>
</div>

View file

@ -55,9 +55,6 @@ function chatApp() {
sidebar,
themeBtn,
scrollToBottomBtn,
contextIndicator,
contextPercentage,
contextProgressBar,
sidebarTitle;
marked.setOptions({ breaks: true, gfm: true });
@ -194,9 +191,6 @@ function chatApp() {
sidebar = document.getElementById("sidebar");
themeBtn = document.getElementById("themeBtn");
scrollToBottomBtn = document.getElementById("scrollToBottom");
contextIndicator = document.getElementById("contextIndicator");
contextPercentage = document.getElementById("contextPercentage");
contextProgressBar = document.getElementById("contextProgressBar");
sidebarTitle = document.getElementById("sidebarTitle");
// Theme initialization and focus
@ -250,14 +244,6 @@ function chatApp() {
});
},
updateContextUsage(u) {
contextUsage = u;
const p = Math.min(100, Math.round(u * 100));
contextPercentage.textContent = `${p}%`;
contextProgressBar.style.width = `${p}%`;
contextIndicator.classList.remove("visible");
},
flashScreen() {
gsap.to(flashOverlay, {
opacity: 0.15,
@ -355,7 +341,6 @@ function chatApp() {
this.loadSessions();
messagesDiv.innerHTML = "";
this.clearSuggestions();
this.updateContextUsage(0);
if (isVoiceMode) {
await this.stopVoiceSession();
isVoiceMode = false;
@ -469,9 +454,6 @@ function chatApp() {
return;
}
if (r.context_usage !== undefined) {
this.updateContextUsage(r.context_usage);
}
if (r.suggestions && r.suggestions.length > 0) {
this.handleSuggestions(r.suggestions);
}
@ -516,7 +498,7 @@ function chatApp() {
this.showWarning(d.message);
break;
case "context_usage":
this.updateContextUsage(d.usage);
// Context usage removed
break;
case "change_theme":
if (d.color1) themeColor1 = d.color1;
@ -632,10 +614,8 @@ function chatApp() {
m.className = "message-container";
if (role === "user") {
m.innerHTML = `<div class="user-message"><div class="user-message-content">${this.escapeHtml(content)}</div></div>`;
this.updateContextUsage(contextUsage + 0.05);
} else if (role === "assistant") {
m.innerHTML = `<div class="assistant-message"><div class="assistant-avatar"></div><div class="assistant-message-content markdown-content" id="${msgId || ""}">${streaming ? "" : marked.parse(content)}</div></div>`;
this.updateContextUsage(contextUsage + 0.03);
} else if (role === "voice") {
m.innerHTML = `<div class="assistant-message"><div class="assistant-avatar">🎤</div><div class="assistant-message-content">${content}</div></div>`;
} else {
@ -728,10 +708,6 @@ function chatApp() {
ws.send(JSON.stringify(s));
});
await pendingContextChange;
const x = document.getElementById("contextIndicator");
if (x) {
document.getElementById("contextPercentage").textContent = c;
}
} else {
console.warn("WebSocket não está conectado. Tentando reconectar...");
this.connectWebSocket();
@ -948,7 +924,6 @@ function chatApp() {
toggleSidebar: toggleSidebar,
toggleTheme: toggleTheme,
applyTheme: applyTheme,
updateContextUsage: updateContextUsage,
flashScreen: flashScreen,
updateConnectionStatus: updateConnectionStatus,
getWebSocketUrl: getWebSocketUrl,
@ -979,6 +954,16 @@ function chatApp() {
startVoiceRecording: startVoiceRecording,
simulateVoiceTranscription: simulateVoiceTranscription,
scrollToBottom: scrollToBottom,
cleanup: function () {
// Cleanup WebSocket connection
if (ws) {
ws.close();
ws = null;
}
// Clear any pending timeouts/intervals
isConnecting = false;
isInitialized = false;
},
};
// Cache and return the singleton instance
@ -988,3 +973,14 @@ function chatApp() {
// Initialize the app
chatApp().init();
// Listen for section changes to cleanup when leaving chat
document.addEventListener("section-hidden", function (e) {
if (
e.target.id === "section-chat" &&
chatAppInstance &&
chatAppInstance.cleanup
) {
chatAppInstance.cleanup();
}
});

View file

@ -51,9 +51,8 @@
.logo-wrapper {
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
padding: 8px 12px;
padding: 8px;
border-radius: 12px;
transition: all 0.3s;
background: rgba(255, 255, 255, 0.9);
@ -67,8 +66,8 @@
}
.logo-icon {
width: 32px;
height: 32px;
width: 36px;
height: 36px;
background: url("https://pragmatismo.com.br/icons/general-bots.svg")
center/contain no-repeat;
}
@ -298,7 +297,6 @@
<div class="header-left">
<div class="logo-wrapper" onclick="window.location.reload()">
<div class="logo-icon"></div>
<div class="logo-text">General Bots</div>
</div>
</div>
@ -344,18 +342,6 @@
<div class="app-icon"></div>
<span>Mail</span>
</a>
<a
class="app-item"
href="#calendar"
data-section="calendar"
>
<div class="app-icon">📅</div>
<span>Calendar</span>
</a>
<a class="app-item" href="#notes" data-section="notes">
<div class="app-icon">📝</div>
<span>Notes</span>
</a>
</div>
</div>
<div class="user-avatar" id="userAvatar" title="User Account">

View file

@ -34,6 +34,20 @@ async function loadSectionHTML(path) {
async function switchSection(section) {
const mainContent = document.getElementById("main-content");
// Validate section exists
if (!sections[section]) {
console.warn(`Section "${section}" does not exist, defaulting to chat`);
section = "chat";
}
// Clean up any existing WebSocket connections from chat
if (
window.chatAppInstance &&
typeof window.chatAppInstance.cleanup === "function"
) {
window.chatAppInstance.cleanup();
}
try {
const htmlPath = sections[section];
console.log("Loading section:", section, "from", htmlPath);
@ -78,6 +92,11 @@ async function switchSection(section) {
});
targetDiv.style.display = "block";
} else {
// Remove any existing loading divs first
container.querySelectorAll(".loading").forEach((div) => {
div.remove();
});
// Show loading placeholder inside the container
const loadingDiv = document.createElement("div");
loadingDiv.className = "loading";
@ -95,15 +114,22 @@ async function switchSection(section) {
// Hide any existing sections
container.querySelectorAll(".section").forEach((div) => {
div.style.display = "none";
// Dispatch a custom event to notify sections they're being hidden
div.dispatchEvent(new CustomEvent("section-hidden"));
});
// Remove loading placeholder
container.removeChild(loadingDiv);
// Remove loading placeholder if it still exists
if (loadingDiv && loadingDiv.parentNode) {
container.removeChild(loadingDiv);
}
// Add the new section to the container and cache it
container.appendChild(wrapper);
sectionCache[section] = wrapper;
// Dispatch a custom event to notify the section it's being shown
wrapper.dispatchEvent(new CustomEvent("section-shown"));
// Ensure the new section is visible with a fast GSAP fade-in
gsap.fromTo(
wrapper,
@ -178,13 +204,27 @@ function getInitialSection() {
window.addEventListener("DOMContentLoaded", () => {
// Small delay to ensure all resources are loaded
setTimeout(() => {
switchSection(getInitialSection());
const section = getInitialSection();
// Ensure valid section
if (!sections[section]) {
window.location.hash = "#chat";
switchSection("chat");
} else {
switchSection(section);
}
}, 50);
});
// Handle browser back/forward navigation
window.addEventListener("popstate", () => {
switchSection(getInitialSection());
const section = getInitialSection();
// Ensure valid section
if (!sections[section]) {
window.location.hash = "#chat";
switchSection("chat");
} else {
switchSection(section);
}
});
// Make switchSection globally accessible