Rename tasks-sentient to tasks (make sentient theme default)

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-12-15 16:33:23 -03:00
parent 664211d6db
commit 4451cffdb5
13 changed files with 5665 additions and 1418 deletions

View file

@ -0,0 +1,983 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BotUI Suite - Base Layout Preview</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* =============================================================================
SENTIENT THEME VARIABLES
============================================================================= */
:root {
--sentient-bg-primary: #0a0a0a;
--sentient-bg-secondary: #111111;
--sentient-bg-tertiary: #1a1a1a;
--sentient-bg-card: #141414;
--sentient-bg-hover: #1f1f1f;
--sentient-accent: #c5f82a;
--sentient-accent-dim: rgba(197, 248, 42, 0.15);
--sentient-accent-glow: rgba(197, 248, 42, 0.3);
--sentient-success: #22c55e;
--sentient-warning: #f59e0b;
--sentient-error: #ef4444;
--sentient-info: #3b82f6;
--sentient-text-primary: #ffffff;
--sentient-text-secondary: #a1a1a1;
--sentient-text-muted: #6b6b6b;
--sentient-border: #2a2a2a;
--sentient-border-hover: #3a3a3a;
--sentient-radius-sm: 6px;
--sentient-radius-md: 10px;
--sentient-radius-lg: 16px;
--sentient-font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--sentient-font-family);
background: var(--sentient-bg-primary);
color: var(--sentient-text-primary);
}
/* =============================================================================
LAYOUT
============================================================================= */
.suite-app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.suite-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
background: var(--sentient-bg-primary);
border-bottom: 1px solid var(--sentient-border);
height: 52px;
}
.topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.topbar-tabs {
display: flex;
gap: 2px;
}
.topbar-tab {
padding: 8px 16px;
background: transparent;
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.topbar-tab:first-child {
border-radius: var(--sentient-radius-sm) 0 0 var(--sentient-radius-sm);
}
.topbar-tab:last-child {
border-radius: 0 var(--sentient-radius-sm) var(--sentient-radius-sm) 0;
border-left: none;
}
.topbar-tab:hover {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
}
.topbar-tab.active {
background: var(--sentient-bg-tertiary);
border-color: var(--sentient-accent);
color: var(--sentient-accent);
}
.topbar-app-launcher {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background: var(--sentient-bg-secondary);
border-radius: var(--sentient-radius-lg);
margin-left: 16px;
}
.app-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-bg-tertiary);
border: none;
border-radius: var(--sentient-radius-md);
color: var(--sentient-text-primary);
font-size: 18px;
cursor: pointer;
transition: all 0.2s ease;
}
.app-icon:hover {
background: var(--sentient-bg-hover);
transform: scale(1.05);
}
.app-icon.active {
background: var(--sentient-accent-dim);
color: var(--sentient-accent);
}
.topbar-right {
display: flex;
align-items: center;
gap: 12px;
}
.topbar-btn-primary {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: var(--sentient-accent);
border: none;
border-radius: var(--sentient-radius-sm);
color: #000;
font-family: var(--sentient-font-family);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.topbar-btn-primary:hover {
background: #d4ff4a;
box-shadow: 0 0 20px var(--sentient-accent-glow);
}
.topbar-btn-icon {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.topbar-btn-icon:hover {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
}
.suite-main {
display: flex;
flex: 1;
overflow: hidden;
}
.suite-content-panel {
flex: 1;
display: flex;
flex-direction: column;
overflow: auto;
padding: 20px 24px;
}
/* =============================================================================
AI PANEL
============================================================================= */
.suite-ai-panel {
width: 320px;
display: flex;
flex-direction: column;
background: var(--sentient-bg-secondary);
border-left: 1px solid var(--sentient-border);
}
.ai-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid var(--sentient-border);
}
.ai-panel-title {
display: flex;
align-items: center;
gap: 12px;
}
.ai-avatar {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-accent-dim);
border-radius: var(--sentient-radius-md);
font-size: 18px;
}
.ai-panel-title h3 {
font-size: 14px;
font-weight: 600;
margin: 0;
}
.ai-panel-title .ai-status {
font-size: 11px;
color: var(--sentient-text-muted);
margin: 2px 0 0 0;
}
.ai-panel-close {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-muted);
font-size: 16px;
cursor: pointer;
}
.ai-panel-close:hover {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
}
.ai-panel-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.ai-message {
display: flex;
flex-direction: column;
gap: 8px;
}
.ai-message.user { align-items: flex-end; }
.ai-message.assistant { align-items: flex-start; }
.ai-message-bubble {
max-width: 90%;
padding: 12px 14px;
border-radius: var(--sentient-radius-md);
font-size: 13px;
line-height: 1.5;
}
.ai-message.user .ai-message-bubble {
background: var(--sentient-accent);
color: #000;
border-bottom-right-radius: 4px;
}
.ai-message.assistant .ai-message-bubble {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
border-bottom-left-radius: 4px;
}
.ai-message-action {
display: inline-block;
padding: 8px 12px;
background: var(--sentient-accent);
color: #000;
border-radius: var(--sentient-radius-sm);
font-size: 12px;
font-weight: 500;
cursor: pointer;
}
.ai-typing-indicator {
display: flex;
gap: 4px;
padding: 12px 14px;
background: var(--sentient-bg-tertiary);
border-radius: var(--sentient-radius-md);
width: fit-content;
}
.ai-typing-indicator span {
width: 8px;
height: 8px;
background: var(--sentient-text-muted);
border-radius: 50%;
animation: typing 1.4s infinite;
}
.ai-typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.ai-typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-4px); opacity: 1; }
}
.ai-quick-actions {
padding: 12px 16px;
border-top: 1px solid var(--sentient-border);
}
.quick-actions-label {
display: block;
font-size: 10px;
font-weight: 600;
color: var(--sentient-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 10px;
}
.quick-actions-grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.quick-action-btn {
padding: 6px 10px;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.quick-action-btn:hover {
background: var(--sentient-bg-hover);
border-color: var(--sentient-accent);
color: var(--sentient-accent);
}
.ai-panel-input {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--sentient-border);
}
.ai-input {
flex: 1;
padding: 10px 14px;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-md);
color: var(--sentient-text-primary);
font-family: var(--sentient-font-family);
font-size: 13px;
}
.ai-input::placeholder { color: var(--sentient-text-muted); }
.ai-input:focus { outline: none; border-color: var(--sentient-accent); }
.ai-send-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-accent);
border: none;
border-radius: var(--sentient-radius-md);
color: #000;
font-size: 16px;
cursor: pointer;
}
.ai-send-btn:hover { background: #d4ff4a; }
/* =============================================================================
STAT CARDS
============================================================================= */
.stat-cards {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.stat-card {
flex: 1;
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--sentient-bg-card);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-md);
}
.stat-card-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-bg-tertiary);
border-radius: var(--sentient-radius-sm);
font-size: 18px;
}
.stat-card-content { flex: 1; }
.stat-card-label {
font-size: 11px;
color: var(--sentient-text-muted);
text-transform: uppercase;
letter-spacing: 0.3px;
margin-bottom: 4px;
}
.stat-card-value {
font-size: 20px;
font-weight: 700;
}
.stat-card.highlight {
border-color: var(--sentient-accent);
}
.stat-card.highlight .stat-card-value {
color: var(--sentient-accent);
}
/* =============================================================================
APP HEADER
============================================================================= */
.app-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20px;
}
.app-title-section h1 {
font-size: 22px;
font-weight: 700;
margin: 0 0 4px 0;
}
.app-title-section p {
font-size: 13px;
color: var(--sentient-text-muted);
margin: 0;
}
.app-actions {
display: flex;
gap: 10px;
}
.app-btn-primary {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 16px;
background: var(--sentient-accent);
border: none;
border-radius: var(--sentient-radius-sm);
color: #000;
font-family: var(--sentient-font-family);
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.app-btn-primary:hover { background: #d4ff4a; }
.app-btn-secondary {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-size: 16px;
cursor: pointer;
}
.app-btn-secondary:hover {
background: var(--sentient-bg-hover);
color: var(--sentient-text-primary);
}
/* =============================================================================
DATA TABLE
============================================================================= */
.data-table-container {
flex: 1;
background: var(--sentient-bg-card);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-lg);
overflow: hidden;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table thead {
background: var(--sentient-bg-tertiary);
}
.data-table th {
padding: 12px 16px;
text-align: left;
font-size: 11px;
font-weight: 600;
color: var(--sentient-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid var(--sentient-border);
}
.data-table td {
padding: 14px 16px;
font-size: 13px;
color: var(--sentient-text-primary);
border-bottom: 1px solid var(--sentient-border);
}
.data-table tbody tr:hover {
background: var(--sentient-bg-tertiary);
}
.data-table tbody tr:last-child td {
border-bottom: none;
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
}
.status-badge.active {
background: rgba(34, 197, 94, 0.15);
color: var(--sentient-success);
}
.status-badge.pending {
background: rgba(245, 158, 11, 0.15);
color: var(--sentient-warning);
}
.status-badge.inactive {
background: rgba(239, 68, 68, 0.15);
color: var(--sentient-error);
}
.table-actions {
display: flex;
gap: 6px;
}
.table-action-btn {
padding: 6px 12px;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.table-action-btn:hover {
background: var(--sentient-bg-hover);
color: var(--sentient-text-primary);
}
.table-action-btn.delete {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
color: var(--sentient-error);
}
.table-action-btn.delete:hover {
background: rgba(239, 68, 68, 0.2);
}
.data-table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--sentient-bg-tertiary);
border-top: 1px solid var(--sentient-border);
}
.pagination-info {
font-size: 12px;
color: var(--sentient-text-muted);
}
.pagination-controls {
display: flex;
align-items: center;
gap: 4px;
}
.pagination-btn {
padding: 6px 12px;
background: var(--sentient-bg-card);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 12px;
cursor: pointer;
}
.pagination-btn:hover {
background: var(--sentient-bg-hover);
color: var(--sentient-text-primary);
}
.pagination-btn.active {
background: var(--sentient-accent);
border-color: var(--sentient-accent);
color: #000;
}
/* =============================================================================
SCROLLBAR
============================================================================= */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: var(--sentient-bg-secondary); }
::-webkit-scrollbar-thumb { background: var(--sentient-bg-tertiary); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--sentient-border-hover); }
</style>
</head>
<body>
<div class="suite-app">
<!-- Top Header Bar -->
<header class="suite-topbar">
<div class="topbar-left">
<nav class="topbar-tabs">
<button class="topbar-tab active">Dashboard</button>
<button class="topbar-tab">Analytics</button>
</nav>
<div class="topbar-app-launcher">
<button class="app-icon" data-app="chat" title="Chat">💬</button>
<button class="app-icon active" data-app="files" title="Files">📁</button>
<button class="app-icon" data-app="terminal" title="Terminal">⌨️</button>
<button class="app-icon" data-app="tasks" title="Tasks"></button>
<button class="app-icon" data-app="calendar" title="Calendar">📅</button>
<button class="app-icon" data-app="docs" title="Docs">📄</button>
<button class="app-icon" data-app="settings" title="Settings">⚙️</button>
</div>
</div>
<div class="topbar-right">
<button class="topbar-btn-primary">✨ New Intent</button>
<button class="topbar-btn-icon" title="Settings">⚙️</button>
</div>
</header>
<!-- Main Content Area -->
<main class="suite-main">
<!-- Left: Content Panel -->
<section class="suite-content-panel">
<!-- Stat Cards -->
<div class="stat-cards">
<div class="stat-card highlight">
<div class="stat-card-icon">📊</div>
<div class="stat-card-content">
<div class="stat-card-label">Total Records</div>
<div class="stat-card-value">12,847</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon"></div>
<div class="stat-card-content">
<div class="stat-card-label">Active</div>
<div class="stat-card-value">8,234</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon"></div>
<div class="stat-card-content">
<div class="stat-card-label">Pending</div>
<div class="stat-card-value">2,156</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">📈</div>
<div class="stat-card-content">
<div class="stat-card-label">Growth</div>
<div class="stat-card-value">+24%</div>
</div>
</div>
</div>
<!-- App Header -->
<div class="app-header">
<div class="app-title-section">
<h1>Files Manager</h1>
<p>Manage your documents and media files</p>
</div>
<div class="app-actions">
<button class="app-btn-primary">+ Upload File</button>
<button class="app-btn-secondary">🔍</button>
<button class="app-btn-secondary"></button>
</div>
</div>
<!-- Data Table -->
<div class="data-table-container">
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Size</th>
<th>Modified</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>project-report.pdf</td>
<td>PDF Document</td>
<td>2.4 MB</td>
<td>Dec 13, 2025</td>
<td><span class="status-badge active">Active</span></td>
<td>
<div class="table-actions">
<button class="table-action-btn">View</button>
<button class="table-action-btn">Edit</button>
<button class="table-action-btn delete">Delete</button>
</div>
</td>
</tr>
<tr>
<td>dashboard-mockup.fig</td>
<td>Figma File</td>
<td>8.1 MB</td>
<td>Dec 12, 2025</td>
<td><span class="status-badge pending">Pending</span></td>
<td>
<div class="table-actions">
<button class="table-action-btn">View</button>
<button class="table-action-btn">Edit</button>
<button class="table-action-btn delete">Delete</button>
</div>
</td>
</tr>
<tr>
<td>api-documentation.md</td>
<td>Markdown</td>
<td>156 KB</td>
<td>Dec 11, 2025</td>
<td><span class="status-badge active">Active</span></td>
<td>
<div class="table-actions">
<button class="table-action-btn">View</button>
<button class="table-action-btn">Edit</button>
<button class="table-action-btn delete">Delete</button>
</div>
</td>
</tr>
<tr>
<td>backup-2025-12.zip</td>
<td>Archive</td>
<td>45.2 MB</td>
<td>Dec 10, 2025</td>
<td><span class="status-badge inactive">Archived</span></td>
<td>
<div class="table-actions">
<button class="table-action-btn">View</button>
<button class="table-action-btn">Edit</button>
<button class="table-action-btn delete">Delete</button>
</div>
</td>
</tr>
<tr>
<td>user-analytics.csv</td>
<td>Spreadsheet</td>
<td>890 KB</td>
<td>Dec 9, 2025</td>
<td><span class="status-badge active">Active</span></td>
<td>
<div class="table-actions">
<button class="table-action-btn">View</button>
<button class="table-action-btn">Edit</button>
<button class="table-action-btn delete">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="data-table-footer">
<span class="pagination-info">Showing 1-5 of 847 files</span>
<div class="pagination-controls">
<button class="pagination-btn"></button>
<button class="pagination-btn active">1</button>
<button class="pagination-btn">2</button>
<button class="pagination-btn">3</button>
<button class="pagination-btn">...</button>
<button class="pagination-btn">170</button>
<button class="pagination-btn"></button>
</div>
</div>
</div>
</section>
<!-- Right: AI Assistant Panel -->
<aside class="suite-ai-panel">
<div class="ai-panel-header">
<div class="ai-panel-title">
<span class="ai-avatar">🤖</span>
<div>
<h3>AI Developer</h3>
<p class="ai-status">Desenvolvendo: CRM Deloitte</p>
</div>
</div>
<button class="ai-panel-close"></button>
</div>
<div class="ai-panel-messages" id="ai-messages">
<div class="ai-message assistant">
<div class="ai-message-bubble">Olá! Sou o AI Developer. Como posso ajudar você hoje?</div>
</div>
<div class="ai-message assistant">
<div class="ai-message-bubble">Você pode me pedir para modificar campos, alterar cores, adicionar validações ou qualquer outra mudança no sistema.</div>
</div>
<div class="ai-message user">
<div class="ai-message-bubble">Adicione um campo de telefone no formulário de cadastro</div>
</div>
<div class="ai-message assistant">
<div class="ai-message-bubble">Perfeito! Adicionei o campo de telefone com máscara automática e validação de formato brasileiro.</div>
<span class="ai-message-action">Ver alterações</span>
</div>
</div>
<div class="ai-quick-actions">
<span class="quick-actions-label">AÇÕES RÁPIDAS</span>
<div class="quick-actions-grid">
<button class="quick-action-btn">Adicionar campo</button>
<button class="quick-action-btn">Mudar cor</button>
<button class="quick-action-btn">Adicionar validação</button>
<button class="quick-action-btn">Exportar dados</button>
</div>
</div>
<div class="ai-panel-input">
<input type="text" class="ai-input" placeholder="Digite suas modificações..." id="ai-input">
<button class="ai-send-btn"></button>
</div>
</aside>
</main>
</div>
<script>
// Tab switching
document.querySelectorAll('.topbar-tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.topbar-tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
// App icon switching
document.querySelectorAll('.app-icon').forEach(icon => {
icon.addEventListener('click', function() {
document.querySelectorAll('.app-icon').forEach(i => i.classList.remove('active'));
this.classList.add('active');
});
});
// Quick actions
document.querySelectorAll('.quick-action-btn').forEach(btn => {
btn.addEventListener('click', function() {
const action = this.textContent;
addMessage('user', action);
setTimeout(() => {
addMessage('assistant', `Ação "${action}" executada com sucesso!`);
}, 1000);
});
});
// Send message
document.querySelector('.ai-send-btn').addEventListener('click', sendMessage);
document.getElementById('ai-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
function sendMessage() {
const input = document.getElementById('ai-input');
const message = input.value.trim();
if (!message) return;
addMessage('user', message);
input.value = '';
setTimeout(() => {
addMessage('assistant', `Entendido! Processando: "${message}"`);
}, 1500);
}
function addMessage(type, content) {
const container = document.getElementById('ai-messages');
const div = document.createElement('div');
div.className = `ai-message ${type}`;
div.innerHTML = `<div class="ai-message-bubble">${content}</div>`;
container.appendChild(div);
container.scrollTop = container.scrollHeight;
}
</script>
</body>
</html>

798
ui/suite/base-layout.css Normal file
View file

@ -0,0 +1,798 @@
/* =============================================================================
BOTUI SUITE - BASE LAYOUT CSS
Sentient Theme with AI Assistant Panel
============================================================================= */
/* =============================================================================
LAYOUT STRUCTURE
============================================================================= */
.suite-app {
display: flex;
flex-direction: column;
min-height: 100vh;
background: var(--sentient-bg-primary);
color: var(--sentient-text-primary);
font-family: var(--sentient-font-family);
}
/* =============================================================================
TOP HEADER BAR
============================================================================= */
.suite-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
background: var(--sentient-bg-primary);
border-bottom: 1px solid var(--sentient-border);
height: 52px;
}
.topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
/* Navigation Tabs */
.topbar-tabs {
display: flex;
gap: 2px;
}
.topbar-tab {
padding: 8px 16px;
background: transparent;
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.topbar-tab:first-child {
border-radius: var(--sentient-radius-sm) 0 0 var(--sentient-radius-sm);
}
.topbar-tab:last-child {
border-radius: 0 var(--sentient-radius-sm) var(--sentient-radius-sm) 0;
border-left: none;
}
.topbar-tab:hover {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
}
.topbar-tab.active {
background: var(--sentient-bg-tertiary);
border-color: var(--sentient-accent);
color: var(--sentient-accent);
}
/* App Launcher */
.topbar-app-launcher {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background: var(--sentient-bg-secondary);
border-radius: var(--sentient-radius-lg);
margin-left: 16px;
}
.app-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-bg-tertiary);
border: none;
border-radius: var(--sentient-radius-md);
color: var(--sentient-text-primary);
font-size: 18px;
cursor: pointer;
transition: all 0.2s ease;
}
.app-icon:hover {
background: var(--sentient-bg-hover);
transform: scale(1.05);
}
.app-icon.active {
background: var(--sentient-accent-dim);
color: var(--sentient-accent);
}
/* Right Actions */
.topbar-right {
display: flex;
align-items: center;
gap: 12px;
}
.topbar-btn-primary {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: var(--sentient-accent);
border: none;
border-radius: var(--sentient-radius-sm);
color: #000;
font-family: var(--sentient-font-family);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.topbar-btn-primary:hover {
background: #d4ff4a;
box-shadow: 0 0 20px var(--sentient-accent-glow);
}
.topbar-btn-icon {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.topbar-btn-icon:hover {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
}
/* =============================================================================
MAIN CONTENT AREA
============================================================================= */
.suite-main {
display: flex;
flex: 1;
overflow: hidden;
}
/* Content Panel (Left) */
.suite-content-panel {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 20px 24px;
}
/* =============================================================================
AI ASSISTANT PANEL (Right)
============================================================================= */
.suite-ai-panel {
width: 320px;
display: flex;
flex-direction: column;
background: var(--sentient-bg-secondary);
border-left: 1px solid var(--sentient-border);
}
.ai-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid var(--sentient-border);
}
.ai-panel-title {
display: flex;
align-items: center;
gap: 12px;
}
.ai-avatar {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-accent-dim);
border-radius: var(--sentient-radius-md);
font-size: 18px;
}
.ai-panel-title h3 {
font-size: 14px;
font-weight: 600;
margin: 0;
color: var(--sentient-text-primary);
}
.ai-panel-title .ai-status {
font-size: 11px;
color: var(--sentient-text-muted);
margin: 2px 0 0 0;
}
.ai-panel-close {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-muted);
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.ai-panel-close:hover {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
}
/* AI Messages */
.ai-panel-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.ai-message {
display: flex;
flex-direction: column;
gap: 8px;
}
.ai-message.user {
align-items: flex-end;
}
.ai-message.assistant {
align-items: flex-start;
}
.ai-message-bubble {
max-width: 90%;
padding: 12px 14px;
border-radius: var(--sentient-radius-md);
font-size: 13px;
line-height: 1.5;
}
.ai-message.user .ai-message-bubble {
background: var(--sentient-accent);
color: #000;
border-bottom-right-radius: 4px;
}
.ai-message.assistant .ai-message-bubble {
background: var(--sentient-bg-tertiary);
color: var(--sentient-text-primary);
border-bottom-left-radius: 4px;
}
.ai-message-action {
display: inline-block;
padding: 8px 12px;
background: var(--sentient-accent);
color: #000;
border-radius: var(--sentient-radius-sm);
font-size: 12px;
font-weight: 500;
text-decoration: none;
margin-top: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.ai-message-action:hover {
background: #d4ff4a;
}
.ai-typing-indicator {
display: flex;
gap: 4px;
padding: 12px 14px;
background: var(--sentient-bg-tertiary);
border-radius: var(--sentient-radius-md);
width: fit-content;
}
.ai-typing-indicator span {
width: 8px;
height: 8px;
background: var(--sentient-text-muted);
border-radius: 50%;
animation: typing 1.4s infinite;
}
.ai-typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.ai-typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-4px); opacity: 1; }
}
/* Quick Actions */
.ai-quick-actions {
padding: 12px 16px;
border-top: 1px solid var(--sentient-border);
}
.quick-actions-label {
display: block;
font-size: 10px;
font-weight: 600;
color: var(--sentient-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 10px;
}
.quick-actions-grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.quick-action-btn {
padding: 6px 10px;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.quick-action-btn:hover {
background: var(--sentient-bg-hover);
border-color: var(--sentient-accent);
color: var(--sentient-accent);
}
/* AI Input */
.ai-panel-input {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--sentient-border);
}
.ai-input {
flex: 1;
padding: 10px 14px;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-md);
color: var(--sentient-text-primary);
font-family: var(--sentient-font-family);
font-size: 13px;
transition: all 0.2s ease;
}
.ai-input::placeholder {
color: var(--sentient-text-muted);
}
.ai-input:focus {
outline: none;
border-color: var(--sentient-accent);
}
.ai-send-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-accent);
border: none;
border-radius: var(--sentient-radius-md);
color: #000;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.ai-send-btn:hover {
background: #d4ff4a;
}
/* =============================================================================
STAT CARDS
============================================================================= */
.stat-cards {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.stat-card {
flex: 1;
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--sentient-bg-card);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-md);
}
.stat-card-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-bg-tertiary);
border-radius: var(--sentient-radius-sm);
font-size: 18px;
}
.stat-card-content {
flex: 1;
}
.stat-card-label {
font-size: 11px;
color: var(--sentient-text-muted);
text-transform: uppercase;
letter-spacing: 0.3px;
margin-bottom: 4px;
}
.stat-card-value {
font-size: 20px;
font-weight: 700;
color: var(--sentient-text-primary);
}
.stat-card.highlight {
border-color: var(--sentient-accent);
}
.stat-card.highlight .stat-card-value {
color: var(--sentient-accent);
}
/* =============================================================================
APP HEADER (Title + Actions)
============================================================================= */
.app-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20px;
}
.app-title-section h1 {
font-size: 22px;
font-weight: 700;
margin: 0 0 4px 0;
}
.app-title-section p {
font-size: 13px;
color: var(--sentient-text-muted);
margin: 0;
}
.app-actions {
display: flex;
gap: 10px;
}
.app-btn-primary {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 16px;
background: var(--sentient-accent);
border: none;
border-radius: var(--sentient-radius-sm);
color: #000;
font-family: var(--sentient-font-family);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.app-btn-primary:hover {
background: #d4ff4a;
}
.app-btn-secondary {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.app-btn-secondary:hover {
background: var(--sentient-bg-hover);
color: var(--sentient-text-primary);
}
/* =============================================================================
DATA TABLE
============================================================================= */
.data-table-container {
flex: 1;
background: var(--sentient-bg-card);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-lg);
overflow: hidden;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table thead {
background: var(--sentient-bg-tertiary);
}
.data-table th {
padding: 12px 16px;
text-align: left;
font-size: 11px;
font-weight: 600;
color: var(--sentient-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid var(--sentient-border);
}
.data-table td {
padding: 14px 16px;
font-size: 13px;
color: var(--sentient-text-primary);
border-bottom: 1px solid var(--sentient-border);
}
.data-table tbody tr:hover {
background: var(--sentient-bg-tertiary);
}
.data-table tbody tr:last-child td {
border-bottom: none;
}
/* Status Badge */
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
}
.status-badge.active {
background: rgba(34, 197, 94, 0.15);
color: var(--sentient-success);
}
.status-badge.pending {
background: rgba(245, 158, 11, 0.15);
color: var(--sentient-warning);
}
.status-badge.inactive {
background: rgba(239, 68, 68, 0.15);
color: var(--sentient-error);
}
/* Action Buttons */
.table-actions {
display: flex;
gap: 6px;
}
.table-action-btn {
padding: 6px 12px;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.table-action-btn:hover {
background: var(--sentient-bg-hover);
color: var(--sentient-text-primary);
}
.table-action-btn.delete {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
color: var(--sentient-error);
}
.table-action-btn.delete:hover {
background: rgba(239, 68, 68, 0.2);
}
/* Pagination */
.data-table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--sentient-bg-tertiary);
border-top: 1px solid var(--sentient-border);
}
.pagination-info {
font-size: 12px;
color: var(--sentient-text-muted);
}
.pagination-controls {
display: flex;
align-items: center;
gap: 4px;
}
.pagination-btn {
padding: 6px 12px;
background: var(--sentient-bg-card);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-sm);
color: var(--sentient-text-secondary);
font-family: var(--sentient-font-family);
font-size: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.pagination-btn:hover {
background: var(--sentient-bg-hover);
color: var(--sentient-text-primary);
}
.pagination-btn.active {
background: var(--sentient-accent);
border-color: var(--sentient-accent);
color: #000;
}
/* =============================================================================
SCROLLBAR
============================================================================= */
.suite-app ::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.suite-app ::-webkit-scrollbar-track {
background: var(--sentient-bg-secondary);
}
.suite-app ::-webkit-scrollbar-thumb {
background: var(--sentient-bg-tertiary);
border-radius: 3px;
}
.suite-app ::-webkit-scrollbar-thumb:hover {
background: var(--sentient-border-hover);
}
/* =============================================================================
AI PANEL COLLAPSIBLE STATE
============================================================================= */
.suite-ai-panel.collapsed {
width: 0;
overflow: hidden;
border-left: none;
}
.suite-ai-panel.collapsed * {
opacity: 0;
pointer-events: none;
}
.ai-toggle {
transition: all 0.2s ease;
}
.ai-toggle.active {
background: var(--sentient-accent-dim);
border-color: var(--sentient-accent);
color: var(--sentient-accent);
}
/* =============================================================================
RESPONSIVE
============================================================================= */
@media (max-width: 1200px) {
.suite-ai-panel {
width: 280px;
}
}
@media (max-width: 1024px) {
.suite-ai-panel {
position: fixed;
right: 0;
top: 52px;
bottom: 0;
width: 320px;
transform: translateX(0);
transition: transform 0.3s ease;
z-index: 100;
}
.suite-ai-panel.collapsed {
transform: translateX(100%);
width: 320px;
}
.topbar-app-launcher {
display: none;
}
}
@media (max-width: 768px) {
.stat-cards {
flex-direction: column;
}
.app-header {
flex-direction: column;
gap: 16px;
}
.topbar-tabs {
display: none;
}
}

98
ui/suite/base-layout.html Normal file
View file

@ -0,0 +1,98 @@
<!-- =============================================================================
BOTUI SUITE - BASE LAYOUT
Sentient Theme with AI Assistant Panel
============================================================================= -->
<link rel="stylesheet" href="/themes/sentient/sentient.css">
<link rel="stylesheet" href="/suite/base-layout.css">
<div class="suite-app sentient-theme">
<!-- Top Header Bar -->
<header class="suite-topbar">
<!-- Left: Navigation Tabs -->
<div class="topbar-left">
<nav class="topbar-tabs">
<button class="topbar-tab active">Dashboard</button>
<button class="topbar-tab">Analytics</button>
</nav>
<!-- App Launcher -->
<div class="topbar-app-launcher">
<button class="app-icon" data-app="chat" title="Chat">
<span>💬</span>
</button>
<button class="app-icon" data-app="files" title="Files">
<span>📁</span>
</button>
<button class="app-icon" data-app="terminal" title="Terminal">
<span>⌨️</span>
</button>
<button class="app-icon" data-app="tasks" title="Tasks">
<span></span>
</button>
<button class="app-icon" data-app="calendar" title="Calendar">
<span>📅</span>
</button>
<button class="app-icon" data-app="docs" title="Docs">
<span>📄</span>
</button>
<button class="app-icon" data-app="settings" title="Settings">
<span>⚙️</span>
</button>
</div>
</div>
<!-- Right: Actions -->
<div class="topbar-right">
<button class="topbar-btn-primary">
<span></span> New Intent
</button>
<button class="topbar-btn-icon" title="Settings">⚙️</button>
</div>
</header>
<!-- Main Content Area -->
<main class="suite-main">
<!-- Left: Content Panel -->
<section class="suite-content-panel" id="suite-content">
<!-- App content goes here -->
</section>
<!-- Right: AI Assistant Panel -->
<aside class="suite-ai-panel" id="ai-panel">
<div class="ai-panel-header">
<div class="ai-panel-title">
<span class="ai-avatar">🤖</span>
<div>
<h3>AI Developer</h3>
<p class="ai-status">Desenvolvendo: CRM Deloitte</p>
</div>
</div>
<button class="ai-panel-close" onclick="toggleAIPanel()"></button>
</div>
<div class="ai-panel-messages" id="ai-messages">
<!-- Messages will be inserted here -->
</div>
<div class="ai-quick-actions">
<span class="quick-actions-label">AÇÕES RÁPIDAS</span>
<div class="quick-actions-grid">
<button class="quick-action-btn">Adicionar campo</button>
<button class="quick-action-btn">Mudar cor</button>
<button class="quick-action-btn">Adicionar validação</button>
<button class="quick-action-btn">Exportar dados</button>
</div>
</div>
<div class="ai-panel-input">
<input type="text" class="ai-input" placeholder="Digite suas modificações..." id="ai-input">
<button class="ai-send-btn" onclick="sendAIMessage()">
<span></span>
</button>
</div>
</aside>
</main>
</div>
<script src="/suite/base-layout.js"></script>

192
ui/suite/base-layout.js Normal file
View file

@ -0,0 +1,192 @@
/* =============================================================================
BOTUI SUITE - BASE LAYOUT JAVASCRIPT
Sentient Theme with AI Assistant Panel
============================================================================= */
(function() {
'use strict';
// =============================================================================
// STATE
// =============================================================================
const state = {
aiPanelOpen: true,
currentApp: 'dashboard',
messages: []
};
// =============================================================================
// AI PANEL
// =============================================================================
window.toggleAIPanel = function() {
const panel = document.getElementById('ai-panel');
if (panel) {
panel.classList.toggle('open');
state.aiPanelOpen = panel.classList.contains('open');
}
};
window.sendAIMessage = function() {
const input = document.getElementById('ai-input');
if (!input || !input.value.trim()) return;
const message = input.value.trim();
input.value = '';
addMessage('user', message);
showTypingIndicator();
// Simulate AI response
setTimeout(() => {
hideTypingIndicator();
addMessage('assistant', `Entendido! Vou processar sua solicitação: "${message}"`);
}, 1500);
};
function addMessage(type, content, action) {
const container = document.getElementById('ai-messages');
if (!container) return;
const messageEl = document.createElement('div');
messageEl.className = `ai-message ${type}`;
let html = `<div class="ai-message-bubble">${content}</div>`;
if (action) {
html += `<span class="ai-message-action">${action}</span>`;
}
messageEl.innerHTML = html;
container.appendChild(messageEl);
container.scrollTop = container.scrollHeight;
state.messages.push({ type, content, action });
}
function showTypingIndicator() {
const container = document.getElementById('ai-messages');
if (!container) return;
const indicator = document.createElement('div');
indicator.className = 'ai-message assistant';
indicator.id = 'typing-indicator';
indicator.innerHTML = `
<div class="ai-typing-indicator">
<span></span><span></span><span></span>
</div>
`;
container.appendChild(indicator);
container.scrollTop = container.scrollHeight;
}
function hideTypingIndicator() {
const indicator = document.getElementById('typing-indicator');
if (indicator) indicator.remove();
}
// =============================================================================
// APP LAUNCHER
// =============================================================================
function initAppLauncher() {
document.querySelectorAll('.app-icon').forEach(icon => {
icon.addEventListener('click', function() {
const app = this.dataset.app;
switchApp(app);
});
});
}
function switchApp(appName) {
document.querySelectorAll('.app-icon').forEach(icon => {
icon.classList.toggle('active', icon.dataset.app === appName);
});
state.currentApp = appName;
// Dispatch custom event for app switching
document.dispatchEvent(new CustomEvent('app-switch', { detail: { app: appName } }));
}
// =============================================================================
// NAVIGATION TABS
// =============================================================================
function initTabs() {
document.querySelectorAll('.topbar-tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.topbar-tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
}
// =============================================================================
// QUICK ACTIONS
// =============================================================================
function initQuickActions() {
document.querySelectorAll('.quick-action-btn').forEach(btn => {
btn.addEventListener('click', function() {
const action = this.textContent;
addMessage('user', action);
showTypingIndicator();
setTimeout(() => {
hideTypingIndicator();
addMessage('assistant', `Ação "${action}" executada com sucesso!`, 'Ver alterações');
}, 1000);
});
});
}
// =============================================================================
// KEYBOARD SHORTCUTS
// =============================================================================
function initKeyboardShortcuts() {
document.addEventListener('keydown', function(e) {
// Enter to send message in AI input
if (e.key === 'Enter' && document.activeElement.id === 'ai-input') {
e.preventDefault();
sendAIMessage();
}
// Escape to close AI panel on mobile
if (e.key === 'Escape' && window.innerWidth <= 1024) {
const panel = document.getElementById('ai-panel');
if (panel && panel.classList.contains('open')) {
toggleAIPanel();
}
}
});
}
// =============================================================================
// INITIAL MESSAGES
// =============================================================================
function loadInitialMessages() {
addMessage('assistant', 'Olá! Sou o AI Developer. Como posso ajudar você hoje?');
addMessage('assistant', 'Você pode me pedir para modificar campos, alterar cores, adicionar validações ou qualquer outra mudança no sistema.');
}
// =============================================================================
// INITIALIZE
// =============================================================================
function init() {
initAppLauncher();
initTabs();
initQuickActions();
initKeyboardShortcuts();
loadInitialMessages();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

View file

@ -12,6 +12,8 @@
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" href="/css/base.css" /> <link rel="stylesheet" href="/css/base.css" />
<link rel="stylesheet" href="/css/app.css" /> <link rel="stylesheet" href="/css/app.css" />
<link rel="stylesheet" href="/themes/sentient/sentient.css" />
<link rel="stylesheet" href="/css/ai-panel.css" />
</head> </head>
<body> <body>
<!-- Skip navigation link for accessibility --> <!-- Skip navigation link for accessibility -->
@ -728,6 +730,44 @@
<div id="main-content" aria-busy="false" tabindex="-1"> <div id="main-content" aria-busy="false" tabindex="-1">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<!-- AI Assistant Panel (visible on all screens except Chat) -->
<aside class="ai-assistant-panel" id="ai-panel">
<div class="ai-panel-header">
<div class="ai-panel-title">
<span class="ai-avatar">🤖</span>
<div>
<h3>AI Assistant</h3>
<p class="ai-status">Ready to help</p>
</div>
</div>
<button class="ai-panel-toggle" onclick="toggleAIPanel()" aria-label="Close AI Panel"></button>
</div>
<div class="ai-panel-messages" id="ai-messages">
<div class="ai-message assistant">
<div class="ai-message-bubble">Olá! Sou seu assistente AI. Posso ajudar com qualquer tarefa nesta tela.</div>
</div>
</div>
<div class="ai-quick-actions">
<span class="quick-actions-label">AÇÕES RÁPIDAS</span>
<div class="quick-actions-grid" id="ai-quick-actions">
<!-- Quick actions loaded dynamically based on current app -->
</div>
</div>
<div class="ai-panel-input">
<input type="text" class="ai-input" placeholder="Como posso ajudar?" id="ai-input"
onkeypress="if(event.key==='Enter')sendAIMessage()">
<button class="ai-send-btn" onclick="sendAIMessage()" aria-label="Send message"></button>
</div>
</aside>
<!-- AI Panel Toggle Button (when panel is collapsed) -->
<button class="ai-panel-fab" id="ai-fab" onclick="toggleAIPanel()" aria-label="Open AI Assistant">
🤖
</button>
</main> </main>
<div class="notifications-container" id="notifications"></div> <div class="notifications-container" id="notifications"></div>
@ -1139,6 +1179,231 @@
5000, 5000,
); );
}); });
// =================================================================
// AI ASSISTANT PANEL
// =================================================================
// Quick actions per app
const aiQuickActions = {
drive: [
{ label: 'Upload file', action: 'upload_file' },
{ label: 'Create folder', action: 'create_folder' },
{ label: 'Search files', action: 'search_files' },
{ label: 'Share', action: 'share_item' }
],
tasks: [
{ label: 'New task', action: 'create_task' },
{ label: 'Due today', action: 'show_due_today' },
{ label: 'Summary', action: 'tasks_summary' },
{ label: 'Priorities', action: 'show_priorities' }
],
mail: [
{ label: 'Compose', action: 'compose_email' },
{ label: 'Unread', action: 'show_unread' },
{ label: 'Search', action: 'search_mail' },
{ label: 'Summary', action: 'mail_summary' }
],
calendar: [
{ label: 'New event', action: 'create_event' },
{ label: 'Today', action: 'show_today' },
{ label: 'This week', action: 'show_week' },
{ label: 'Find time', action: 'find_free_time' }
],
meet: [
{ label: 'Start call', action: 'start_meeting' },
{ label: 'Schedule', action: 'schedule_meeting' },
{ label: 'Join', action: 'join_meeting' }
],
paper: [
{ label: 'New doc', action: 'create_document' },
{ label: 'Templates', action: 'show_templates' },
{ label: 'Recent', action: 'show_recent' }
],
research: [
{ label: 'New search', action: 'new_research' },
{ label: 'Sources', action: 'show_sources' },
{ label: 'Citations', action: 'generate_citations' }
],
sources: [
{ label: 'Add source', action: 'add_source' },
{ label: 'Import', action: 'import_sources' },
{ label: 'Categories', action: 'show_categories' }
],
analytics: [
{ label: 'Dashboard', action: 'show_dashboard' },
{ label: 'Reports', action: 'show_reports' },
{ label: 'Export', action: 'export_data' }
],
admin: [
{ label: 'Users', action: 'manage_users' },
{ label: 'Settings', action: 'show_settings' },
{ label: 'Logs', action: 'show_logs' }
],
monitoring: [
{ label: 'Status', action: 'show_status' },
{ label: 'Alerts', action: 'show_alerts' },
{ label: 'Metrics', action: 'show_metrics' }
],
default: [
{ label: 'Help', action: 'show_help' },
{ label: 'Shortcuts', action: 'show_shortcuts' },
{ label: 'Settings', action: 'open_settings' }
]
};
// Get current app from URL or hash
function getCurrentApp() {
const hash = window.location.hash.replace('#', '');
const path = window.location.pathname;
if (hash) return hash;
const match = path.match(/\/([a-z]+)\//);
return match ? match[1] : 'default';
}
// Update body data-app attribute
function updateCurrentApp() {
const app = getCurrentApp();
document.body.setAttribute('data-app', app);
loadQuickActions(app);
}
// Load quick actions for current app
function loadQuickActions(app) {
const container = document.getElementById('ai-quick-actions');
if (!container) return;
const actions = aiQuickActions[app] || aiQuickActions.default;
container.innerHTML = actions.map(a =>
`<button class="quick-action-btn" onclick="handleQuickAction('${a.action}')">${a.label}</button>`
).join('');
}
// Handle quick action click
function handleQuickAction(action) {
const input = document.getElementById('ai-input');
const actionMessages = {
upload_file: 'Help me upload a file',
create_folder: 'Create a new folder',
search_files: 'Search for files',
share_item: 'Help me share this item',
create_task: 'Create a new task',
show_due_today: 'Show tasks due today',
tasks_summary: 'Give me a summary of my tasks',
show_priorities: 'Show my priority tasks',
compose_email: 'Help me compose an email',
show_unread: 'Show unread emails',
search_mail: 'Search my emails',
mail_summary: 'Summarize my inbox',
create_event: 'Create a calendar event',
show_today: 'Show today\'s schedule',
show_week: 'Show this week\'s events',
find_free_time: 'Find free time slots',
start_meeting: 'Start a new meeting',
schedule_meeting: 'Schedule a meeting',
join_meeting: 'Join a meeting',
create_document: 'Create a new document',
show_templates: 'Show document templates',
show_recent: 'Show recent documents',
new_research: 'Start new research',
show_sources: 'Show my sources',
generate_citations: 'Generate citations',
add_source: 'Add a new source',
import_sources: 'Import sources',
show_categories: 'Show categories',
show_dashboard: 'Show analytics dashboard',
show_reports: 'Show reports',
export_data: 'Export analytics data',
manage_users: 'Manage users',
show_settings: 'Show admin settings',
show_logs: 'Show system logs',
show_status: 'Show system status',
show_alerts: 'Show active alerts',
show_metrics: 'Show performance metrics',
show_help: 'Help me get started',
show_shortcuts: 'Show keyboard shortcuts',
open_settings: 'Open settings'
};
if (input && actionMessages[action]) {
input.value = actionMessages[action];
sendAIMessage();
}
}
// Toggle AI panel
function toggleAIPanel() {
document.body.classList.toggle('ai-panel-collapsed');
localStorage.setItem('ai-panel-collapsed', document.body.classList.contains('ai-panel-collapsed'));
}
// Send AI message
function sendAIMessage() {
const input = document.getElementById('ai-input');
const messagesContainer = document.getElementById('ai-messages');
const message = input?.value?.trim();
if (!message || !messagesContainer) return;
// Add user message
const userMsg = document.createElement('div');
userMsg.className = 'ai-message user';
userMsg.innerHTML = `<div class="ai-message-bubble">${escapeHtml(message)}</div>`;
messagesContainer.appendChild(userMsg);
// Clear input
input.value = '';
// Scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Show typing indicator
const typing = document.createElement('div');
typing.className = 'ai-message assistant';
typing.id = 'ai-typing';
typing.innerHTML = `<div class="ai-typing-indicator"><span></span><span></span><span></span></div>`;
messagesContainer.appendChild(typing);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Simulate AI response (replace with actual API call)
setTimeout(() => {
typing.remove();
const aiMsg = document.createElement('div');
aiMsg.className = 'ai-message assistant';
aiMsg.innerHTML = `<div class="ai-message-bubble">Entendi! Estou processando sua solicitação: "${escapeHtml(message)}". Como posso ajudar mais?</div>`;
messagesContainer.appendChild(aiMsg);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}, 1500);
}
// Escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Restore AI panel state on load
function initAIPanel() {
const collapsed = localStorage.getItem('ai-panel-collapsed') === 'true';
if (collapsed) {
document.body.classList.add('ai-panel-collapsed');
}
updateCurrentApp();
}
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', initAIPanel);
// Update app on navigation
document.body.addEventListener('htmx:afterSwap', function(e) {
if (e.detail.target.id === 'main-content') {
updateCurrentApp();
}
});
// Also track hash changes
window.addEventListener('hashchange', updateCurrentApp);
</script> </script>
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}

