botui/ui/suite/index.html

3640 lines
170 KiB
HTML
Raw Normal View History

2025-12-03 18:42:22 -03:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Chat - General Bots</title>
2025-12-03 18:42:22 -03:00
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta
name="description"
content="General Bots - AI-powered workspace"
/>
<meta name="theme-color" content="#d4f505" />
2025-12-03 18:42:22 -03:00
<!-- Styles -->
<link rel="stylesheet" href="css/app.css" />
<link rel="stylesheet" href="css/apps-extended.css" />
<link rel="stylesheet" href="css/components.css" />
<link rel="stylesheet" href="css/base.css" />
<link rel="stylesheet" href="css/partials.css" />
<link rel="stylesheet" href="css/theme-sentient.css" />
<!-- App-specific CSS -->
<link rel="stylesheet" href="chat/chat.css" />
<link rel="stylesheet" href="calendar/calendar.css" />
<link rel="stylesheet" href="drive/drive.css" />
<link rel="stylesheet" href="mail/mail.css" />
<link rel="stylesheet" href="meet/meet.css" />
<link rel="stylesheet" href="paper/paper.css" />
<link rel="stylesheet" href="sheet/sheet.css" />
<link rel="stylesheet" href="slides/slides.css" />
<link rel="stylesheet" href="research/research.css" />
<link rel="stylesheet" href="tasks/tasks.css?v=20251230" />
<link rel="stylesheet" href="tasks/taskmd.css?v=20251230" />
<link rel="stylesheet" href="analytics/analytics.css" />
<link rel="stylesheet" href="dashboards/dashboards.css" />
<link rel="stylesheet" href="monitoring/monitoring.css" />
<link rel="stylesheet" href="crm/crm.css" />
<link rel="stylesheet" href="billing/billing.css" />
<link rel="stylesheet" href="products/products.css" />
<link rel="stylesheet" href="tickets/tickets.css" />
2025-12-03 18:42:22 -03:00
<!-- Local Libraries (no external CDN dependencies) -->
<script src="js/vendor/htmx.min.js"></script>
<script src="js/vendor/htmx-ws.js"></script>
<script src="js/vendor/htmx-json-enc.js"></script>
<script src="js/vendor/marked.min.js"></script>
<!-- SECURITY BOOTSTRAP - MUST load immediately after HTMX -->
<!-- This provides centralized auth for ALL apps: HTMX, fetch, XHR -->
<script src="js/security-bootstrap.js?v=20260110"></script>
<!-- i18n -->
<script src="js/i18n.js"></script>
<!-- Enable HTMX to process inline scripts in swapped content -->
<script>
htmx.config.allowEval = true;
htmx.config.includeIndicatorStyles = false;
htmx.config.timeout = 300000; // 5 minutes for long-running LLM operations
// Global error handler to catch HTMX DOM manipulation errors
window.addEventListener("error", function (event) {
// Catch insertBefore errors from HTMX swap operations
if (
event.message &&
event.message.includes("insertBefore") &&
event.message.includes("null")
) {
console.warn(
"HTMX DOM error caught and suppressed:",
event.message,
);
event.preventDefault();
return true;
}
});
// Handle unhandled promise rejections from HTMX
window.addEventListener("unhandledrejection", function (event) {
if (
event.reason &&
event.reason.message &&
event.reason.message.includes("insertBefore")
) {
console.warn(
"HTMX promise rejection caught:",
event.reason.message,
);
event.preventDefault();
}
});
</script>
2025-12-03 18:42:22 -03:00
</head>
<body data-theme="sentient">
2025-12-03 18:42:22 -03:00
<!-- Loading overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner"></div>
</div>
<!-- Floating header -->
<header class="float-header" role="banner">
<!-- Left: Logo + App Tabs -->
2025-12-03 18:42:22 -03:00
<div class="header-left">
<button
class="logo-wrapper"
hx-get="/suite/home.html"
hx-target="#main-content"
hx-push-url="/#home"
title="General Bots"
aria-label="General Bots - Home"
2025-12-03 18:42:22 -03:00
>
<svg
2025-12-03 18:42:22 -03:00
class="logo-icon"
width="32"
height="20"
viewBox="0 0 140 80"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<!-- Left antenna -->
<line x1="5" y1="40" x2="25" y2="40" />
<line x1="25" y1="28" x2="25" y2="52" />
<!-- Left gear (outer) -->
<circle cx="50" cy="40" r="16" />
<!-- Left gear (inner) -->
<circle cx="50" cy="40" r="6" />
<!-- Left gear teeth -->
<line x1="50" y1="20" x2="50" y2="26" />
<line x1="50" y1="54" x2="50" y2="60" />
<line x1="30" y1="40" x2="36" y2="40" />
<line x1="64" y1="40" x2="70" y2="40" />
<line x1="38" y1="28" x2="42" y2="32" />
<line x1="58" y1="48" x2="62" y2="52" />
<line x1="38" y1="52" x2="42" y2="48" />
<line x1="58" y1="32" x2="62" y2="28" />
<!-- Right gear (outer) -->
<circle cx="90" cy="40" r="16" />
<!-- Right gear (inner) -->
<circle cx="90" cy="40" r="6" />
<!-- Right gear teeth -->
<line x1="90" y1="20" x2="90" y2="26" />
<line x1="90" y1="54" x2="90" y2="60" />
<line x1="70" y1="40" x2="76" y2="40" />
<line x1="104" y1="40" x2="110" y2="40" />
<line x1="78" y1="28" x2="82" y2="32" />
<line x1="98" y1="48" x2="102" y2="52" />
<line x1="78" y1="52" x2="82" y2="48" />
<line x1="98" y1="32" x2="102" y2="28" />
<!-- Right antenna -->
<line x1="115" y1="40" x2="135" y2="40" />
<line x1="115" y1="28" x2="115" y2="52" />
</svg>
2025-12-03 18:42:22 -03:00
</button>
<!-- Main App Tabs -->
<nav class="header-app-tabs">
<a
class="app-tab active"
href="#chat"
data-section="chat"
hx-get="/suite/chat/chat.html"
hx-target="#main-content"
hx-push-url="/#chat"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
/>
</svg>
<span data-i18n="nav-chat">Chat</span>
</a>
<a
class="app-tab"
href="#mail"
data-section="mail"
hx-get="/suite/mail/mail.html"
hx-target="#main-content"
hx-push-url="/#mail"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
/>
<polyline points="22,6 12,13 2,6" />
</svg>
<span data-i18n="nav-mail">E-mail</span>
</a>
<a
class="app-tab"
href="#calendar"
data-section="calendar"
hx-get="/suite/calendar/calendar.html"
hx-target="#main-content"
hx-push-url="/#calendar"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="4"
width="18"
height="18"
rx="2"
ry="2"
/>
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
<span data-i18n="nav-calendar">Calendário</span>
</a>
<a
class="app-tab"
href="#drive"
data-section="drive"
hx-get="/suite/drive/drive.html"
hx-target="#main-content"
hx-push-url="/#drive"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
/>
</svg>
<span data-i18n="nav-drive">Arquivos</span>
</a>
<a
class="app-tab"
href="#tasks"
data-section="tasks"
hx-get="/suite/tasks/tasks.html"
hx-target="#main-content"
hx-push-url="/#tasks"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M9 11l3 3L22 4" />
<path
d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"
/>
</svg>
<span data-i18n="nav-tasks">Tarefas</span>
</a>
<a
class="app-tab"
href="#crm"
data-section="crm"
hx-get="/suite/crm/crm.html"
hx-target="#main-content"
hx-push-url="/#crm"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"
/>
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
<span data-i18n="nav-crm">CRM</span>
</a>
<a
class="app-tab"
href="#docs"
data-section="docs"
hx-get="/suite/docs/docs.html"
hx-target="#main-content"
hx-push-url="/#docs"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>
<span data-i18n="nav-docs">Docs</span>
</a>
<a
class="app-tab"
href="#sheet"
data-section="sheet"
hx-get="/suite/sheet/sheet.html"
hx-target="#main-content"
hx-push-url="/#sheet"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
/>
<line x1="3" y1="9" x2="21" y2="9" />
<line x1="3" y1="15" x2="21" y2="15" />
<line x1="9" y1="3" x2="9" y2="21" />
<line x1="15" y1="3" x2="15" y2="21" />
</svg>
<span data-i18n="nav-sheet">Planilhas</span>
</a>
<a
class="app-tab"
href="#slides"
data-section="slides"
hx-get="/suite/slides/slides.html"
hx-target="#main-content"
hx-push-url="/#slides"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="2"
y="3"
width="20"
height="14"
rx="2"
ry="2"
/>
<line x1="8" y1="21" x2="16" y2="21" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
<span data-i18n="nav-slides">Apresentações</span>
</a>
<a
class="app-tab"
href="#social"
data-section="social"
hx-get="/suite/social/social.html"
hx-target="#main-content"
hx-push-url="/#social"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"
/>
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
<span data-i18n="nav-social">Social</span>
</a>
</nav>
2025-12-03 18:42:22 -03:00
<!-- Apps menu container (button + dropdown) -->
<div class="apps-menu-container" style="position: relative">
<button
class="icon-button apps-button"
id="appsButton"
title="All Applications"
aria-label="Open applications menu"
aria-expanded="false"
aria-haspopup="true"
2025-12-03 18:42:22 -03:00
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<circle cx="5" cy="12" r="2.5"></circle>
<circle cx="12" cy="12" r="2.5"></circle>
<circle cx="19" cy="12" r="2.5"></circle>
</svg>
</button>
<!-- Apps dropdown menu -->
<nav
class="apps-dropdown"
id="appsDropdown"
role="menu"
aria-label="Applications"
>
<div
class="apps-dropdown-title"
data-i18n="nav-all-apps"
>
All Applications
</div>
<div class="app-grid" role="group">
<!-- =================================== -->
<!-- DYNAMIC ITEMS (Header Tab Apps) -->
<!-- These hide/show based on screen -->
<!-- =================================== -->
<!-- Chat -->
<a
class="app-item active"
href="#chat"
data-section="chat"
role="menuitem"
aria-label="Chat application"
hx-get="/suite/chat/chat.html"
hx-target="#main-content"
hx-push-url="/#chat"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
/>
</svg>
</div>
<span data-i18n="nav-chat">Chat</span>
</a>
<!-- Mail -->
<a
class="app-item"
href="#mail"
data-section="mail"
role="menuitem"
aria-label="Mail application"
hx-get="/suite/mail/mail.html"
hx-target="#main-content"
hx-push-url="/#mail"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
/>
<polyline points="22,6 12,13 2,6" />
</svg>
</div>
<span data-i18n="nav-mail">E-mail</span>
</a>
<!-- Calendar -->
<a
class="app-item"
href="#calendar"
data-section="calendar"
role="menuitem"
aria-label="Calendar application"
hx-get="/suite/calendar/calendar.html"
hx-target="#main-content"
hx-push-url="/#calendar"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="4"
width="18"
height="18"
rx="2"
ry="2"
/>
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
</div>
<span data-i18n="nav-calendar">Calendário</span>
</a>
<!-- Drive -->
<a
class="app-item"
href="#drive"
data-section="drive"
role="menuitem"
aria-label="Drive application"
hx-get="/suite/drive/drive.html"
hx-target="#main-content"
hx-push-url="/#drive"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
/>
</svg>
</div>
<span data-i18n="nav-drive">Arquivos</span>
</a>
<!-- Tasks -->
<a
class="app-item"
href="#tasks"
data-section="tasks"
role="menuitem"
aria-label="Tasks application"
hx-get="/suite/tasks/tasks.html"
hx-target="#main-content"
hx-push-url="/#tasks"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M9 11l3 3L22 4" />
<path
d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"
/>
</svg>
</div>
<span data-i18n="nav-tasks">Tarefas</span>
</a>
<!-- Docs -->
<a
class="app-item"
href="#docs"
data-section="docs"
role="menuitem"
aria-label="Docs - Documents"
hx-get="/suite/docs/docs.html"
hx-target="#main-content"
hx-push-url="/#docs"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>
</div>
<span data-i18n="nav-docs">Docs</span>
</a>
<!-- Sheet -->
<a
class="app-item"
href="#sheet"
data-section="sheet"
role="menuitem"
aria-label="Sheet - Spreadsheets"
hx-get="/suite/sheet/sheet.html"
hx-target="#main-content"
hx-push-url="/#sheet"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
/>
<line x1="3" y1="9" x2="21" y2="9" />
<line x1="3" y1="15" x2="21" y2="15" />
<line x1="9" y1="3" x2="9" y2="21" />
<line x1="15" y1="3" x2="15" y2="21" />
</svg>
</div>
<span data-i18n="nav-sheet">Planilhas</span>
</a>
<!-- Slides -->
<a
class="app-item"
href="#slides"
data-section="slides"
role="menuitem"
aria-label="Slides - Presentations"
hx-get="/suite/slides/slides.html"
hx-target="#main-content"
hx-push-url="/#slides"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="2"
y="3"
width="20"
height="14"
rx="2"
ry="2"
/>
<line x1="8" y1="21" x2="16" y2="21" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
</div>
<span data-i18n="nav-slides"
>Apresentações</span
>
</a>
<!-- Social -->
<a
class="app-item"
href="#social"
data-section="social"
role="menuitem"
aria-label="Social Network"
hx-get="/suite/social/social.html"
hx-target="#main-content"
hx-push-url="/#social"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"
/>
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</div>
<span data-i18n="nav-social">Social</span>
</a>
<!-- =================================== -->
<!-- STATIC ITEMS (Always visible) -->
<!-- =================================== -->
<!-- People (Contacts) - First static item after dynamic -->
<a
class="app-item"
href="#people"
data-section="people"
role="menuitem"
aria-label="People - Contacts"
hx-get="/suite/people/people.html"
hx-target="#main-content"
hx-push-url="/#people"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"
/>
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</div>
<span data-i18n="nav-people">People</span>
</a>
<!-- CRM -->
<a
class="app-item"
href="#crm"
data-section="crm"
role="menuitem"
aria-label="CRM - Customer Relationship Management"
hx-get="/suite/crm/crm.html"
hx-target="#main-content"
hx-push-url="/#crm"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"
/>
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
<line x1="19" y1="8" x2="19" y2="14" />
<line x1="22" y1="11" x2="16" y2="11" />
</svg>
</div>
<span data-i18n="nav-crm">CRM</span>
</a>
<!-- Billing -->
<a
class="app-item"
href="#billing"
data-section="billing"
role="menuitem"
aria-label="Billing - Invoices & Payments"
hx-get="/suite/billing/billing.html"
hx-target="#main-content"
hx-push-url="/#billing"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="1"
y="4"
width="22"
height="16"
rx="2"
ry="2"
/>
<line x1="1" y1="10" x2="23" y2="10" />
</svg>
</div>
<span data-i18n="nav-billing">Billing</span>
</a>
<!-- Products -->
<a
class="app-item"
href="#products"
data-section="products"
role="menuitem"
aria-label="Products - Product & Service Catalog"
hx-get="/suite/products/products.html"
hx-target="#main-content"
hx-push-url="/#products"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
/>
<polyline
points="3.27 6.96 12 12.01 20.73 6.96"
/>
<line
x1="12"
y1="22.08"
x2="12"
y2="12"
/>
</svg>
</div>
<span data-i18n="nav-products">Products</span>
</a>
<!-- Tickets -->
<a
class="app-item"
href="#tickets"
data-section="tickets"
role="menuitem"
aria-label="Tickets - Support Cases"
hx-get="/suite/tickets/tickets.html"
hx-target="#main-content"
hx-push-url="/#tickets"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>
</div>
<span data-i18n="nav-tickets">Tickets</span>
</a>
<!-- Paper -->
<a
class="app-item"
href="#paper"
data-section="paper"
role="menuitem"
aria-label="Paper"
hx-get="/suite/paper/paper.html"
hx-target="#main-content"
hx-push-url="/#paper"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<line x1="10" y1="9" x2="8" y2="9" />
</svg>
</div>
<span data-i18n="nav-paper">Paper</span>
</a>
<!-- Editor -->
<a
class="app-item"
href="#editor"
data-section="editor"
role="menuitem"
aria-label="Editor - Code & Text"
hx-get="/editor.html"
hx-target="#main-content"
hx-push-url="/#editor"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" />
<line x1="12" y1="2" x2="12" y2="22" />
</svg>
</div>
<span data-i18n="nav-editor">Editor</span>
</a>
<a
class="app-item"
href="#research"
data-section="research"
role="menuitem"
aria-label="Research application"
hx-get="/suite/research/research.html"
hx-target="#main-content"
hx-push-url="/#research"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
<path d="M11 8v6M8 11h6" />
</svg>
</div>
<span data-i18n="nav-research">Pesquisa</span>
</a>
<!-- Meet -->
<a
class="app-item"
href="#meet"
data-section="meet"
role="menuitem"
aria-label="Meet application"
hx-get="/suite/meet/meet.html"
hx-target="#main-content"
hx-push-url="/#meet"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polygon
points="23 7 16 12 23 17 23 7"
/>
<rect
x="1"
y="5"
width="15"
height="14"
rx="2"
ry="2"
/>
</svg>
</div>
<span data-i18n="nav-meet">Reuniões</span>
</a>
<!-- Analytics -->
<a
class="app-item"
href="#analytics"
data-section="analytics"
role="menuitem"
aria-label="Analytics Dashboard"
hx-get="/suite/analytics/analytics.html"
hx-target="#main-content"
hx-push-url="/#analytics"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="20" x2="18" y2="10" />
<line x1="12" y1="20" x2="12" y2="4" />
<line x1="6" y1="20" x2="6" y2="14" />
</svg>
</div>
<span data-i18n="nav-analytics">Analytics</span>
</a>
<!-- Dashboards -->
<a
class="app-item"
href="#dashboards"
data-section="dashboards"
role="menuitem"
aria-label="Custom Dashboards"
hx-get="/suite/dashboards/dashboards.html"
hx-target="#main-content"
hx-push-url="/#dashboards"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="7"
height="7"
rx="1"
/>
<rect
x="14"
y="3"
width="7"
height="7"
rx="1"
/>
<rect
x="3"
y="14"
width="7"
height="7"
rx="1"
/>
<rect
x="14"
y="14"
width="7"
height="7"
rx="1"
/>
</svg>
</div>
<span data-i18n="nav-dashboards"
>Dashboards</span
>
</a>
<!-- Monitoring -->
<a
class="app-item"
href="#monitoring"
data-section="monitoring"
role="menuitem"
aria-label="System Monitoring"
hx-get="/suite/monitoring/monitoring.html"
hx-target="#main-content"
hx-push-url="/#monitoring"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="6" />
<circle cx="12" cy="12" r="2" />
</svg>
</div>
<span data-i18n="nav-monitoring"
>Monitoring</span
>
</a>
<!-- Sources -->
<a
class="app-item"
href="#sources"
data-section="sources"
role="menuitem"
aria-label="Data Sources"
hx-get="/suite/sources/index.html"
hx-target="#main-content"
hx-push-url="/#sources"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path
d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"
/>
<path
d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"
/>
</svg>
</div>
<span data-i18n="nav-sources">Sources</span>
</a>
<!-- Security -->
<a
class="app-item"
href="#security"
data-section="security"
role="menuitem"
aria-label="Security"
hx-get="/suite/tools/security.html"
hx-target="#main-content"
hx-push-url="/#security"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
/>
<path d="M9 12l2 2 4-4" />
</svg>
</div>
<span data-i18n="nav-security">Security</span>
</a>
<!-- Designer (.bas Editor) -->
<a
class="app-item"
href="#designer"
data-section="designer"
role="menuitem"
aria-label="Dialog Designer"
hx-get="/designer.html"
hx-target="#main-content"
hx-push-url="/#designer"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M12 2L2 7l10 5 10-5-10-5z" />
<path d="M2 17l10 5 10-5" />
<path d="M2 12l10 5 10-5" />
</svg>
</div>
<span data-i18n="nav-designer">Designer</span>
</a>
<!-- Attendant -->
<a
class="app-item"
href="#attendant"
data-section="attendant"
role="menuitem"
aria-label="Attendant"
hx-get="/suite/attendant/index.html"
hx-target="#main-content"
hx-push-url="/#attendant"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"
/>
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
<path d="M12 2v2" />
</svg>
</div>
<span data-i18n="nav-attendant">Attendant</span>
</a>
<!-- Project -->
<a
class="app-item"
href="#project"
data-section="project"
role="menuitem"
aria-label="Project Management"
hx-get="/suite/project/project.html"
hx-target="#main-content"
hx-push-url="/#project"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
/>
<path d="M3 9h18" />
<path d="M9 21V9" />
</svg>
</div>
<span data-i18n="nav-project">Project</span>
</a>
<!-- Canvas -->
<a
class="app-item"
href="#canvas"
data-section="canvas"
role="menuitem"
aria-label="Canvas Whiteboard"
hx-get="/suite/canvas/canvas.html"
hx-target="#main-content"
hx-push-url="/#canvas"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
/>
<path d="M8 12h8" />
<path d="M12 8v8" />
</svg>
</div>
<span data-i18n="nav-canvas">Canvas</span>
</a>
<!-- Goals -->
<a
class="app-item"
href="#goals"
data-section="goals"
role="menuitem"
aria-label="Goals & OKRs"
hx-get="/suite/goals/goals.html"
hx-target="#main-content"
hx-push-url="/#goals"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="6" />
<circle cx="12" cy="12" r="2" />
</svg>
</div>
<span data-i18n="nav-goals">Goals</span>
</a>
<!-- Player -->
<a
class="app-item"
href="#player"
data-section="player"
role="menuitem"
aria-label="Media Player"
hx-get="/suite/player/player.html"
hx-target="#main-content"
hx-push-url="/#player"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polygon points="5 3 19 12 5 21 5 3" />
</svg>
</div>
<span data-i18n="nav-player">Player</span>
</a>
<!-- Workspace -->
<a
class="app-item"
href="#workspace"
data-section="workspace"
role="menuitem"
aria-label="Workspace"
hx-get="/suite/workspace/workspace.html"
hx-target="#main-content"
hx-push-url="/#workspace"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="2"
y="7"
width="20"
height="14"
rx="2"
ry="2"
/>
<path
d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"
/>
</svg>
</div>
<span data-i18n="nav-workspace">Workspace</span>
</a>
<!-- Video -->
<a
class="app-item"
href="#video"
data-section="video"
role="menuitem"
aria-label="Video"
hx-get="/suite/video/video.html"
hx-target="#main-content"
hx-push-url="/#video"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="2"
y="2"
width="20"
height="20"
rx="2.18"
ry="2.18"
/>
<line x1="7" y1="2" x2="7" y2="22" />
<line x1="17" y1="2" x2="17" y2="22" />
<line x1="2" y1="12" x2="22" y2="12" />
</svg>
</div>
<span data-i18n="nav-video">Video</span>
</a>
<!-- Learn -->
<a
class="app-item"
href="#learn"
data-section="learn"
role="menuitem"
aria-label="Learn"
hx-get="/suite/learn/learn.html"
hx-target="#main-content"
hx-push-url="/#learn"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"
/>
<path
d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"
/>
</svg>
</div>
<span data-i18n="nav-learn">Learn</span>
</a>
<!-- Settings (last) -->
<a
class="app-item"
href="#settings"
data-section="settings"
role="menuitem"
aria-label="Settings"
hx-get="/suite/settings/index.html"
hx-target="#main-content"
hx-push-url="/#settings"
>
<div class="app-icon" aria-hidden="true">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
</div>
<span data-i18n="nav-settings">Settings</span>
</a>
</div>
</nav>
</div>
</div>
<!-- Center: Omnibox (Search + Chat) -->
<div class="header-center">
<div class="omnibox" id="omnibox">
<div class="omnibox-input-area">
<svg
class="omnibox-icon search-icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
<svg
class="omnibox-icon bot-icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
style="display: none"
>
<rect x="3" y="11" width="18" height="10" rx="2" />
<circle cx="9" cy="16" r="1" />
<circle cx="15" cy="16" r="1" />
<path d="M8 11V7a4 4 0 0 1 8 0v4" />
</svg>
<input
type="text"
class="omnibox-input"
placeholder="Search or ask anything..."
id="omniboxInput"
autocomplete="off"
/>
<div class="omnibox-hints">
<kbd class="omnibox-shortcut">⌘K</kbd>
<button
class="omnibox-mode-toggle"
id="omniboxModeToggle"
title="Toggle chat mode"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
/>
</svg>
</button>
</div>
</div>
<!-- Expandable Results/Chat Panel -->
<div class="omnibox-panel" id="omniboxPanel">
<!-- Search Results Section -->
<div class="omnibox-results" id="omniboxResults">
<div class="omnibox-section">
<div class="omnibox-section-title">
Quick Actions
</div>
<div
class="omnibox-actions"
id="omniboxActions"
>
<button
class="omnibox-action"
data-action="chat"
>
<span class="action-icon">💬</span>
<span class="action-text"
>Chat with Bot</span
>
<kbd></kbd>
</button>
<button
class="omnibox-action"
data-action="navigate"
data-target="chat"
>
<span class="action-icon">📱</span>
<span class="action-text"
>Open Chat App</span
>
</button>
<button
class="omnibox-action"
data-action="navigate"
data-target="tasks"
>
<span class="action-icon"></span>
<span class="action-text"
>Go to Tasks</span
>
</button>
<button
class="omnibox-action"
data-action="navigate"
data-target="mail"
>
<span class="action-icon">✉️</span>
<span class="action-text"
>Go to Mail</span
>
</button>
</div>
</div>
<div
class="omnibox-section"
id="searchResultsSection"
style="display: none"
>
<div class="omnibox-section-title">
Search Results
</div>
<div
class="omnibox-search-results"
id="searchResultsList"
></div>
</div>
</div>
<!-- Chat Section -->
<div
class="omnibox-chat"
id="omniboxChat"
style="display: none"
>
<div
class="omnibox-chat-messages"
id="omniboxChatMessages"
>
<div class="omnibox-chat-welcome">
<div class="welcome-icon">🤖</div>
<div class="welcome-text">
<strong>Hi! I'm your assistant.</strong>
<p>
Ask me anything or tell me what
you'd like to do.
</p>
</div>
</div>
</div>
<div class="omnibox-chat-input-area">
<input
type="text"
class="omnibox-chat-input"
placeholder="Type your message..."
id="omniboxChatInput"
/>
<button
class="omnibox-send-btn"
id="omniboxSendBtn"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="22" y1="2" x2="11" y2="13" />
<polygon
points="22 2 15 22 11 13 2 9 22 2"
/>
</svg>
</button>
</div>
<div class="omnibox-chat-footer">
<button
class="omnibox-back-btn"
id="omniboxBackBtn"
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
Back to search
</button>
<button
class="omnibox-expand-btn"
id="omniboxExpandBtn"
title="Open full chat"
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="15 3 21 3 21 9" />
<polyline points="9 21 3 21 3 15" />
<line x1="21" y1="3" x2="14" y2="10" />
<line x1="3" y1="21" x2="10" y2="14" />
</svg>
Open full chat
</button>
</div>
</div>
</div>
</div>
<div class="omnibox-backdrop" id="omniboxBackdrop"></div>
</div>
<!-- Right: Notifications + User -->
<div class="header-right">
<!-- Notifications Bell -->
<div class="notifications-menu">
<button
class="icon-button"
id="notificationsBtn"
title="Notifications"
aria-expanded="false"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"
/>
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
<span
class="notifications-badge"
id="notificationsBadge"
style="display: none"
>0</span
>
</button>
<div class="notifications-panel" id="notificationsPanel">
<div class="notifications-panel-header">
<span class="notifications-panel-title"
>Notifications</span
>
<button
class="notifications-clear-btn"
id="clearNotificationsBtn"
>
Clear all
</button>
</div>
<div class="notifications-list" id="notificationsList">
<div class="notifications-empty">
<span>🔔</span>
<p>No notifications</p>
</div>
</div>
</div>
</div>
<button
class="icon-button"
id="settingsBtn"
title="Settings"
aria-expanded="false"
aria-haspopup="true"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
</button>
2025-12-03 18:42:22 -03:00
<!-- Settings/Theme Panel -->
<div
class="settings-panel"
id="settingsPanel"
role="menu"
aria-label="Settings"
>
<div class="settings-panel-title">Theme</div>
<div class="theme-grid">
<!-- Core Themes -->
<button
class="theme-option theme-sentient"
data-theme="sentient"
title="Sentient"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🤖 Sentient</span>
</button>
<button
class="theme-option theme-dark"
data-theme="dark"
title="Dark"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🌑 Dark</span>
</button>
<button
class="theme-option theme-light"
data-theme="light"
title="Light"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">☀️ Light</span>
</button>
<button
class="theme-option theme-blue"
data-theme="blue"
title="Ocean"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🌊 Ocean</span>
</button>
<button
class="theme-option theme-purple"
data-theme="purple"
title="Violet"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">💜 Violet</span>
</button>
<button
class="theme-option theme-green"
data-theme="green"
title="Forest"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🌲 Forest</span>
</button>
<button
class="theme-option theme-orange"
data-theme="orange"
title="Sunset"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🌅 Sunset</span>
</button>
<!-- Retro Themes -->
<button
class="theme-option theme-cyberpunk"
data-theme="cyberpunk"
title="Cyberpunk"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🌃 Cyberpunk</span>
</button>
<button
class="theme-option theme-retrowave"
data-theme="retrowave"
title="Retrowave"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🌴 Retrowave</span>
</button>
<button
class="theme-option theme-vapordream"
data-theme="vapordream"
title="Vapor Dream"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name"
>💭 Vapor Dream</span
>
</button>
<button
class="theme-option theme-y2kglow"
data-theme="y2kglow"
title="Y2K"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">✨ Y2K</span>
</button>
<button
class="theme-option theme-arcadeflash"
data-theme="arcadeflash"
title="Arcade"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🕹️ Arcade</span>
</button>
<button
class="theme-option theme-discofever"
data-theme="discofever"
title="Disco"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🪩 Disco</span>
</button>
<button
class="theme-option theme-grungeera"
data-theme="grungeera"
title="Grunge"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🎸 Grunge</span>
</button>
<!-- Classic Themes -->
<button
class="theme-option theme-jazzage"
data-theme="jazzage"
title="Jazz Age"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🎺 Jazz Age</span>
</button>
<button
class="theme-option theme-mellowgold"
data-theme="mellowgold"
title="Mellow Gold"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name"
>🌻 Mellow Gold</span
>
</button>
<button
class="theme-option theme-midcenturymod"
data-theme="midcenturymod"
title="Mid Century"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name"
>🏠 Mid Century</span
>
</button>
<button
class="theme-option theme-polaroidmemories"
data-theme="polaroidmemories"
title="Polaroid"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">📷 Polaroid</span>
</button>
<button
class="theme-option theme-saturdaycartoons"
data-theme="saturdaycartoons"
title="Cartoons"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">📺 Cartoons</span>
</button>
<button
class="theme-option theme-seasidepostcard"
data-theme="seasidepostcard"
title="Seaside"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🏖️ Seaside</span>
</button>
<button
class="theme-option theme-typewriter"
data-theme="typewriter"
title="Typewriter"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">⌨️ Typewriter</span>
</button>
<!-- Tech Themes -->
<button
class="theme-option theme-3dbevel"
data-theme="3dbevel"
title="3D Bevel"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">🔲 3D Bevel</span>
</button>
<button
class="theme-option theme-xeroxui"
data-theme="xeroxui"
title="Xerox UI"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">📠 Xerox UI</span>
</button>
<button
class="theme-option theme-xtreegold"
data-theme="xtreegold"
title="XTree Gold"
>
<div class="theme-option-inner">
<div class="theme-option-header">
<div class="theme-option-dot"></div>
</div>
<div class="theme-option-body">
<div class="theme-option-line"></div>
</div>
</div>
<span class="theme-option-name">📁 XTree Gold</span>
</button>
</div>
<a
class="settings-link"
href="#settings"
hx-get="/suite/settings/index.html"
hx-target="#main-content"
hx-push-url="/#settings"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
2025-12-03 18:42:22 -03:00
>
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
<span>Settings</span>
</a>
</div>
2025-12-03 18:42:22 -03:00
<!-- User avatar with dropdown -->
<div class="user-avatar-container" style="position: relative">
<button
class="user-avatar"
id="userAvatar"
title="User Account"
aria-label="User account menu"
aria-expanded="false"
aria-haspopup="true"
>
<span aria-hidden="true">U</span>
</button>
<!-- User Menu Dropdown -->
<div class="user-menu" id="userMenu" style="display: none">
<div class="user-menu-header">
<div class="user-avatar-large" id="userAvatarLarge">
U
</div>
<div class="user-info">
<div class="user-name" id="userName">User</div>
<div class="user-email" id="userEmail">
user@example.com
</div>
</div>
</div>
<div class="user-menu-divider"></div>
<nav class="user-menu-nav">
<a
href="#settings"
class="user-menu-item"
hx-get="/suite/settings/index.html"
hx-target="#main-content"
hx-push-url="/#settings"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"
></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<span>Profile</span>
</a>
<a
href="#settings"
class="user-menu-item"
hx-get="/suite/settings/index.html"
hx-target="#main-content"
hx-push-url="/#settings"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
></path>
</svg>
<span>Settings</span>
</a>
<a
href="https://docs.pragmatismo.com.br"
target="_blank"
class="user-menu-item"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10"></circle>
<path
d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"
></path>
<line
x1="12"
y1="17"
x2="12.01"
y2="17"
></line>
</svg>
<span>Help & Support</span>
</a>
</nav>
<div class="user-menu-divider"></div>
<a
href="/auth/login.html"
class="user-menu-item login-item"
id="authAction"
style="margin: 8px; color: var(--primary)"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
id="authIcon"
>
<path
d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"
></path>
<polyline points="10 17 15 12 10 7"></polyline>
<line x1="15" y1="12" x2="3" y2="12"></line>
</svg>
<span id="authText">Sign in</span>
</a>
</div>
</div>
2025-12-03 18:42:22 -03:00
</div>
</header>
<!-- Main content area -->
<main id="main-content" role="main">
2025-12-03 18:42:22 -03:00
<!-- Sections will be loaded dynamically -->
</main>
<!-- Core scripts (auth handled by security-bootstrap.js in head) -->
<script src="js/api-client.js"></script>
2025-12-03 18:42:22 -03:00
<script src="js/theme-manager.js"></script>
<script src="js/htmx-app.js"></script>
<script src="js/base.js"></script>
<script src="tasks/tasks.js?v=20260102"></script>
2025-12-03 18:42:22 -03:00
<!-- Application initialization -->
<script>
// Simple initialization for HTMX app
// Note: Chat module is self-contained in chat.html
// ==========================================
// OMNIBOX (Search + Chat) Functionality
// ==========================================
const Omnibox = {
isActive: false,
isChatMode: false,
chatHistory: [],
selectedIndex: 0,
init() {
this.omnibox = document.getElementById("omnibox");
this.input = document.getElementById("omniboxInput");
this.panel = document.getElementById("omniboxPanel");
this.backdrop = document.getElementById("omniboxBackdrop");
this.results = document.getElementById("omniboxResults");
this.chat = document.getElementById("omniboxChat");
this.chatMessages = document.getElementById(
"omniboxChatMessages",
);
this.chatInput =
document.getElementById("omniboxChatInput");
this.modeToggle =
document.getElementById("omniboxModeToggle");
this.bindEvents();
},
bindEvents() {
// Input focus/blur
this.input.addEventListener("focus", () => this.open());
this.backdrop.addEventListener("click", () => this.close());
// Input typing
this.input.addEventListener("input", (e) =>
this.handleInput(e.target.value),
);
// Keyboard navigation
this.input.addEventListener("keydown", (e) =>
this.handleKeydown(e),
);
this.chatInput?.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Mode toggle
this.modeToggle.addEventListener("click", (e) => {
e.stopPropagation();
this.toggleChatMode();
});
// Action buttons
document
.querySelectorAll(".omnibox-action")
.forEach((btn) => {
btn.addEventListener("click", () =>
this.handleAction(btn),
);
});
// Send button
document
.getElementById("omniboxSendBtn")
?.addEventListener("click", () => this.sendMessage());
// Back button
document
.getElementById("omniboxBackBtn")
?.addEventListener("click", () => this.showResults());
// Expand button
document
.getElementById("omniboxExpandBtn")
?.addEventListener("click", () =>
this.expandToFullChat(),
);
// Global shortcut (Cmd+K / Ctrl+K)
document.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
this.input.focus();
this.open();
}
if (e.key === "Escape" && this.isActive) {
this.close();
}
});
},
open() {
this.isActive = true;
this.omnibox.classList.add("active");
this.updateActions();
},
close() {
this.isActive = false;
this.omnibox.classList.remove("active");
this.input.blur();
},
handleInput(value) {
if (value.trim()) {
this.searchContent(value);
} else {
this.showDefaultActions();
}
},
handleKeydown(e) {
const actions = document.querySelectorAll(
'.omnibox-action:not([style*="display: none"])',
);
if (e.key === "ArrowDown") {
e.preventDefault();
this.selectedIndex = Math.min(
this.selectedIndex + 1,
actions.length - 1,
);
this.updateSelection(actions);
} else if (e.key === "ArrowUp") {
e.preventDefault();
this.selectedIndex = Math.max(
this.selectedIndex - 1,
0,
);
this.updateSelection(actions);
} else if (e.key === "Enter") {
e.preventDefault();
const selected = actions[this.selectedIndex];
if (selected) {
this.handleAction(selected);
} else if (this.input.value.trim()) {
// Start chat with the query
this.startChatWithQuery(this.input.value);
}
}
},
updateSelection(actions) {
actions.forEach((a, i) => {
a.classList.toggle(
"selected",
i === this.selectedIndex,
);
});
},
updateActions() {
const currentApp = this.getCurrentApp();
const actionsContainer =
document.getElementById("omniboxActions");
const contextActions = {
chat: [
{
icon: "💬",
text: "New conversation",
action: "chat",
},
{
icon: "📋",
text: "View history",
action: "navigate",
target: "chat",
},
],
mail: [
{
icon: "✉️",
text: "Compose email",
action: "chat",
query: "Help me compose an email",
},
{
icon: "📥",
text: "Check inbox",
action: "navigate",
target: "mail",
},
],
tasks: [
{
icon: "✅",
text: "Create task",
action: "chat",
query: "Create a new task",
},
{
icon: "📋",
text: "Show my tasks",
action: "navigate",
target: "tasks",
},
],
calendar: [
{
icon: "📅",
text: "Schedule event",
action: "chat",
query: "Schedule a meeting",
},
{
icon: "📆",
text: "View calendar",
action: "navigate",
target: "calendar",
},
],
default: [
{
icon: "💬",
text: "Chat with Bot",
action: "chat",
},
{
icon: "🔍",
text: "Search everywhere",
action: "search",
},
],
};
const actions =
contextActions[currentApp] || contextActions.default;
// Add navigation shortcuts
const navActions = [
{
icon: "📱",
text: "Open Chat",
action: "navigate",
target: "chat",
},
{
icon: "✉️",
text: "Open Mail",
action: "navigate",
target: "mail",
},
{
icon: "✓",
text: "Open Tasks",
action: "navigate",
target: "tasks",
},
{
icon: "📅",
text: "Open Calendar",
action: "navigate",
target: "calendar",
},
];
actionsContainer.innerHTML = actions
.concat(navActions)
.map(
(a, i) => `
<button class="omnibox-action ${i === 0 ? "selected" : ""}"
data-action="${a.action}"
data-target="${a.target || ""}"
data-query="${a.query || ""}">
<span class="action-icon">${a.icon}</span>
<span class="action-text">${a.text}</span>
${i === 0 ? "<kbd></kbd>" : ""}
</button>
`,
)
.join("");
// Rebind events
actionsContainer
.querySelectorAll(".omnibox-action")
.forEach((btn) => {
btn.addEventListener("click", () =>
this.handleAction(btn),
);
});
this.selectedIndex = 0;
},
getCurrentApp() {
const hash = window.location.hash.replace("#", "");
return hash || "default";
},
handleAction(btn) {
const action = btn.dataset.action;
const target = btn.dataset.target;
const query = btn.dataset.query;
if (action === "chat") {
if (query) {
this.startChatWithQuery(query);
} else {
this.showChat();
}
} else if (action === "navigate" && target) {
this.navigateTo(target);
} else if (action === "search") {
this.input.focus();
}
},
navigateTo(target) {
this.close();
const link = document.querySelector(
`a[data-section="${target}"]`,
);
if (link) {
link.click();
}
},
toggleChatMode() {
this.isChatMode = !this.isChatMode;
this.omnibox.classList.toggle("chat-mode", this.isChatMode);
if (this.isChatMode) {
this.input.placeholder = "Ask me anything...";
this.showChat();
} else {
this.input.placeholder = "Search or ask anything...";
this.showResults();
}
},
showChat() {
this.results.style.display = "none";
this.chat.style.display = "flex";
this.isChatMode = true;
this.omnibox.classList.add("chat-mode");
this.chatInput?.focus();
},
showResults() {
this.chat.style.display = "none";
this.results.style.display = "block";
this.isChatMode = false;
this.omnibox.classList.remove("chat-mode");
this.input.focus();
},
showDefaultActions() {
document.getElementById(
"searchResultsSection",
).style.display = "none";
this.updateActions();
},
searchContent(query) {
// Show search results section
const resultsSection = document.getElementById(
"searchResultsSection",
);
const resultsList =
document.getElementById("searchResultsList");
resultsSection.style.display = "block";
// Update first action to be "Ask about: query"
const actionsContainer =
document.getElementById("omniboxActions");
const firstAction =
actionsContainer.querySelector(".omnibox-action");
if (firstAction) {
firstAction.dataset.action = "chat";
firstAction.dataset.query = query;
firstAction.querySelector(".action-icon").textContent =
"💬";
firstAction.querySelector(".action-text").textContent =
`Ask: "${query.substring(0, 30)}${query.length > 30 ? "..." : ""}"`;
}
// Simple client-side search of navigation items
const searchResults = this.performSearch(query);
resultsList.innerHTML =
searchResults
.map(
(r) => `
<button class="omnibox-result" data-action="navigate" data-target="${r.target}">
<span class="result-icon">${r.icon}</span>
<div class="result-content">
<span class="result-title">${r.title}</span>
<span class="result-desc">${r.description}</span>
</div>
</button>
`,
)
.join("") ||
'<div class="no-results">No results found. Try asking the bot!</div>';
// Bind click events
resultsList
.querySelectorAll(".omnibox-result")
.forEach((btn) => {
btn.addEventListener("click", () =>
this.navigateTo(btn.dataset.target),
);
});
},
performSearch(query) {
const q = query.toLowerCase();
const items = [
{
target: "chat",
icon: "💬",
title: "Chat",
description: "Chat with the bot",
},
{
target: "mail",
icon: "✉️",
title: "Mail",
description: "Email inbox",
},
{
target: "tasks",
icon: "✓",
title: "Tasks",
description: "Task management",
},
{
target: "calendar",
icon: "📅",
title: "Calendar",
description: "Schedule and events",
},
{
target: "drive",
icon: "📁",
title: "Drive",
description: "File storage",
},
{
target: "paper",
icon: "📄",
title: "Documents",
description: "Document editor",
},
{
target: "sheet",
icon: "📊",
title: "Sheet",
description: "Spreadsheets",
},
{
target: "slides",
icon: "📽️",
title: "Slides",
description: "Presentations",
},
{
target: "editor",
icon: "📝",
title: "Editor",
description: "Code & text editor",
},
{
target: "designer",
icon: "🔷",
title: "Designer",
description: "Visual .bas editor",
},
{
target: "meet",
icon: "📹",
title: "Meet",
description: "Video meetings",
},
{
target: "research",
icon: "🔍",
title: "Research",
description: "Research assistant",
},
{
target: "analytics",
icon: "📊",
title: "Analytics",
description: "Data analytics",
},
{
target: "settings",
icon: "⚙️",
title: "Settings",
description: "App settings",
},
];
return items.filter(
(item) =>
item.title.toLowerCase().includes(q) ||
item.description.toLowerCase().includes(q),
);
},
startChatWithQuery(query) {
this.showChat();
this.input.value = "";
setTimeout(() => {
this.addMessage(query, "user");
this.sendToBot(query);
}, 100);
},
async sendMessage() {
const message = this.chatInput?.value.trim();
if (!message) return;
this.chatInput.value = "";
this.addMessage(message, "user");
await this.sendToBot(message);
},
async sendToBot(message) {
// Show typing indicator
const typingId = this.addTypingIndicator();
try {
// Call the bot API
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message,
context: this.getCurrentApp(),
}),
});
this.removeTypingIndicator(typingId);
if (response.ok) {
const data = await response.json();
this.addMessage(
data.reply ||
data.message ||
"I received your message.",
"bot",
);
// Handle any actions the bot suggests
if (data.action) {
this.handleBotAction(data.action);
}
} else {
this.addMessage(
"Sorry, I encountered an error. Please try again.",
"bot",
);
}
} catch (error) {
this.removeTypingIndicator(typingId);
// Fallback response when API is not available
this.addMessage(
this.getFallbackResponse(message),
"bot",
);
}
},
getFallbackResponse(message) {
const msg = message.toLowerCase();
if (msg.includes("help")) {
return "I can help you navigate the app, search for content, manage tasks, compose emails, and more. What would you like to do?";
} else if (msg.includes("task") || msg.includes("todo")) {
return "Would you like me to open Tasks for you? You can create, view, and manage your tasks there.";
} else if (msg.includes("email") || msg.includes("mail")) {
return "I can help with email! Would you like to compose a new message or check your inbox?";
} else if (
msg.includes("calendar") ||
msg.includes("meeting") ||
msg.includes("schedule")
) {
return "I can help with scheduling. Would you like to create an event or view your calendar?";
}
return (
"I understand you're asking about: \"" +
message +
'". How can I assist you further?'
);
},
addMessage(text, sender) {
const msgDiv = document.createElement("div");
msgDiv.className = `omnibox-message ${sender}`;
msgDiv.innerHTML = `
<div class="message-avatar">${sender === "user" ? "👤" : "🤖"}</div>
<div class="message-content">${this.escapeHtml(text)}</div>
`;
this.chatMessages.appendChild(msgDiv);
this.chatMessages.scrollTop =
this.chatMessages.scrollHeight;
this.chatHistory.push({ role: sender, content: text });
},
addTypingIndicator() {
const id = "typing-" + Date.now();
const typingDiv = document.createElement("div");
typingDiv.id = id;
typingDiv.className = "omnibox-message bot typing";
typingDiv.innerHTML = `
<div class="message-avatar">🤖</div>
<div class="message-content typing-indicator">
<span></span><span></span><span></span>
</div>
`;
this.chatMessages.appendChild(typingDiv);
this.chatMessages.scrollTop =
this.chatMessages.scrollHeight;
return id;
},
removeTypingIndicator(id) {
const el = document.getElementById(id);
if (el) el.remove();
},
handleBotAction(action) {
if (action.navigate) {
setTimeout(
() => this.navigateTo(action.navigate),
1000,
);
}
},
expandToFullChat() {
this.close();
const chatLink = document.querySelector(
'a[data-section="chat"]',
);
if (chatLink) chatLink.click();
},
escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
},
};
// Initialize Omnibox when DOM is ready
2025-12-03 18:42:22 -03:00
document.addEventListener("DOMContentLoaded", () => {
Omnibox.init();
2025-12-03 18:42:22 -03:00
console.log("🚀 Initializing General Bots with HTMX...");
// Hide loading overlay
setTimeout(() => {
const loadingOverlay =
document.getElementById("loadingOverlay");
if (loadingOverlay) {
loadingOverlay.classList.add("hidden");
}
}, 500);
// Simple apps menu handling
const appsBtn = document.getElementById("appsButton");
const appsDropdown = document.getElementById("appsDropdown");
const settingsBtn = document.getElementById("settingsBtn");
const settingsPanel = document.getElementById("settingsPanel");
2025-12-03 18:42:22 -03:00
if (appsBtn && appsDropdown) {
appsBtn.addEventListener("click", (e) => {
e.stopPropagation();
const isOpen = appsDropdown.classList.toggle("show");
appsBtn.setAttribute("aria-expanded", isOpen);
// Close settings panel
if (settingsPanel)
settingsPanel.classList.remove("show");
2025-12-03 18:42:22 -03:00
});
document.addEventListener("click", (e) => {
if (
!appsDropdown.contains(e.target) &&
!appsBtn.contains(e.target)
) {
appsDropdown.classList.remove("show");
appsBtn.setAttribute("aria-expanded", "false");
}
});
}
// Settings panel handling
if (settingsBtn && settingsPanel) {
settingsBtn.addEventListener("click", (e) => {
e.stopPropagation();
const isOpen = settingsPanel.classList.toggle("show");
settingsBtn.setAttribute("aria-expanded", isOpen);
// Close apps dropdown
if (appsDropdown) appsDropdown.classList.remove("show");
});
document.addEventListener("click", (e) => {
if (
!settingsPanel.contains(e.target) &&
!settingsBtn.contains(e.target)
) {
settingsPanel.classList.remove("show");
settingsBtn.setAttribute("aria-expanded", "false");
}
});
}
// Theme selection handling
const themeOptions = document.querySelectorAll(".theme-option");
const savedTheme =
localStorage.getItem("gb-theme") || "sentient";
// Apply saved theme
document.body.setAttribute("data-theme", savedTheme);
document
.querySelector(`.theme-option[data-theme="${savedTheme}"]`)
?.classList.add("active");
themeOptions.forEach((option) => {
option.addEventListener("click", () => {
const theme = option.getAttribute("data-theme");
document.body.setAttribute("data-theme", theme);
localStorage.setItem("gb-theme", theme);
themeOptions.forEach((o) =>
o.classList.remove("active"),
);
option.classList.add("active");
// Update theme-color meta tag
const themeColors = {
dark: "#3b82f6",
light: "#3b82f6",
purple: "#a855f7",
green: "#22c55e",
orange: "#f97316",
sentient: "#d4f505",
};
const metaTheme = document.querySelector(
'meta[name="theme-color"]',
);
if (metaTheme) {
metaTheme.setAttribute(
"content",
themeColors[theme] || "#d4f505",
);
}
});
});
// List of sections that appear in header tabs
const headerTabSections = [
"chat",
"paper",
"sheet",
"slides",
"mail",
"calendar",
"drive",
"tasks",
];
// Update active states for navigation (single selection only)
function updateNavigationActive(section) {
// Remove all active states first
document
.querySelectorAll(".app-tab")
.forEach((t) => t.classList.remove("active"));
document
.querySelectorAll(".app-item")
.forEach((i) => i.classList.remove("active"));
appsBtn.classList.remove("active");
// Check if section is in header tabs
const isInHeaderTabs = headerTabSections.includes(section);
// Activate the matching app-tab if in header
if (isInHeaderTabs) {
const headerTab = document.querySelector(
`.app-tab[data-section="${section}"]`,
);
if (headerTab) {
headerTab.classList.add("active");
}
} else {
// Section is NOT in header tabs, activate apps button
appsBtn.classList.add("active");
}
// Always mark the app-item in dropdown as active
const appItem = document.querySelector(
`.app-item[data-section="${section}"]`,
);
if (appItem) {
appItem.classList.add("active");
}
}
2025-12-03 18:42:22 -03:00
// Handle app item clicks - update active state
document.querySelectorAll(".app-item").forEach((item) => {
item.addEventListener("click", function () {
const section = this.getAttribute("data-section");
updateNavigationActive(section);
2025-12-03 18:42:22 -03:00
appsDropdown.classList.remove("show");
appsBtn.setAttribute("aria-expanded", "false");
});
});
// Handle app tab clicks
document.querySelectorAll(".app-tab").forEach((tab) => {
tab.addEventListener("click", function () {
const section = this.getAttribute("data-section");
updateNavigationActive(section);
});
});
// Track currently loaded section to prevent duplicate loads
let currentLoadedSection = null;
let isLoadingSection = false;
let pendingLoadTimeout = null;
2025-12-03 18:42:22 -03:00
// Handle hash navigation
function handleHashChange(fromHtmxSwap = false) {
2025-12-03 18:42:22 -03:00
const hash = window.location.hash.slice(1) || "chat";
// Skip if already loaded this section or currently loading
if (currentLoadedSection === hash || isLoadingSection) {
return;
}
// If this was triggered by HTMX swap, just update tracking
if (fromHtmxSwap) {
currentLoadedSection = hash;
return;
}
updateNavigationActive(hash);
// Abort any pending load timeout
if (pendingLoadTimeout) {
clearTimeout(pendingLoadTimeout);
pendingLoadTimeout = null;
}
// Abort any in-flight HTMX requests for main-content
const mainContent = document.getElementById("main-content");
if (mainContent) {
try {
htmx.trigger(mainContent, "htmx:abort");
} catch (e) {
// Ignore abort errors
2025-12-03 18:42:22 -03:00
}
}
// Validate target exists before triggering HTMX load
if (!mainContent) {
console.warn(
"handleHashChange: #main-content not found, skipping load",
);
return;
}
// Check if main-content is in the DOM
if (!document.body.contains(mainContent)) {
console.warn(
"handleHashChange: #main-content not in DOM, skipping load",
);
return;
}
// Verify main-content has a valid parent (prevents insertBefore errors)
if (!mainContent.parentNode) {
console.warn(
"handleHashChange: #main-content has no parent, skipping load",
);
return;
}
// Debounce the load to prevent rapid double-requests
pendingLoadTimeout = setTimeout(() => {
// Re-check if section changed during debounce
const currentHash =
window.location.hash.slice(1) || "chat";
if (currentLoadedSection === currentHash) {
return;
}
// Trigger HTMX load
const appItem = document.querySelector(
`.app-item[data-section="${currentHash}"]`,
);
if (appItem) {
const hxGet = appItem.getAttribute("hx-get");
if (hxGet) {
try {
isLoadingSection = true;
currentLoadedSection = currentHash;
htmx.ajax("GET", hxGet, {
target: "#main-content",
swap: "innerHTML",
});
} catch (e) {
console.warn(
"handleHashChange: HTMX ajax error:",
e,
);
currentLoadedSection = null;
} finally {
isLoadingSection = false;
}
}
}
}, 50);
2025-12-03 18:42:22 -03:00
}
// Listen for HTMX swaps to track loaded sections and prevent duplicates
document.body.addEventListener("htmx:afterSwap", (event) => {
if (
event.detail.target &&
event.detail.target.id === "main-content"
) {
const hash = window.location.hash.slice(1) || "chat";
currentLoadedSection = hash;
isLoadingSection = false;
}
});
// Reset tracking on swap errors
document.body.addEventListener("htmx:swapError", (event) => {
if (
event.detail.target &&
event.detail.target.id === "main-content"
) {
isLoadingSection = false;
}
});
2025-12-03 18:42:22 -03:00
// Load initial content based on hash or default to chat
window.addEventListener("hashchange", handleHashChange);
// Initial load
setTimeout(() => {
handleHashChange();
}, 100);
// ==========================================================================
// GBAlerts - Global Notification System
// ==========================================================================
window.GBAlerts = (function () {
const notifications = [];
const badge = document.getElementById("notificationsBadge");
const list = document.getElementById("notificationsList");
const btn = document.getElementById("notificationsBtn");
const panel = document.getElementById("notificationsPanel");
const clearBtn = document.getElementById(
"clearNotificationsBtn",
);
function updateBadge() {
if (badge) {
if (notifications.length > 0) {
badge.textContent =
notifications.length > 99
? "99+"
: notifications.length;
badge.style.display = "flex";
} else {
badge.style.display = "none";
}
}
}
function renderList() {
if (!list) return;
if (notifications.length === 0) {
list.innerHTML =
'<div class="notifications-empty"><span>🔔</span><p>No notifications</p></div>';
} else {
list.innerHTML = notifications
.map(
(n, i) => `
<div class="notification-item notification-${n.type}" data-index="${i}">
<div class="notification-icon">${n.icon || "📢"}</div>
<div class="notification-content">
<div class="notification-title">${n.title}</div>
<div class="notification-message">${n.message || ""}</div>
<div class="notification-time">${n.time}</div>
</div>
${n.action ? `<button class="notification-action" onclick="GBAlerts.handleAction(${i})">${n.actionText || "Open"}</button>` : ""}
<button class="notification-dismiss" onclick="GBAlerts.dismiss(${i})">×</button>
</div>
`,
)
.join("");
}
}
function add(notification) {
notifications.unshift({
...notification,
time: new Date().toLocaleTimeString(),
});
updateBadge();
renderList();
// Auto-open panel when new notification arrives
if (panel) {
panel.classList.add("show");
if (btn) btn.setAttribute("aria-expanded", "true");
}
}
function dismiss(index) {
notifications.splice(index, 1);
updateBadge();
renderList();
}
function clearAll() {
notifications.length = 0;
updateBadge();
renderList();
}
function handleAction(index) {
const n = notifications[index];
if (n && n.action) {
if (typeof n.action === "function") {
n.action();
} else if (typeof n.action === "string") {
window.open(n.action, "_blank");
}
}
dismiss(index);
}
// Toggle panel
if (btn && panel) {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const isOpen = panel.classList.toggle("show");
btn.setAttribute("aria-expanded", isOpen);
});
document.addEventListener("click", (e) => {
if (
!panel.contains(e.target) &&
!btn.contains(e.target)
) {
panel.classList.remove("show");
btn.setAttribute("aria-expanded", "false");
}
});
}
if (clearBtn) {
clearBtn.addEventListener("click", clearAll);
}
// Convenience methods for common notifications
return {
add,
dismiss,
clearAll,
handleAction,
taskCompleted: function (title, url) {
add({
type: "success",
icon: "✅",
title: "Task Completed",
message: title,
action: url,
actionText: "Open App",
});
},
taskFailed: function (title, error) {
add({
type: "error",
icon: "❌",
title: "Task Failed",
message: title + (error ? ": " + error : ""),
});
},
info: function (title, message) {
add({
type: "info",
icon: "",
title: title,
message: message,
});
},
warning: function (title, message) {
add({
type: "warning",
icon: "⚠️",
title: title,
message: message,
});
},
connectionStatus: function (status, message) {
add({
type:
status === "connected"
? "success"
: status === "disconnected"
? "error"
: "warning",
icon:
status === "connected"
? "🟢"
: status === "disconnected"
? "🔴"
: "🟡",
title:
"Connection " +
status.charAt(0).toUpperCase() +
status.slice(1),
message: message || "",
});
},
};
})();
2025-12-03 18:42:22 -03:00
// Keyboard shortcuts
document.addEventListener("keydown", (e) => {
// Alt + number for quick app switching
if (e.altKey && !e.ctrlKey && !e.shiftKey) {
const num = parseInt(e.key);
if (num >= 1 && num <= 9) {
const items =
document.querySelectorAll(".app-item");
if (items[num - 1]) {
items[num - 1].click();
e.preventDefault();
}
}
}
// Alt + A to open apps menu
if (e.altKey && e.key.toLowerCase() === "a") {
appsBtn.click();
e.preventDefault();
}
});
});
// User Profile Loading
(function () {
function updateUserUI(user) {
if (!user) return;
const userName = document.getElementById("userName");
const userEmail = document.getElementById("userEmail");
const userAvatar = document.getElementById("userAvatar");
const userAvatarLarge =
document.getElementById("userAvatarLarge");
const authAction = document.getElementById("authAction");
const authText = document.getElementById("authText");
const authIcon = document.getElementById("authIcon");
const displayName =
user.display_name ||
user.first_name ||
user.username ||
"User";
const email = user.email || "";
const initial = (
displayName.charAt(0) || "U"
).toUpperCase();
console.log("Updating user UI:", displayName, email);
if (userName) userName.textContent = displayName;
if (userEmail) userEmail.textContent = email;
if (userAvatar) {
const avatarSpan = userAvatar.querySelector("span");
if (avatarSpan) avatarSpan.textContent = initial;
}
if (userAvatarLarge) userAvatarLarge.textContent = initial;
if (authAction) {
authAction.href = "#";
authAction.onclick = function (e) {
e.preventDefault();
fetch("/api/auth/logout", {
method: "POST",
}).finally(function () {
localStorage.removeItem("gb-access-token");
localStorage.removeItem("gb-refresh-token");
localStorage.removeItem("gb-user-data");
sessionStorage.removeItem("gb-access-token");
window.location.href = "/auth/login.html";
});
};
authAction.style.color = "var(--error)";
}
if (authText) authText.textContent = "Sign out";
if (authIcon) {
authIcon.innerHTML =
'<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line>';
}
}
function loadUserProfile() {
var token =
localStorage.getItem("gb-access-token") ||
sessionStorage.getItem("gb-access-token");
if (!token) {
console.log("No auth token found");
return;
}
console.log(
"Loading user profile with token:",
token.substring(0, 10) + "...",
);
fetch("/api/auth/me", {
headers: { Authorization: "Bearer " + token },
})
.then(function (res) {
if (!res.ok) throw new Error("Not authenticated");
return res.json();
})
.then(function (user) {
console.log("User profile loaded:", user);
updateUserUI(user);
localStorage.setItem(
"gb-user-data",
JSON.stringify(user),
);
})
.catch(function (err) {
console.log("Failed to load user profile:", err);
});
}
// Try to load cached user first
var cachedUser = localStorage.getItem("gb-user-data");
if (cachedUser) {
try {
var user = JSON.parse(cachedUser);
if (user && user.email) {
updateUserUI(user);
}
} catch (e) {}
}
// Always fetch fresh user data
if (document.readyState === "loading") {
document.addEventListener(
"DOMContentLoaded",
loadUserProfile,
);
} else {
loadUserProfile();
}
})();
2025-12-03 18:42:22 -03:00
</script>
</body>
</html>