🔴 Alta priority: tema claro/escuro, modal tags, favicon K, badge modelo dinâmico

This commit is contained in:
2026-05-26 15:39:37 +00:00
parent 36ea7cce7a
commit f42bb49360
5 changed files with 629 additions and 16 deletions
+116 -13
View File
@@ -96,6 +96,120 @@ const initApp = () => {
let recognition = null;
let isListening = false;
// ==========================================================================
// THEME TOGGLE — Claro/Escuro
// ==========================================================================
const btnThemeToggle = document.getElementById('btnThemeToggle');
const iconThemeSun = document.getElementById('iconThemeSun');
const iconThemeMoon = document.getElementById('iconThemeMoon');
const applyTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('camila_theme', theme);
if (theme === 'light') {
iconThemeSun.style.display = 'none';
iconThemeMoon.style.display = 'block';
} else {
iconThemeSun.style.display = 'block';
iconThemeMoon.style.display = 'none';
}
};
// Init theme from localStorage or system preference
const savedTheme = localStorage.getItem('camila_theme');
if (savedTheme) {
applyTheme(savedTheme);
} else {
// Default to dark
applyTheme('dark');
}
btnThemeToggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme') || 'dark';
applyTheme(current === 'dark' ? 'light' : 'dark');
});
// ==========================================================================
// BADGE DE MODELO DINÂMICO
// ==========================================================================
const modelBadgeText = document.getElementById('modelBadgeText');
const updateModelBadge = async () => {
try {
const res = await fetch('/api/status');
if (res.ok) {
const data = await res.json();
// Extrai nome abreviado do modelo (ex: "openai/gpt-4.1-nano" → "GPT-4.1 Nano")
const modelName = data.model.split('/').pop();
const formatted = modelName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
modelBadgeText.textContent = formatted;
}
} catch (e) {
// Mantém texto padrão se falhar
}
};
updateModelBadge();
// ==========================================================================
// MODAL DE TAGS
// ==========================================================================
const AVAILABLE_TAGS = ['HTPC', 'ATA', 'Planejamento', 'Formação', 'Projeto', 'Relatório', 'Sugestão', 'Dúvida'];
const modalTagOverlay = document.getElementById('modalTagOverlay');
const modalTagList = document.getElementById('modalTagList');
const modalTagClose = document.getElementById('modalTagClose');
const modalTagCancel = document.getElementById('modalTagCancel');
const modalTagConfirm = document.getElementById('modalTagConfirm');
let pendingTagAction = null; // { chatId, callback }
let selectedTagsState = [];
const openTagModal = (chatId, currentTags, callback) => {
pendingTagAction = { chatId, callback };
selectedTagsState = [...currentTags];
renderTagModalList();
modalTagOverlay.classList.add('active');
};
const closeTagModal = () => {
modalTagOverlay.classList.remove('active');
pendingTagAction = null;
};
const renderTagModalList = () => {
modalTagList.innerHTML = AVAILABLE_TAGS.map(tag => `
<div class="modal-tag-item ${selectedTagsState.includes(tag) ? 'selected' : ''}" data-tag="${tag}">
<input type="checkbox" ${selectedTagsState.includes(tag) ? 'checked' : ''}>
<span>${tag}</span>
</div>
`).join('');
modalTagList.querySelectorAll('.modal-tag-item').forEach(item => {
item.addEventListener('click', () => {
const tag = item.dataset.tag;
if (selectedTagsState.includes(tag)) {
selectedTagsState = selectedTagsState.filter(t => t !== tag);
} else {
selectedTagsState.push(tag);
}
renderTagModalList();
});
});
};
modalTagClose.addEventListener('click', closeTagModal);
modalTagCancel.addEventListener('click', closeTagModal);
modalTagOverlay.addEventListener('click', (e) => {
if (e.target === modalTagOverlay) closeTagModal();
});
modalTagConfirm.addEventListener('click', () => {
if (pendingTagAction) {
pendingTagAction.callback(selectedTagsState);
}
closeTagModal();
});
// Expose globally for inline onclick handlers
window.openTagModal = openTagModal;
// ==========================================================================
// CONTROLES DE INTERFACE & SIDEBAR
// ==========================================================================
@@ -696,24 +810,13 @@ const initApp = () => {
const editTags = (id) => {
const chat = chats.find(c => c.id === id);
if (!chat) return;
const availableTags = ['relatorios', 'planejamento', 'mindlab', 'estudos'];
const currentTags = chat.tags || [];
// Prompt amigável mostrando opções
const promptText = `Atribua tags à conversa.\nOpções válidas: relatorios, planejamento, mindlab, estudos\n(Use vírgula para separar várias tags, ou deixe em branco para limpar):\n`;
const tagsStr = prompt(promptText, currentTags.join(', '));
if (tagsStr !== null) {
const newTags = tagsStr.split(',')
.map(t => t.trim().toLowerCase())
.filter(t => availableTags.includes(t));
openTagModal(id, currentTags, (newTags) => {
chat.tags = newTags;
chat.updatedAt = Date.now();
saveChats();
renderHistory();
}
});
};
const selectChat = (id) => {
+35 -2
View File
@@ -11,7 +11,8 @@
<!-- Highlight.js para destaque de sintaxe em códigos -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<link rel="stylesheet" href="style.css">
<link rel="shortcut icon" href="https://chatgpt.com/favicon.ico" type="image/x-icon">
<!-- Favicon customizado K (Kemily/Camila) -->
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%2310a37f'/><text x='16' y='22' font-family='Arial' font-size='18' font-weight='bold' fill='white' text-anchor='middle'>K</text></svg>" type="image/svg+xml">
</head>
<body>
<div class="app-container">
@@ -101,12 +102,25 @@
</button>
<div class="model-badge">
<span>GPT-4.1 Nano</span>
<span id="modelBadgeText">GPT-4.1 Nano</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="chevron">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</div>
<div class="header-right">
<button class="btn-theme-toggle" id="btnThemeToggle" title="Alternar tema" aria-label="Alternar entre tema claro e escuro">
<!-- Sun icon (shown in dark mode, to switch to light) -->
<svg id="iconThemeSun" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M12 6.75a3.75 3.75 0 1 0 0 7.5 3.75 3.75 0 0 0 0-7.5Z" />
</svg>
<!-- Moon icon (shown in light mode, to switch to dark) -->
<svg id="iconThemeMoon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="display:none">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>
</button>
</div>
<button class="btn-new-chat-mobile" id="btnNewChatMobile" aria-label="Novo chat">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
@@ -159,6 +173,25 @@
<p class="disclaimer">Camila AI pode cometer erros. Considere verificar informações importantes.</p>
</div>
</footer>
<!-- Modal de Tags -->
<div class="modal-overlay" id="modalTagOverlay">
<div class="modal-tag">
<div class="modal-tag-header">
<span class="modal-tag-title">Selecionar tags</span>
<button class="modal-tag-close" id="modalTagClose" aria-label="Fechar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="20" height="20">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="modal-tag-list" id="modalTagList"></div>
<div class="modal-tag-actions">
<button class="modal-tag-btn secondary" id="modalTagCancel">Cancelar</button>
<button class="modal-tag-btn primary" id="modalTagConfirm">Confirmar</button>
</div>
</div>
</div>
</main>
</div>
+316 -1
View File
@@ -2,6 +2,7 @@
CSS GLOBAL & VARIÁVEIS (DESIGN SYSTEM PREMIUM)
========================================================================== */
:root {
/* Dark theme (default) */
--bg-primary: #0d0d0d;
--bg-secondary: #171717;
--bg-tertiary: #212121;
@@ -9,7 +10,7 @@
--text-primary: #ececec;
--text-secondary: #b4b4b4;
--text-muted: #8e8e8e;
--text-muted: #9e9e9e;
--brand-green: #10a37f;
--brand-green-hover: #1a7f64;
@@ -39,6 +40,30 @@
--transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
/* LIGHT THEME */
[data-theme="light"] {
--bg-primary: #f5f5f5;
--bg-secondary: #ffffff;
--bg-tertiary: #e8e8e8;
--bg-accent: #dcdcdc;
--text-primary: #1a1a1a;
--text-secondary: #4a4a4a;
--text-muted: #7a7a7a;
--brand-green: #10a37f;
--brand-green-hover: #0d8f6f;
--brand-glow: rgba(16, 163, 127, 0.1);
--border-light: rgba(0, 0, 0, 0.08);
--border-focus: rgba(16, 163, 127, 0.4);
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.15), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
* {
box-sizing: border-box;
margin: 0;
@@ -1583,3 +1608,293 @@ code {
outline: none !important;
box-shadow: 0 0 5px var(--brand-glow) !important;
}
/* ==========================================================================
THEME TOGGLE BUTTON
========================================================================== */
.btn-theme-toggle {
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 8px;
color: var(--text-secondary);
transition: background var(--transition-fast), color var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
}
.btn-theme-toggle:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.btn-theme-toggle svg {
width: 20px;
height: 20px;
}
/* Theme toggle in header - right side */
.header-right {
display: flex;
align-items: center;
gap: 4px;
}
/* ==========================================================================
MODAL DE TAGS (substitui prompt())
========================================================================== */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: opacity var(--transition-normal), visibility var(--transition-normal);
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.modal-tag {
background: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: 16px;
padding: 24px;
min-width: 320px;
max-width: 90vw;
box-shadow: var(--shadow-xl);
transform: scale(0.9) translateY(20px);
transition: transform var(--transition-normal);
}
.modal-overlay.active .modal-tag {
transform: scale(1) translateY(0);
}
.modal-tag-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.modal-tag-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.modal-tag-close {
background: none;
border: none;
cursor: pointer;
padding: 6px;
border-radius: 6px;
color: var(--text-muted);
transition: background var(--transition-fast), color var(--transition-fast);
}
.modal-tag-close:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.modal-tag-list {
display: flex;
flex-direction: column;
gap: 8px;
max-height: 300px;
overflow-y: auto;
}
.modal-tag-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 10px;
cursor: pointer;
transition: background var(--transition-fast);
border: 1px solid transparent;
}
.modal-tag-item:hover {
background: var(--bg-tertiary);
}
.modal-tag-item.selected {
background: var(--brand-glow);
border-color: var(--brand-green);
}
.modal-tag-item input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--brand-green);
cursor: pointer;
}
.modal-tag-item span {
font-size: 14px;
color: var(--text-primary);
}
.modal-tag-item.selected span {
color: var(--brand-green);
font-weight: 500;
}
.modal-tag-actions {
display: flex;
gap: 10px;
margin-top: 20px;
justify-content: flex-end;
}
.modal-tag-btn {
padding: 10px 20px;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background var(--transition-fast), transform var(--transition-fast);
border: none;
}
.modal-tag-btn:active {
transform: scale(0.97);
}
.modal-tag-btn.primary {
background: var(--brand-green);
color: white;
}
.modal-tag-btn.primary:hover {
background: var(--brand-green-hover);
}
.modal-tag-btn.secondary {
background: var(--bg-tertiary);
color: var(--text-secondary);
}
.modal-tag-btn.secondary:hover {
background: var(--bg-accent);
color: var(--text-primary);
}
/* ==========================================================================
TAG INPUT DISPLAY
========================================================================== */
.tags-display {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
padding: 10px;
background: var(--bg-tertiary);
border-radius: 10px;
min-height: 44px;
align-items: center;
}
.tag-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border-radius: 16px;
font-size: 12px;
font-weight: 500;
background: var(--brand-glow);
color: var(--brand-green);
border: 1px solid rgba(16, 163, 127, 0.3);
}
.tag-badge button {
background: none;
border: none;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
color: var(--brand-green);
opacity: 0.7;
transition: opacity var(--transition-fast);
}
.tag-badge button:hover {
opacity: 1;
}
.no-tags {
color: var(--text-muted);
font-size: 12px;
font-style: italic;
}
/* ==========================================================================
WELCOME CARDS (tematicos)
========================================================================== */
.welcome-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
max-width: 600px;
margin-top: 20px;
}
@media (max-width: 500px) {
.welcome-grid {
grid-template-columns: 1fr;
}
}
.suggestion-card {
background: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: 14px;
padding: 16px;
cursor: pointer;
text-align: left;
transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
display: flex;
flex-direction: column;
gap: 8px;
}
.suggestion-card:hover {
background: var(--bg-tertiary);
border-color: var(--brand-green);
transform: translateY(-2px);
}
.suggestion-card .card-icon {
font-size: 24px;
line-height: 1;
}
.suggestion-card .card-title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
}
.suggestion-card .card-desc {
font-size: 12px;
color: var(--text-muted);
line-height: 1.4;
}