392
ui/suite/css/ai-panel.css Normal file
View file

@ -0,0 +1,392 @@
/* =============================================================================
AI ASSISTANT PANEL - Global Styles
Collapsible panel that appears on all screens except Chat
============================================================================= */
/* Main container adjustment */
.app-main {
display: flex;
position: relative;
}
#main-content {
flex: 1;
overflow: auto;
transition: margin-right 0.3s ease;
}
/* When AI panel is open */
body:not(.ai-panel-collapsed) #main-content {
margin-right: 320px;
}
/* =============================================================================
AI PANEL
============================================================================= */
.ai-assistant-panel {
position: fixed;
right: 0;
top: 56px; /* Below header */
bottom: 0;
width: 320px;
display: flex;
flex-direction: column;
background: var(--surface, #111111);
border-left: 1px solid var(--border, #2a2a2a);
z-index: 50;
transition: transform 0.3s ease;
}
/* Collapsed state */
body.ai-panel-collapsed .ai-assistant-panel {
transform: translateX(100%);
}
body.ai-panel-collapsed #main-content {
margin-right: 0;
}
/* Hide on Chat app */
body[data-app="chat"] .ai-assistant-panel,
body[data-app="chat"] .ai-panel-fab {
display: none !important;
}
/* =============================================================================
AI PANEL HEADER
============================================================================= */
.ai-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid var(--border, #2a2a2a);
background: var(--surface, #111111);
}
.ai-panel-title {
display: flex;
align-items: center;
gap: 12px;
}
.ai-panel-title .ai-avatar {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary-light, rgba(59, 130, 246, 0.15));
border-radius: 10px;
font-size: 18px;
}
.ai-panel-title h3 {
font-size: 14px;
font-weight: 600;
margin: 0;
color: var(--text, #ffffff);
}
.ai-panel-title .ai-status {
font-size: 11px;
color: var(--text-secondary, #6b6b6b);
margin: 2px 0 0 0;
}
.ai-panel-toggle {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-secondary, #6b6b6b);
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.ai-panel-toggle:hover {
background: var(--surface-hover, #1a1a1a);
color: var(--text, #ffffff);
}
/* =============================================================================
AI MESSAGES
============================================================================= */
.ai-panel-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.ai-message {
display: flex;
flex-direction: column;
gap: 8px;
}
.ai-message.user {
align-items: flex-end;
}
.ai-message.assistant {
align-items: flex-start;
}
.ai-message-bubble {
max-width: 90%;
padding: 12px 14px;
border-radius: 10px;
font-size: 13px;
line-height: 1.5;
}
.ai-message.user .ai-message-bubble {
background: var(--primary, #3b82f6);
color: #ffffff;
border-bottom-right-radius: 4px;
}
.ai-message.assistant .ai-message-bubble {
background: var(--surface-hover, #1a1a1a);
color: var(--text, #ffffff);
border-bottom-left-radius: 4px;
}
.ai-message-action {
display: inline-block;
padding: 8px 12px;
background: var(--primary, #3b82f6);
color: #ffffff;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
}
.ai-message-action:hover {
opacity: 0.9;
}
.ai-typing-indicator {
display: flex;
gap: 4px;
padding: 12px 14px;
background: var(--surface-hover, #1a1a1a);
border-radius: 10px;
width: fit-content;
}
.ai-typing-indicator span {
width: 8px;
height: 8px;
background: var(--text-secondary, #6b6b6b);
border-radius: 50%;
animation: ai-typing 1.4s infinite;
}
.ai-typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.ai-typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes ai-typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-4px); opacity: 1; }
}
/* =============================================================================
QUICK ACTIONS
============================================================================= */
.ai-quick-actions {
padding: 12px 16px;
border-top: 1px solid var(--border, #2a2a2a);
}
.ai-quick-actions .quick-actions-label {
display: block;
font-size: 10px;
font-weight: 600;
color: var(--text-secondary, #6b6b6b);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 10px;
}
.ai-quick-actions .quick-actions-grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.ai-quick-actions .quick-action-btn {
padding: 6px 10px;
background: var(--surface-hover, #1a1a1a);
border: 1px solid var(--border, #2a2a2a);
border-radius: 6px;
color: var(--text-secondary, #a1a1a1);
font-family: inherit;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.ai-quick-actions .quick-action-btn:hover {
background: var(--surface, #111111);
border-color: var(--primary, #3b82f6);
color: var(--primary, #3b82f6);
}
/* =============================================================================
AI INPUT
============================================================================= */
.ai-panel-input {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--border, #2a2a2a);
background: var(--surface, #111111);
}
.ai-panel-input .ai-input {
flex: 1;
padding: 10px 14px;
background: var(--surface-hover, #1a1a1a);
border: 1px solid var(--border, #2a2a2a);
border-radius: 10px;
color: var(--text, #ffffff);
font-family: inherit;
font-size: 13px;
transition: all 0.2s ease;
}
.ai-panel-input .ai-input::placeholder {
color: var(--text-secondary, #6b6b6b);
}
.ai-panel-input .ai-input:focus {
outline: none;
border-color: var(--primary, #3b82f6);
}
.ai-panel-input .ai-send-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary, #3b82f6);
border: none;
border-radius: 10px;
color: #ffffff;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.ai-panel-input .ai-send-btn:hover {
opacity: 0.9;
}
/* =============================================================================
FAB (Floating Action Button) - Toggle when collapsed
============================================================================= */
.ai-panel-fab {
position: fixed;
right: 20px;
bottom: 20px;
width: 56px;
height: 56px;
display: none;
align-items: center;
justify-content: center;
background: var(--primary, #3b82f6);
border: none;
border-radius: 50%;
color: #ffffff;
font-size: 24px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 100;
transition: all 0.2s ease;
}
.ai-panel-fab:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}
body.ai-panel-collapsed .ai-panel-fab {
display: flex;
}
/* =============================================================================
SCROLLBAR
============================================================================= */
.ai-panel-messages::-webkit-scrollbar {
width: 6px;
}
.ai-panel-messages::-webkit-scrollbar-track {
background: var(--surface, #111111);
}
.ai-panel-messages::-webkit-scrollbar-thumb {
background: var(--border, #2a2a2a);
border-radius: 3px;
}
.ai-panel-messages::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary, #6b6b6b);
}
/* =============================================================================
RESPONSIVE
============================================================================= */
@media (max-width: 1024px) {
.ai-assistant-panel {
width: 100%;
max-width: 320px;
}
body:not(.ai-panel-collapsed) #main-content {
margin-right: 0;
}
}
@media (max-width: 768px) {
.ai-assistant-panel {
width: 100%;
max-width: none;
top: 56px;
}
.ai-panel-fab {
right: 16px;
bottom: 16px;
width: 48px;
height: 48px;
font-size: 20px;
}
}

View file

@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Drive - BotUI Suite</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/themes/sentient/sentient.css">
<link rel="stylesheet" href="/suite/base-layout.css">
<link rel="stylesheet" href="/suite/drive/drive-sentient.css">
</head>
<body>
<div class="suite-app sentient-theme">
<!-- Top Header Bar -->
<header class="suite-topbar">
<div class="topbar-left">
<nav class="topbar-tabs">
<button class="topbar-tab active">My Files</button>
<button class="topbar-tab">Shared</button>
</nav>
<div class="topbar-app-launcher">
<button class="app-icon" data-app="chat" title="Chat">💬</button>
<button class="app-icon" data-app="mail" title="Mail">📧</button>
<button class="app-icon active" data-app="drive" title="Drive">📁</button>
<button class="app-icon" data-app="tasks" title="Tasks"></button>
<button class="app-icon" data-app="calendar" title="Calendar">📅</button>
<button class="app-icon" data-app="meet" title="Meet">📹</button>
<button class="app-icon" data-app="paper" title="Paper">📝</button>
</div>
</div>
<div class="topbar-right">
<button class="topbar-btn-primary" onclick="uploadFile()">
<span>⬆️</span> Upload
</button>
<button class="topbar-btn-icon ai-toggle active" title="AI Assistant" onclick="toggleAIPanel()">🤖</button>
<button class="topbar-btn-icon" title="Settings">⚙️</button>
</div>
</header>
<!-- Main Content Area -->
<main class="suite-main">
<!-- Left: Content Panel -->
<section class="suite-content-panel">
<!-- Stat Cards -->
<div class="stat-cards">
<div class="stat-card highlight">
<div class="stat-card-icon">💾</div>
<div class="stat-card-content">
<div class="stat-card-label">Storage Used</div>
<div class="stat-card-value">12.4 GB</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">📄</div>
<div class="stat-card-content">
<div class="stat-card-label">Files</div>
<div class="stat-card-value">1,847</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">📂</div>
<div class="stat-card-content">
<div class="stat-card-label">Folders</div>
<div class="stat-card-value">156</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">🔗</div>
<div class="stat-card-content">
<div class="stat-card-label">Shared</div>
<div class="stat-card-value">42</div>
</div>
</div>
</div>
<!-- App Header -->
<div class="app-header">
<div class="app-title-section">
<h1>Drive</h1>
<p>Manage your files and folders</p>
</div>
<div class="app-actions">
<div class="search-box">
<span class="search-icon">🔍</span>
<input type="text" placeholder="Search files..." class="search-input">
</div>
<button class="app-btn-secondary" onclick="toggleView('grid')"></button>
<button class="app-btn-secondary" onclick="toggleView('list')"></button>
</div>
</div>
<!-- Breadcrumb -->
<div class="breadcrumb">
<span class="breadcrumb-item">Home</span>
<span class="breadcrumb-separator"></span>
<span class="breadcrumb-item">Projects</span>
<span class="breadcrumb-separator"></span>
<span class="breadcrumb-item current">General Bots</span>
</div>
<!-- File Grid -->
<div class="file-grid" id="file-view">
<!-- Folders -->
<div class="file-item folder" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-name">Documents</div>
<div class="file-meta">12 items</div>
</div>
<div class="file-item folder" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-name">Images</div>
<div class="file-meta">45 items</div>
</div>
<div class="file-item folder" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-name">Source Code</div>
<div class="file-meta">89 items</div>
</div>
<div class="file-item folder" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-name">Backups</div>
<div class="file-meta">8 items</div>
</div>
<!-- Files -->
<div class="file-item" onclick="selectFile(this)">
<div class="file-icon">📄</div>
<div class="file-name">project-report.pdf</div>
<div class="file-meta">2.4 MB</div>
</div>
<div class="file-item" onclick="selectFile(this)">
<div class="file-icon">🖼️</div>
<div class="file-name">dashboard-mockup.png</div>
<div class="file-meta">1.8 MB</div>
</div>
<div class="file-item" onclick="selectFile(this)">
<div class="file-icon">📊</div>
<div class="file-name">analytics-2025.xlsx</div>
<div class="file-meta">890 KB</div>
</div>
<div class="file-item" onclick="selectFile(this)">
<div class="file-icon">📝</div>
<div class="file-name">meeting-notes.md</div>
<div class="file-meta">45 KB</div>
</div>
<div class="file-item" onclick="selectFile(this)">
<div class="file-icon">🎬</div>
<div class="file-name">demo-video.mp4</div>
<div class="file-meta">125 MB</div>
</div>
<div class="file-item" onclick="selectFile(this)">
<div class="file-icon">📦</div>
<div class="file-name">release-v2.0.zip</div>
<div class="file-meta">45 MB</div>
</div>
</div>
</section>
<!-- Right: AI Assistant Panel -->
<aside class="suite-ai-panel" id="ai-panel">
<div class="ai-panel-header">
<div class="ai-panel-title">
<span class="ai-avatar">🤖</span>
<div>
<h3>AI Assistant</h3>
<p class="ai-status">File Helper</p>
</div>
</div>
<button class="ai-panel-close" onclick="toggleAIPanel()"></button>
</div>
<div class="ai-panel-messages" id="ai-messages">
<div class="ai-message assistant">
<div class="ai-message-bubble">Olá! Posso ajudar a organizar, buscar ou processar seus arquivos.</div>
</div>
</div>
<div class="ai-quick-actions">
<span class="quick-actions-label">AÇÕES RÁPIDAS</span>
<div class="quick-actions-grid">
<button class="quick-action-btn" onclick="aiAction('organize')">Organizar pasta</button>
<button class="quick-action-btn" onclick="aiAction('find')">Buscar arquivo</button>
<button class="quick-action-btn" onclick="aiAction('analyze')">Analisar conteúdo</button>
<button class="quick-action-btn" onclick="aiAction('share')">Compartilhar</button>
</div>
</div>
<div class="ai-panel-input">
<input type="text" class="ai-input" placeholder="Peça ajuda com arquivos..." id="ai-input">
<button class="ai-send-btn" onclick="sendAIMessage()"></button>
</div>
</aside>
</main>
</div>
<script src="/suite/drive/drive-sentient.js"></script>
</body>
</html>

View file

@ -0,0 +1,217 @@
/* =============================================================================
MAIL APP - SENTIENT THEME
============================================================================= */
/* Search Box */
.search-box {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
background: var(--sentient-bg-tertiary);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-md);
min-width: 250px;
}
.search-icon {
font-size: 14px;
color: var(--sentient-text-muted);
}
.search-input {
flex: 1;
background: transparent;
border: none;
color: var(--sentient-text-primary);
font-family: var(--sentient-font-family);
font-size: 13px;
}
.search-input::placeholder {
color: var(--sentient-text-muted);
}
.search-input:focus {
outline: none;
}
/* Email List Container */
.email-list-container {
flex: 1;
background: var(--sentient-bg-card);
border: 1px solid var(--sentient-border);
border-radius: var(--sentient-radius-lg);
overflow: hidden;
display: flex;
flex-direction: column;
}
.email-list {
flex: 1;
overflow-y: auto;
}
/* Email Item */
.email-item {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
border-bottom: 1px solid var(--sentient-border);
cursor: pointer;
transition: all 0.2s ease;
}
.email-item:hover {
background: var(--sentient-bg-tertiary);
}
.email-item:last-child {
border-bottom: none;
}
.email-item.unread {
background: rgba(197, 248, 42, 0.03);
}
.email-item.unread .email-sender,
.email-item.unread .email-subject {
font-weight: 600;
}
/* Email Checkbox */
.email-checkbox {
width: 18px;
height: 18px;
accent-color: var(--sentient-accent);
cursor: pointer;
}
/* Email Star */
.email-star {
background: transparent;
border: none;
font-size: 16px;
cursor: pointer;
color: var(--sentient-text-muted);
transition: all 0.2s ease;
}
.email-star:hover {
color: var(--sentient-warning);
}
.email-star.starred {
color: var(--sentient-warning);
}
/* Email Sender */
.email-sender {
min-width: 140px;
font-size: 13px;
color: var(--sentient-text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Email Content */
.email-content {
flex: 1;
display: flex;
align-items: center;
gap: 4px;
overflow: hidden;
}
.email-subject {
font-size: 13px;
color: var(--sentient-text-primary);
white-space: nowrap;
}
.email-preview {
font-size: 13px;
color: var(--sentient-text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Email Labels */
.email-labels {
display: flex;
gap: 6px;
min-width: 80px;
}
.email-label {
padding: 3px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
}
.email-label.urgent {
background: rgba(239, 68, 68, 0.15);
color: var(--sentient-error);
}
.email-label.meeting {
background: rgba(59, 130, 246, 0.15);
color: var(--sentient-info);
}
.email-label.notification {
background: rgba(139, 92, 246, 0.15);
color: var(--sentient-paused);
}
.email-label.work {
background: var(--sentient-accent-dim);
color: var(--sentient-accent);
}
.email-label.success {
background: rgba(34, 197, 94, 0.15);
color: var(--sentient-success);
}
/* Email Date */
.email-date {
min-width: 60px;
font-size: 12px;
color: var(--sentient-text-muted);
text-align: right;
}
/* Email List Footer */
.email-list-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--sentient-bg-tertiary);
border-top: 1px solid var(--sentient-border);
}
/* Mobile Responsive */
@media (max-width: 768px) {
.email-sender {
min-width: 100px;
}
.email-preview {
display: none;
}
.email-labels {
display: none;
}
.search-box {
min-width: 150px;
}
}

View file

@ -0,0 +1,232 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email - BotUI Suite</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/themes/sentient/sentient.css">
<link rel="stylesheet" href="/suite/base-layout.css">
<link rel="stylesheet" href="/suite/mail/mail-sentient.css">
</head>
<body>
<div class="suite-app sentient-theme">
<!-- Top Header Bar -->
<header class="suite-topbar">
<div class="topbar-left">
<nav class="topbar-tabs">
<button class="topbar-tab active">Inbox</button>
<button class="topbar-tab">Sent</button>
</nav>
<div class="topbar-app-launcher">
<button class="app-icon" data-app="chat" title="Chat">💬</button>
<button class="app-icon active" data-app="mail" title="Mail">📧</button>
<button class="app-icon" data-app="drive" title="Drive">📁</button>
<button class="app-icon" data-app="tasks" title="Tasks"></button>
<button class="app-icon" data-app="calendar" title="Calendar">📅</button>
<button class="app-icon" data-app="meet" title="Meet">📹</button>
<button class="app-icon" data-app="paper" title="Paper">📝</button>
</div>
</div>
<div class="topbar-right">
<button class="topbar-btn-primary" onclick="composeEmail()">
<span>✉️</span> Compose
</button>
<button class="topbar-btn-icon ai-toggle" title="AI Assistant" onclick="toggleAIPanel()">🤖</button>
<button class="topbar-btn-icon" title="Settings">⚙️</button>
</div>
</header>
<!-- Main Content Area -->
<main class="suite-main">
<!-- Left: Content Panel -->
<section class="suite-content-panel">
<!-- Stat Cards -->
<div class="stat-cards">
<div class="stat-card highlight">
<div class="stat-card-icon">📥</div>
<div class="stat-card-content">
<div class="stat-card-label">Inbox</div>
<div class="stat-card-value">234</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon"></div>
<div class="stat-card-content">
<div class="stat-card-label">Starred</div>
<div class="stat-card-value">18</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">📤</div>
<div class="stat-card-content">
<div class="stat-card-label">Sent</div>
<div class="stat-card-value">156</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">📋</div>
<div class="stat-card-content">
<div class="stat-card-label">Drafts</div>
<div class="stat-card-value">5</div>
</div>
</div>
</div>
<!-- App Header -->
<div class="app-header">
<div class="app-title-section">
<h1>Email</h1>
<p>Manage your inbox and communications</p>
</div>
<div class="app-actions">
<div class="search-box">
<span class="search-icon">🔍</span>
<input type="text" placeholder="Search emails..." class="search-input">
</div>
<button class="app-btn-secondary">🔄</button>
<button class="app-btn-secondary"></button>
</div>
</div>
<!-- Email List -->
<div class="email-list-container">
<div class="email-list">
<div class="email-item unread">
<input type="checkbox" class="email-checkbox">
<button class="email-star starred"></button>
<div class="email-sender">Maria Santos</div>
<div class="email-content">
<span class="email-subject">Relatório Q4 finalizado</span>
<span class="email-preview"> - Olá, segue em anexo o relatório completo do quarto trimestre...</span>
</div>
<div class="email-labels">
<span class="email-label urgent">Urgente</span>
</div>
<div class="email-date">10:45</div>
</div>
<div class="email-item unread">
<input type="checkbox" class="email-checkbox">
<button class="email-star"></button>
<div class="email-sender">João Silva</div>
<div class="email-content">
<span class="email-subject">Re: Reunião amanhã</span>
<span class="email-preview"> - Confirmado! Estarei presente às 14h na sala de reuniões...</span>
</div>
<div class="email-labels">
<span class="email-label meeting">Reunião</span>
</div>
<div class="email-date">09:32</div>
</div>
<div class="email-item">
<input type="checkbox" class="email-checkbox">
<button class="email-star"></button>
<div class="email-sender">General Bots</div>
<div class="email-content">
<span class="email-subject">Novo recurso disponível</span>
<span class="email-preview"> - Temos o prazer de anunciar um novo recurso de automação...</span>
</div>
<div class="email-labels">
<span class="email-label notification">Notificação</span>
</div>
<div class="email-date">Ontem</div>
</div>
<div class="email-item">
<input type="checkbox" class="email-checkbox">
<button class="email-star starred"></button>
<div class="email-sender">Ana Costa</div>
<div class="email-content">
<span class="email-subject">Proposta comercial - Cliente ABC</span>
<span class="email-preview"> - Conforme conversamos, segue a proposta atualizada com os novos termos...</span>
</div>
<div class="email-labels">
<span class="email-label work">Trabalho</span>
</div>
<div class="email-date">Ontem</div>
</div>
<div class="email-item">
<input type="checkbox" class="email-checkbox">
<button class="email-star"></button>
<div class="email-sender">Pedro Oliveira</div>
<div class="email-content">
<span class="email-subject">Atualização do projeto</span>
<span class="email-preview"> - O desenvolvimento está 80% concluído. Próxima etapa é a revisão...</span>
</div>
<div class="email-labels"></div>
<div class="email-date">12 Dez</div>
</div>
<div class="email-item">
<input type="checkbox" class="email-checkbox">
<button class="email-star"></button>
<div class="email-sender">Lucas Mendes</div>
<div class="email-content">
<span class="email-subject">Feedback do cliente</span>
<span class="email-preview"> - Recebi feedback positivo sobre a última entrega. Eles querem...</span>
</div>
<div class="email-labels">
<span class="email-label success">Sucesso</span>
</div>
<div class="email-date">11 Dez</div>
</div>
</div>
<div class="email-list-footer">
<span class="pagination-info">1-6 of 234 emails</span>
<div class="pagination-controls">
<button class="pagination-btn"></button>
<button class="pagination-btn active">1</button>
<button class="pagination-btn">2</button>
<button class="pagination-btn">3</button>
<button class="pagination-btn"></button>
</div>
</div>
</div>
</section>
<!-- Right: AI Assistant Panel -->
<aside class="suite-ai-panel" id="ai-panel">
<div class="ai-panel-header">
<div class="ai-panel-title">
<span class="ai-avatar">🤖</span>
<div>
<h3>AI Assistant</h3>
<p class="ai-status">Email Helper</p>
</div>
</div>
<button class="ai-panel-close" onclick="toggleAIPanel()"></button>
</div>
<div class="ai-panel-messages" id="ai-messages">
<div class="ai-message assistant">
<div class="ai-message-bubble">Olá! Posso ajudar com seus emails. Peça para resumir, responder ou organizar.</div>
</div>
</div>
<div class="ai-quick-actions">
<span class="quick-actions-label">AÇÕES RÁPIDAS</span>
<div class="quick-actions-grid">
<button class="quick-action-btn" onclick="aiAction('summarize')">Resumir email</button>
<button class="quick-action-btn" onclick="aiAction('reply')">Gerar resposta</button>
<button class="quick-action-btn" onclick="aiAction('translate')">Traduzir</button>
<button class="quick-action-btn" onclick="aiAction('organize')">Organizar inbox</button>
</div>
</div>
<div class="ai-panel-input">
<input type="text" class="ai-input" placeholder="Peça ajuda com emails..." id="ai-input">
<button class="ai-send-btn" onclick="sendAIMessage()"></button>
</div>
</aside>
</main>
</div>
<script src="/suite/mail/mail-sentient.js"></script>
</body>
</html>

View file

@ -0,0 +1,203 @@
/* =============================================================================
MAIL APP - SENTIENT THEME JAVASCRIPT
============================================================================= */
(function() {
'use strict';
// =============================================================================
// AI PANEL (Collapsible, Mobile-friendly)
// =============================================================================
window.toggleAIPanel = function() {
const panel = document.getElementById('ai-panel');
const toggle = document.querySelector('.ai-toggle');
if (panel) {
panel.classList.toggle('collapsed');
if (toggle) {
toggle.classList.toggle('active', !panel.classList.contains('collapsed'));
}
localStorage.setItem('aiPanelCollapsed', panel.classList.contains('collapsed'));
}
};
window.sendAIMessage = function() {
const input = document.getElementById('ai-input');
if (!input || !input.value.trim()) return;
const message = input.value.trim();
input.value = '';
addMessage('user', message);
showTypingIndicator();
setTimeout(() => {
hideTypingIndicator();
addMessage('assistant', `Processando: "${message}". Como posso ajudar mais?`);
}, 1500);
};
window.aiAction = function(action) {
const actions = {
'summarize': 'Resumindo o email selecionado...',
'reply': 'Gerando uma resposta profissional...',
'translate': 'Traduzindo o conteúdo...',
'organize': 'Organizando sua caixa de entrada...'
};
addMessage('assistant', actions[action] || 'Processando...');
setTimeout(() => {
addMessage('assistant', 'Ação concluída com sucesso! O que mais posso fazer?');
}, 2000);
};
function addMessage(type, content) {
const container = document.getElementById('ai-messages');
if (!container) return;
const messageEl = document.createElement('div');
messageEl.className = `ai-message ${type}`;
messageEl.innerHTML = `<div class="ai-message-bubble">${content}</div>`;
container.appendChild(messageEl);
container.scrollTop = container.scrollHeight;
}
function showTypingIndicator() {
const container = document.getElementById('ai-messages');
if (!container) return;
const indicator = document.createElement('div');
indicator.className = 'ai-message assistant';
indicator.id = 'typing-indicator';
indicator.innerHTML = `
<div class="ai-typing-indicator">
<span></span><span></span><span></span>
</div>
`;
container.appendChild(indicator);
container.scrollTop = container.scrollHeight;
}
function hideTypingIndicator() {
const indicator = document.getElementById('typing-indicator');
if (indicator) indicator.remove();
}
// =============================================================================
// EMAIL FUNCTIONS
// =============================================================================
window.composeEmail = function() {
addMessage('assistant', 'Abrindo composer de email. Deseja que eu escreva um rascunho?');
};
function initEmailList() {
document.querySelectorAll('.email-item').forEach(item => {
item.addEventListener('click', function(e) {
if (e.target.type === 'checkbox' || e.target.classList.contains('email-star')) return;
document.querySelectorAll('.email-item').forEach(i => i.classList.remove('selected'));
this.classList.add('selected');
this.classList.remove('unread');
});
});
document.querySelectorAll('.email-star').forEach(star => {
star.addEventListener('click', function(e) {
e.stopPropagation();
this.classList.toggle('starred');
this.textContent = this.classList.contains('starred') ? '⭐' : '☆';
});
});
}
// =============================================================================
// APP NAVIGATION
// =============================================================================
function initAppLauncher() {
document.querySelectorAll('.app-icon').forEach(icon => {
icon.addEventListener('click', function() {
const app = this.dataset.app;
if (app === 'chat') {
window.location.href = '/suite/chat/chat-sentient.html';
} else if (app === 'drive') {
window.location.href = '/suite/drive/drive-sentient.html';
} else if (app === 'tasks') {
window.location.href = '/suite/tasks/tasks-sentient.html';
} else if (app === 'calendar') {
window.location.href = '/suite/calendar/calendar-sentient.html';
} else if (app === 'meet') {
window.location.href = '/suite/meet/meet-sentient.html';
} else if (app === 'paper') {
window.location.href = '/suite/paper/paper-sentient.html';
}
});
});
}
function initTabs() {
document.querySelectorAll('.topbar-tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.topbar-tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
}
// =============================================================================
// KEYBOARD SHORTCUTS
// =============================================================================
function initKeyboardShortcuts() {
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && document.activeElement.id === 'ai-input') {
e.preventDefault();
sendAIMessage();
}
// Ctrl+Shift+A to toggle AI panel
if (e.ctrlKey && e.shiftKey && e.key === 'A') {
e.preventDefault();
toggleAIPanel();
}
});
}
// =============================================================================
// RESTORE AI PANEL STATE
// =============================================================================
function restoreAIPanelState() {
const collapsed = localStorage.getItem('aiPanelCollapsed') === 'true';
const panel = document.getElementById('ai-panel');
const toggle = document.querySelector('.ai-toggle');
if (panel && collapsed) {
panel.classList.add('collapsed');
}
if (toggle) {
toggle.classList.toggle('active', !collapsed);
}
}
// =============================================================================
// INITIALIZE
// =============================================================================
function init() {
initEmailList();
initAppLauncher();
initTabs();
initKeyboardShortcuts();
restoreAIPanelState();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,226 +1,657 @@
/* Tasks page JavaScript */ /* =============================================================================
TASKS APP JAVASCRIPT
Automated Intelligent Task Management Interface
============================================================================= */
// Set active tab // =============================================================================
function setActiveTab(button) { // STATE MANAGEMENT
document.querySelectorAll(".filter-tab").forEach((tab) => { // =============================================================================
tab.classList.remove("active");
const TasksState = {
selectedTaskId: 2, // Default selected task
currentFilter: "complete",
tasks: [],
wsConnection: null,
agentLogPaused: false,
};
// =============================================================================
// INITIALIZATION
// =============================================================================
document.addEventListener("DOMContentLoaded", function () {
initTasksApp();
});
function initTasksApp() {
// Initialize WebSocket for real-time updates
initWebSocket();
// Setup event listeners
setupEventListeners();
// Setup keyboard shortcuts
setupKeyboardShortcuts();
// Auto-scroll agent log to bottom
scrollAgentLogToBottom();
console.log("[Tasks] Initialized");
}
// =============================================================================
// WEBSOCKET CONNECTION
// =============================================================================
function initWebSocket() {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/ws/tasks`;
try {
TasksState.wsConnection = new WebSocket(wsUrl);
TasksState.wsConnection.onopen = function () {
console.log("[Sentient Tasks] WebSocket connected");
addAgentLog("info", "[SYSTEM] Connected to task orchestrator");
};
TasksState.wsConnection.onmessage = function (event) {
handleWebSocketMessage(JSON.parse(event.data));
};
TasksState.wsConnection.onclose = function () {
console.log("[Sentient Tasks] WebSocket disconnected, reconnecting...");
setTimeout(initWebSocket, 5000);
};
TasksState.wsConnection.onerror = function (error) {
console.error("[Sentient Tasks] WebSocket error:", error);
};
} catch (e) {
console.warn("[Sentient Tasks] WebSocket not available");
}
}
function handleWebSocketMessage(data) {
switch (data.type) {
case "task_update":
updateTaskCard(data.task);
if (data.task.id === TasksState.selectedTaskId) {
updateTaskDetail(data.task);
}
break;
case "step_progress":
updateStepProgress(data.taskId, data.step);
break;
case "agent_log":
addAgentLog(data.level, data.message);
break;
case "decision_required":
showDecisionRequired(data.decision);
break;
case "task_completed":
onTaskCompleted(data.task);
break;
case "task_failed":
onTaskFailed(data.task, data.error);
break;
}
}
// =============================================================================
// EVENT LISTENERS
// =============================================================================
function setupEventListeners() {
// Filter pills
document.querySelectorAll(".status-pill").forEach((pill) => {
pill.addEventListener("click", function (e) {
e.preventDefault();
const filter = this.dataset.filter;
setActiveFilter(filter, this);
});
});
// Search input
const searchInput = document.querySelector(".topbar-search-input");
if (searchInput) {
searchInput.addEventListener(
"input",
debounce(function (e) {
searchTasks(e.target.value);
}, 300),
);
}
// Nav items
document.querySelectorAll(".topbar-nav-item").forEach((item) => {
item.addEventListener("click", function () {
document
.querySelectorAll(".topbar-nav-item")
.forEach((i) => i.classList.remove("active"));
this.classList.add("active");
});
});
// Progress log toggle
const logToggle = document.querySelector(".progress-log-toggle");
if (logToggle) {
logToggle.addEventListener("click", toggleProgressLog);
}
}
function setupKeyboardShortcuts() {
document.addEventListener("keydown", function (e) {
// Escape: Deselect task
if (e.key === "Escape") {
deselectTask();
}
// Cmd/Ctrl + K: Focus search
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
document.querySelector(".topbar-search-input")?.focus();
}
// Arrow keys: Navigate tasks
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
e.preventDefault();
navigateTasks(e.key === "ArrowDown" ? 1 : -1);
}
// Enter: Submit decision if in decision mode
if (
e.key === "Enter" &&
document.querySelector(".decision-option.selected")
) {
submitDecision();
}
// 1-5: Quick filter
if (e.key >= "1" && e.key <= "5" && !e.target.matches("input, textarea")) {
const pills = document.querySelectorAll(".status-pill");
const index = parseInt(e.key) - 1;
if (pills[index]) {
pills[index].click();
}
}
});
}
// =============================================================================
// TASK SELECTION & FILTERING
// =============================================================================
function selectTask(taskId) {
TasksState.selectedTaskId = taskId;
// Update selected state in list
document.querySelectorAll(".task-card").forEach((card) => {
card.classList.toggle("selected", card.dataset.taskId == taskId);
});
// Load task details (in real app, this would fetch from API)
loadTaskDetails(taskId);
}
function deselectTask() {
TasksState.selectedTaskId = null;
document.querySelectorAll(".task-card").forEach((card) => {
card.classList.remove("selected");
});
}
function navigateTasks(direction) {
const cards = Array.from(document.querySelectorAll(".task-card"));
if (cards.length === 0) return;
const currentIndex = cards.findIndex((c) => c.classList.contains("selected"));
let newIndex;
if (currentIndex === -1) {
newIndex = direction === 1 ? 0 : cards.length - 1;
} else {
newIndex = currentIndex + direction;
if (newIndex < 0) newIndex = cards.length - 1;
if (newIndex >= cards.length) newIndex = 0;
}
const taskId = cards[newIndex].dataset.taskId;
selectTask(taskId);
cards[newIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
}
function setActiveFilter(filter, button) {
TasksState.currentFilter = filter;
// Update active pill
document.querySelectorAll(".status-pill").forEach((pill) => {
pill.classList.remove("active");
}); });
button.classList.add("active"); button.classList.add("active");
// Filter will be handled by HTMX, but we track state
addAgentLog("info", `[FILTER] Showing ${filter} tasks`);
} }
// Export tasks as JSON function searchTasks(query) {
function exportTasks() { if (query.length > 0) {
fetch("/api/tasks?format=json") addAgentLog("info", `[SEARCH] Searching: "${query}"`);
}
// In real app, this would filter via API
// For demo, we'll do client-side filtering
const cards = document.querySelectorAll(".task-card");
cards.forEach((card) => {
const title =
card.querySelector(".task-card-title")?.textContent.toLowerCase() || "";
const subtitle =
card.querySelector(".task-card-subtitle")?.textContent.toLowerCase() ||
"";
const matches =
title.includes(query.toLowerCase()) ||
subtitle.includes(query.toLowerCase());
card.style.display = matches || query === "" ? "block" : "none";
});
}
// =============================================================================
// TASK DETAILS
// =============================================================================
function loadTaskDetails(taskId) {
// In real app, fetch from API
// htmx.ajax('GET', `/api/tasks/${taskId}`, {target: '#task-detail-panel', swap: 'innerHTML'});
addAgentLog("info", `[LOAD] Loading task #${taskId} details`);
}
function updateTaskCard(task) {
const card = document.querySelector(`[data-task-id="${task.id}"]`);
if (!card) return;
// Update progress
const progressFill = card.querySelector(".task-progress-fill");
const progressPercent = card.querySelector(".task-progress-percent");
const progressSteps = card.querySelector(".task-progress-steps");
if (progressFill) progressFill.style.width = `${task.progress}%`;
if (progressPercent) progressPercent.textContent = `${task.progress}%`;
if (progressSteps)
progressSteps.textContent = `${task.currentStep}/${task.totalSteps} steps`;
// Update status badge
const statusBadge = card.querySelector(".task-card-status");
if (statusBadge) {
statusBadge.className = `task-card-status ${task.status}`;
statusBadge.textContent = formatStatus(task.status);
}
}
function updateTaskDetail(task) {
// Update detail panel with task data
const detailTitle = document.querySelector(".task-detail-title");
if (detailTitle) detailTitle.textContent = task.title;
}
// =============================================================================
// DECISION HANDLING
// =============================================================================
function selectDecision(element, value) {
// Remove selected from all options
document.querySelectorAll(".decision-option").forEach((opt) => {
opt.classList.remove("selected");
});
// Add selected to clicked option
element.classList.add("selected");
// Store selected value
TasksState.selectedDecision = value;
addAgentLog("info", `[DECISION] Selected: ${value}`);
}
function submitDecision() {
const selectedOption = document.querySelector(".decision-option.selected");
if (!selectedOption) {
showToast("Please select an option", "warning");
return;
}
const value = TasksState.selectedDecision;
const taskId = TasksState.selectedTaskId;
addAgentLog("accent", `[AGENT] Applying decision: ${value}`);
addAgentLog("info", `[TASK] Resuming task #${taskId}...`);
// In real app, send to API
fetch(`/api/tasks/${taskId}/decide`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ decision: value }),
})
.then((response) => response.json()) .then((response) => response.json())
.then((tasks) => { .then((result) => {
const dataStr = JSON.stringify(tasks, null, 2); if (result.success) {
const dataUri = showToast("Decision applied successfully", "success");
"data:application/json;charset=utf-8," + addAgentLog("success", `[OK] Decision applied, task resuming`);
encodeURIComponent(dataStr);
const exportFileDefaultName = `tasks-${new Date().toISOString().split("T")[0]}.json`; // Hide decision section (in real app, would update via HTMX)
const decisionSection = document.querySelector(
const linkElement = document.createElement("a"); ".decision-required-section",
linkElement.setAttribute("href", dataUri); );
linkElement.setAttribute("download", exportFileDefaultName); if (decisionSection) {
linkElement.click(); decisionSection.style.display = "none";
}); }
}
// Update task statistics
function updateStats() {
fetch("/api/tasks/stats")
.then((response) => response.json())
.then((stats) => {
// Update header stats
document.querySelector(
".stat-item:nth-child(1) .stat-value",
).textContent = stats.total || 0;
document.querySelector(
".stat-item:nth-child(2) .stat-value",
).textContent = stats.active || 0;
document.querySelector(
".stat-item:nth-child(3) .stat-value",
).textContent = stats.completed || 0;
// Update tab counts
document.getElementById("count-all").textContent =
stats.total || 0;
document.getElementById("count-active").textContent =
stats.active || 0;
document.getElementById("count-completed").textContent =
stats.completed || 0;
document.getElementById("count-priority").textContent =
stats.priority || 0;
// Update footer text
const footerText = document.getElementById("footer-text");
if (stats.active === 0) {
footerText.innerHTML = "All tasks completed! 🎉";
} else { } else {
footerText.innerHTML = `<strong>${stats.active}</strong> ${stats.active === 1 ? "task" : "tasks"} remaining`; showToast("Failed to apply decision", "error");
addAgentLog(
"error",
`[ERROR] Failed to apply decision: ${result.error}`,
);
} }
})
.catch((error) => {
// For demo, simulate success
showToast("Decision applied successfully", "success");
addAgentLog("success", `[OK] Decision applied, task resuming`);
// Show/hide footer const decisionSection = document.querySelector(
const footer = document.getElementById("task-footer"); ".decision-required-section",
footer.style.display = stats.total > 0 ? "flex" : "none"; );
}); if (decisionSection) {
} decisionSection.style.opacity = "0.5";
// Handle checkbox changes
document.addEventListener("change", function (e) {
if (e.target.classList.contains("task-checkbox")) {
const taskId = e.target.dataset.taskId;
const completed = e.target.checked;
fetch(`/api/tasks/${taskId}/status`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ completed }),
}).then(() => {
const taskItem = e.target.closest(".task-item");
if (completed) {
taskItem.classList.add("completed");
} else {
taskItem.classList.remove("completed");
}
updateStats();
});
}
});
// Handle task actions
document.addEventListener("click", function (e) {
// Priority toggle
if (e.target.closest('[data-action="priority"]')) {
const btn = e.target.closest('[data-action="priority"]');
const taskId = btn.dataset.taskId;
const priority = !btn.classList.contains("active");
fetch(`/api/tasks/${taskId}/priority`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ priority }),
}).then(() => {
btn.classList.toggle("active");
updateStats();
});
}
// Edit task
if (e.target.closest('[data-action="edit"]')) {
const btn = e.target.closest('[data-action="edit"]');
const taskId = btn.dataset.taskId;
const taskItem = btn.closest(".task-item");
const taskText = taskItem.querySelector(".task-text");
const currentText = taskText.textContent;
const input = document.createElement("input");
input.type = "text";
input.className = "task-edit-input";
input.value = currentText;
taskText.replaceWith(input);
input.focus();
input.select();
input.addEventListener("blur", function () {
saveEdit();
});
input.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
saveEdit();
} else if (e.key === "Escape") {
cancelEdit();
}
});
function saveEdit() {
const newText = input.value.trim();
if (newText && newText !== currentText) {
fetch(`/api/tasks/${taskId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: newText }),
}).then(() => {
const span = document.createElement("span");
span.className = "task-text";
span.textContent = newText;
input.replaceWith(span);
});
} else {
cancelEdit();
}
}
function cancelEdit() {
const span = document.createElement("span");
span.className = "task-text";
span.textContent = currentText;
input.replaceWith(span);
}
}
// Delete task
if (e.target.closest('[data-action="delete"]')) {
const btn = e.target.closest('[data-action="delete"]');
const taskId = btn.dataset.taskId;
if (confirm("Delete this task?")) {
fetch(`/api/tasks/${taskId}`, {
method: "DELETE",
}).then(() => {
const taskItem = btn.closest(".task-item");
taskItem.style.animation = "slideOut 0.3s ease";
setTimeout(() => { setTimeout(() => {
taskItem.remove(); decisionSection.style.display = "none";
updateStats(); }, 500);
}, 300);
});
} }
}
});
// Animation for removing tasks // Update step status
const activeStep = document.querySelector(".step-item.active");
if (activeStep) {
activeStep.classList.remove("active");
activeStep.classList.add("completed");
activeStep.querySelector(".step-icon").textContent = "✓";
activeStep.querySelector(".step-detail").textContent =
"Completed with merge strategy";
const nextStep = activeStep.nextElementSibling;
if (nextStep && nextStep.classList.contains("pending")) {
nextStep.classList.remove("pending");
nextStep.classList.add("active");
nextStep.querySelector(".step-icon").textContent = "●";
nextStep.querySelector(".step-time").textContent = "Now";
}
}
});
}
function showDecisionRequired(decision) {
addAgentLog("warning", `[ALERT] Decision required: ${decision.title}`);
showToast(`Decision required: ${decision.title}`, "warning");
}
// =============================================================================
// PROGRESS LOG
// =============================================================================
function toggleProgressLog() {
const stepList = document.querySelector(".step-list");
const toggle = document.querySelector(".progress-log-toggle");
if (stepList.style.display === "none") {
stepList.style.display = "flex";
toggle.textContent = "Collapse";
} else {
stepList.style.display = "none";
toggle.textContent = "Expand";
}
}
function updateStepProgress(taskId, step) {
if (taskId !== TasksState.selectedTaskId) return;
const stepItems = document.querySelectorAll(".step-item");
stepItems.forEach((item, index) => {
if (index < step.index) {
item.classList.remove("active", "pending");
item.classList.add("completed");
item.querySelector(".step-icon").textContent = "✓";
} else if (index === step.index) {
item.classList.remove("completed", "pending");
item.classList.add("active");
item.querySelector(".step-icon").textContent = "●";
item.querySelector(".step-name").textContent = step.name;
item.querySelector(".step-detail").textContent = step.detail;
item.querySelector(".step-time").textContent = "Now";
} else {
item.classList.remove("completed", "active");
item.classList.add("pending");
item.querySelector(".step-icon").textContent = "○";
}
});
}
// =============================================================================
// AGENT ACTIVITY LOG
// =============================================================================
function addAgentLog(level, message) {
if (TasksState.agentLogPaused) return;
const log = document.getElementById("agent-log");
if (!log) return;
const now = new Date();
const timestamp = now.toTimeString().split(" ")[0].substring(0, 8);
const line = document.createElement("div");
line.className = `activity-line ${level}`;
line.innerHTML = `
<span class="activity-timestamp">${timestamp}</span>
<span class="activity-message">${message}</span>
`;
// Insert at the top
log.insertBefore(line, log.firstChild);
// Limit log entries
while (log.children.length > 100) {
log.removeChild(log.lastChild);
}
}
function scrollAgentLogToBottom() {
const log = document.getElementById("agent-log");
if (log) {
log.scrollTop = 0; // Since newest is at top
}
}
function clearAgentLog() {
const log = document.getElementById("agent-log");
if (log) {
log.innerHTML = "";
addAgentLog("info", "[SYSTEM] Log cleared");
}
}
function toggleAgentLogPause() {
TasksState.agentLogPaused = !TasksState.agentLogPaused;
const pauseBtn = document.querySelector(".agent-activity-btn:last-child");
if (pauseBtn) {
pauseBtn.textContent = TasksState.agentLogPaused ? "Resume" : "Pause";
}
addAgentLog(
"info",
TasksState.agentLogPaused ? "[SYSTEM] Log paused" : "[SYSTEM] Log resumed",
);
}
// =============================================================================
// TASK LIFECYCLE
// =============================================================================
function onTaskCompleted(task) {
showToast(`Task completed: ${task.title}`, "success");
addAgentLog("success", `[COMPLETE] Task #${task.id}: ${task.title}`);
updateTaskCard(task);
}
function onTaskFailed(task, error) {
showToast(`Task failed: ${task.title}`, "error");
addAgentLog("error", `[FAILED] Task #${task.id}: ${error}`);
updateTaskCard(task);
}
// =============================================================================
// TOAST NOTIFICATIONS
// =============================================================================
function showToast(message, type = "info") {
let container = document.getElementById("toast-container");
if (!container) {
container = document.createElement("div");
container.id = "toast-container";
container.style.cssText = `
position: fixed;
bottom: 24px;
right: 24px;
z-index: 10000;
display: flex;
flex-direction: column;
gap: 8px;
`;
document.body.appendChild(container);
}
const toast = document.createElement("div");
const bgColors = {
success: "rgba(34, 197, 94, 0.95)",
error: "rgba(239, 68, 68, 0.95)",
warning: "rgba(245, 158, 11, 0.95)",
info: "rgba(59, 130, 246, 0.95)",
};
const icons = {
success: "✓",
error: "✕",
warning: "⚠",
info: "",
};
toast.style.cssText = `
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: ${bgColors[type] || bgColors.info};
border-radius: 10px;
color: white;
font-size: 14px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
animation: slideIn 0.3s ease;
`;
toast.innerHTML = `
<span style="font-size: 16px;">${icons[type] || icons.info}</span>
<span>${message}</span>
`;
container.appendChild(toast);
setTimeout(() => {
toast.style.animation = "fadeOut 0.3s ease forwards";
setTimeout(() => toast.remove(), 300);
}, 4000);
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function formatStatus(status) {
const statusMap = {
complete: "Complete",
running: "Running",
awaiting: "Awaiting",
paused: "Paused",
blocked: "Blocked",
};
return statusMap[status] || status;
}
function formatTime(seconds) {
if (seconds < 60) return `${seconds}s`;
if (seconds < 3600) {
const mins = Math.floor(seconds / 60);
return `${mins}m`;
}
const hours = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
return `${hours}h ${mins}m`;
}
// =============================================================================
// GLOBAL STYLES FOR TOAST ANIMATIONS
// =============================================================================
const style = document.createElement("style"); const style = document.createElement("style");
style.textContent = ` style.textContent = `
@keyframes slideOut { @keyframes slideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translateX(0);
}
to { to {
opacity: 0; opacity: 0;
transform: translateX(-100%); transform: translateX(20px);
}
} }
}
`; `;
document.head.appendChild(style); document.head.appendChild(style);
// Update stats after any HTMX request // =============================================================================
document.body.addEventListener("htmx:afterSwap", function (evt) { // DEMO: Simulate real-time activity
if (evt.detail.target.id === "task-list") { // =============================================================================
updateStats();
}
});
// Initial stats load // Simulate agent activity for demo
document.addEventListener("DOMContentLoaded", function () { setInterval(() => {
updateStats(); if (Math.random() > 0.7) {
}); const messages = [
{ level: "info", msg: "[SCAN] Monitoring task queues..." },
// Keyboard shortcuts { level: "info", msg: "[AGENT] Processing next batch..." },
document.addEventListener("keydown", function (e) { { level: "success", msg: "[OK] Checkpoint saved" },
// Alt + N for new task { level: "info", msg: "[SYNC] Synchronizing state..." },
if (e.altKey && e.key === "n") { ];
e.preventDefault(); const { level, msg } =
document.querySelector(".task-input").focus(); messages[Math.floor(Math.random() * messages.length)];
addAgentLog(level, msg);
} }
}, 5000);
// Alt + 1-4 for filter tabs
if (e.altKey && e.key >= "1" && e.key <= "4") {
e.preventDefault();
const tabs = document.querySelectorAll(".filter-tab");
const index = parseInt(e.key) - 1;
if (tabs[index]) {
tabs[index].click();
}
}
});