feat: adiciona barra de botoes de atalho rapido de modo (musica, video, imagem, chat) no rodape com validacao cruzada

This commit is contained in:
2026-05-26 17:59:57 +00:00
parent 63ac641c5f
commit 19552a6078
4 changed files with 173 additions and 6 deletions
+35 -2
View File
@@ -1173,7 +1173,7 @@ const initApp = () => {
};
// Função principal de envio de mensagens
const handleSendMessage = async (text, skipUserAppend = false, files = null) => {
const handleSendMessage = async (text, skipUserAppend = false, files = null, forcedIntent = null) => {
if (!text && (!files || files.length === 0)) return;
// Se não recebe files por parâmetro, usa os anexados globalmente
@@ -1282,7 +1282,7 @@ const initApp = () => {
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ messages: messagesPayload })
body: JSON.stringify({ messages: messagesPayload, forcedIntent: forcedIntent })
});
if (!response.ok) {
@@ -1521,6 +1521,39 @@ const initApp = () => {
handleSendMessage(text);
});
// Botões de Modos Rápidos de Prompt (Rodapé)
document.querySelectorAll('.btn-prompt-mode').forEach(btn => {
btn.addEventListener('click', () => {
const mode = btn.dataset.mode;
const text = userInput.value.trim();
// Se não houver texto digitado, insere um exemplo de prompt baseado na modalidade
if (!text) {
let suggestion = "";
if (mode === 'AUDIO') {
suggestion = "Crie uma música pedagógica alegre e animada sobre ";
} else if (mode === 'VIDEO') {
suggestion = "Crie um roteiro detalhado para um vídeo educativo sobre ";
} else if (mode === 'IMAGE') {
suggestion = "Crie uma ilustração pedagógica com cores vibrantes mostrando ";
} else if (mode === 'TEXT') {
suggestion = "Escreva uma história educativa infantil curta sobre ";
}
userInput.value = suggestion;
userInput.focus();
// Ajustar altura do textarea para acomodar a sugestão
userInput.style.height = 'auto';
userInput.style.height = userInput.scrollHeight + 'px';
btnSend.disabled = false;
return;
}
// Se houver texto, envia imediatamente forçando o modo correspondente
handleSendMessage(text, false, null, mode);
});
});
// Sugestões de Prompt Rápidas
suggestionCards.forEach(card => {
card.addEventListener('click', () => {
+15
View File
@@ -179,6 +179,21 @@
<!-- Barra inferior com input de texto -->
<footer class="chat-footer">
<div class="input-container">
<!-- Barra de Modos de Prompt Rápidos -->
<div class="prompt-modes-bar">
<button type="button" class="btn-prompt-mode mode-music" data-mode="AUDIO" title="Compor e gerar uma música pedagógica">
<span class="mode-icon">🎵</span> Criar Música
</button>
<button type="button" class="btn-prompt-mode mode-video" data-mode="VIDEO" title="Criar um roteiro ou animação de vídeo">
<span class="mode-icon">🎬</span> Criar Vídeo
</button>
<button type="button" class="btn-prompt-mode mode-image" data-mode="IMAGE" title="Gerar ou desenhar uma ilustração educativa">
<span class="mode-icon">🎨</span> Criar Imagem
</button>
<button type="button" class="btn-prompt-mode mode-text" data-mode="TEXT" title="Escrever histórias, relatórios ou conversar">
<span class="mode-icon">✍️</span> Criar História / Chat
</button>
</div>
<form id="chatForm">
<div class="input-box">
<button type="button" id="btnAttach" class="btn-attach" aria-label="Anexar arquivo" title="Anexar imagem ou documento">
+89
View File
@@ -2103,3 +2103,92 @@ code {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ==========================================================================
BARRA DE MODOS DE PROMPT RÁPIDOS (RODAPÉ DO INPUT)
========================================================================== */
.prompt-modes-bar {
display: flex;
gap: 8px;
margin-bottom: 8px;
overflow-x: auto;
padding: 4px 2px;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
.prompt-modes-bar::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
.btn-prompt-mode {
display: flex;
align-items: center;
gap: 6px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-light);
border-radius: 20px;
padding: 6px 14px;
color: var(--text-secondary);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast) cubic-bezier(0.4, 0, 0.2, 1);
white-space: nowrap;
font-family: var(--font-sans);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.btn-prompt-mode:hover {
transform: translateY(-1.5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background-color: var(--bg-tertiary);
}
.btn-prompt-mode:active {
transform: translateY(0);
}
.btn-prompt-mode .mode-icon {
font-size: 14px;
}
/* Modo Música: Estilos e Cores (Roxo/Violeta) */
.btn-prompt-mode.mode-music {
border-color: rgba(168, 85, 247, 0.3);
}
.btn-prompt-mode.mode-music:hover {
border-color: rgb(168, 85, 247);
background-color: rgba(168, 85, 247, 0.06);
color: rgb(216, 180, 254);
}
/* Modo Vídeo: Estilos e Cores (Azul/Ciano) */
.btn-prompt-mode.mode-video {
border-color: rgba(6, 182, 212, 0.3);
}
.btn-prompt-mode.mode-video:hover {
border-color: rgb(6, 182, 212);
background-color: rgba(6, 182, 212, 0.06);
color: rgb(165, 243, 252);
}
/* Modo Imagem: Estilos e Cores (Laranja/Rosa) */
.btn-prompt-mode.mode-image {
border-color: rgba(249, 115, 22, 0.3);
}
.btn-prompt-mode.mode-image:hover {
border-color: rgb(249, 115, 22);
background-color: rgba(249, 115, 22, 0.06);
color: rgb(253, 186, 116);
}
/* Modo Texto/História: Estilos e Cores (Verde Kemily) */
.btn-prompt-mode.mode-text {
border-color: rgba(16, 163, 127, 0.3);
}
.btn-prompt-mode.mode-text:hover {
border-color: var(--brand-green);
background-color: rgba(16, 163, 127, 0.06);
color: rgb(167, 243, 208);
}
+34 -4
View File
@@ -255,7 +255,7 @@ Responda SEMPRE em português do Brasil.`
// Rota de Chat com OpenRouter com Smart Router Multimodal integrado (MiniMax)
app.post('/api/chat', requireAuth, async (req, res) => {
const { messages } = req.body;
const { messages, forcedIntent } = req.body;
if (!messages || !Array.isArray(messages)) {
return res.status(400).json({ error: 'Mensagens inválidas' });
@@ -267,10 +267,40 @@ app.post('/api/chat', requireAuth, async (req, res) => {
// 1. Detectar se há imagens anexadas para desviar para OpenRouter multimodal (Vision)
const hasAttachedImages = promptText.includes('*[Imagem anexada:');
let intent = 'TEXT';
let intent = forcedIntent || 'TEXT';
// Executar classificação apenas se houver prompt de texto e não houver imagens anexadas
if (!hasAttachedImages && promptText && (process.env.OPENROUTER_API_KEY || process.env.GROQ_API_KEY)) {
// 1.1 Validações cruzadas de coerência com a intenção forçada
if (forcedIntent && promptText) {
if (forcedIntent === 'IMAGE' && /música|musica|cantiga|canção|cancao|trilha|áudio|audio|ouvir|som|melodia/i.test(promptText)) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.write(`data: ${JSON.stringify({ content: "Ops! Você digitou uma solicitação de música/áudio, mas clicou no botão **Criar Imagem** (🎨). Para compor uma música, por favor, digite sua solicitação e clique no botão **Criar Música** (🎵)!" })}\n\n`);
res.write('data: [DONE]\n\n');
return res.end();
}
if (forcedIntent === 'AUDIO' && /imagem|desenhe|foto|ilustração|ilustracao|ilustre|desenho|figura|vídeo|video|animação|animacao/i.test(promptText)) {
const isVideo = /vídeo|video|animação|animacao/i.test(promptText);
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.write(`data: ${JSON.stringify({ content: `Ops! Você digitou uma solicitação de ${isVideo ? 'vídeo' : 'imagem'}, mas clicou no botão **Criar Música** (🎵). Para gerar um ${isVideo ? 'vídeo' : 'desenho/imagem'}, por favor, clique no botão correspondente (**${isVideo ? 'Criar Vídeo 🎬' : 'Criar Imagem 🎨'}**)!` })}\n\n`);
res.write('data: [DONE]\n\n');
return res.end();
}
if (forcedIntent === 'VIDEO' && /música|musica|cantiga|canção|cancao|trilha|áudio|audio|ouvir|som|melodia|imagem|desenhe|foto|ilustração/i.test(promptText)) {
const isMusic = /música|musica|cantiga|canção|cancao/i.test(promptText);
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.write(`data: ${JSON.stringify({ content: `Ops! Você digitou uma solicitação de ${isMusic ? 'música' : 'imagem'}, mas clicou no botão **Criar Vídeo** (🎬). Para realizar essa tarefa, por favor, clique no botão **${isMusic ? 'Criar Música 🎵' : 'Criar Imagem 🎨'}**!` })}\n\n`);
res.write('data: [DONE]\n\n');
return res.end();
}
}
// Executar classificação automática apenas se não houver intenção forçada, não houver imagens anexadas e houver prompt
if (!forcedIntent && !hasAttachedImages && promptText && (process.env.OPENROUTER_API_KEY || process.env.GROQ_API_KEY)) {
try {
const systemDetector = "Você é um classificador de intenções de prompt. Classifique a última mensagem do usuário em uma destas quatro categorias exatas: 'TEXT' (para conversas normais, perguntas, redação de relatórios, planejamentos, textos pedagógicos, respostas gerais), 'IMAGE' (se o usuário estiver pedindo explicitamente para gerar, desenhar, criar ou ilustrar uma imagem, figura, ilustração ou foto), 'AUDIO' (se o usuário estiver pedindo explicitamente para gerar uma música, melodia, trilha sonora, cantiga ou som), ou 'VIDEO' (se o usuário estiver pedindo explicitamente para gerar ou criar um vídeo ou animação). Responda APENAS com a palavra da categoria em letras maiúsculas: TEXT, IMAGE, AUDIO, VIDEO.";