From 68ad4907dac7fc5320b949b241c1427a5be7721d Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Fri, 5 Jun 2026 12:38:42 +0000 Subject: [PATCH] Ajustes de UI: barra de progresso da IA e toggle de ferramentas no mobile --- public/app.js | 143 ++++++++++++++++++++++++++++++++++++++++++---- public/index.html | 6 ++ public/style.css | 114 ++++++++++++++++++++++++++++++++++++ 3 files changed, 251 insertions(+), 12 deletions(-) diff --git a/public/app.js b/public/app.js index fa8d52f..23befd2 100644 --- a/public/app.js +++ b/public/app.js @@ -1087,6 +1087,17 @@ const initApp = () => { fileInput.click(); }); + // Botão alternador de ferramentas (recursos) + const btnToggleTools = document.getElementById('btnToggleTools'); + const promptModesBar = document.querySelector('.prompt-modes-bar'); + + if (btnToggleTools && promptModesBar) { + btnToggleTools.addEventListener('click', () => { + promptModesBar.classList.toggle('show'); + btnToggleTools.classList.toggle('active'); + }); + } + // Quando arquivos são selecionados fileInput.addEventListener('change', () => { const files = Array.from(fileInput.files); @@ -1780,12 +1791,50 @@ const initApp = () => { if (lastUserMsg && lastUserMsg.role === 'user') { handleSendMessage(lastUserMsg.content, true); } - }; - const scrollToBottom = () => { messagesContainer.scrollTop = messagesContainer.scrollHeight; }; + const getProgressSteps = (promptText) => { + const promptLower = (promptText || '').toLowerCase(); + + if (promptLower.includes('historia') || promptLower.includes('conto') || promptLower.includes('história') || promptLower.includes('escrever')) { + return [ + { text: "Analisando a solicitação da professora...", progress: 15 }, + { text: "Esboçando o enredo e personagens infantis...", progress: 40 }, + { text: "Elaborando a história pedagógica...", progress: 70 }, + { text: "Fazendo sugestões para uso pedagógico...", progress: 90 } + ]; + } + + if (promptLower.includes('relatorio') || promptLower.includes('relatório') || promptLower.includes('observa') || promptLower.includes('htpc')) { + return [ + { text: "Analisando as observações pedagógicas...", progress: 15 }, + { text: "Sintetizando os avanços e interações...", progress: 40 }, + { text: "Estruturando os blocos do relatório individual...", progress: 65 }, + { text: "Refinando o texto e alinhando com a BNCC...", progress: 85 }, + { text: "Ajustando o tom formal-pedagógico...", progress: 95 } + ]; + } + + if (promptLower.includes('planejamento') || promptLower.includes('aula') || promptLower.includes('atividade') || promptLower.includes('projeto') || promptLower.includes('brincar')) { + return [ + { text: "Revisando os campos de experiência da BNCC...", progress: 20 }, + { text: "Estruturando os momentos da vivência lúdica...", progress: 45 }, + { text: "Definindo a intencionalidade educativa e mediação...", progress: 70 }, + { text: "Finalizando as comandas e recursos pedagógicos...", progress: 90 } + ]; + } + + // Caso geral + return [ + { text: "Analisando a solicitação...", progress: 20 }, + { text: "Processando as diretrizes pedagógicas...", progress: 45 }, + { text: "Elaborando a resposta...", progress: 75 }, + { text: "Refinando a formatação e estilo...", progress: 90 } + ]; + }; + // Função principal de envio de mensagens const handleSendMessage = async (text, skipUserAppend = false, files = null, forcedIntent = null) => { if (!text && (!files || files.length === 0)) return; @@ -1886,6 +1935,46 @@ const initApp = () => { const botContentDiv = botRow.querySelector('.message-content'); let assistantContent = ''; + // Obter etapas de progresso personalizadas + const progressSteps = getProgressSteps(fullText); + let currentStepIdx = 0; + + // Renderizar barra de progresso inicial + botContentDiv.innerHTML = ` +
+
+
+
${progressSteps[0].text}
+
+
+
+
+
+ `; + scrollToBottom(); + + // Fechar barra de prompt no mobile se estiver aberta para liberar espaço + const mobilePromptBar = document.querySelector('.prompt-modes-bar'); + const mobileToggleBtn = document.getElementById('btnToggleTools'); + if (mobilePromptBar && window.innerWidth <= 768) { + mobilePromptBar.classList.remove('show'); + if (mobileToggleBtn) mobileToggleBtn.classList.remove('active'); + } + + // Iniciar timer para avançar as etapas + let progressTimer = setInterval(() => { + if (currentStepIdx < progressSteps.length - 1) { + currentStepIdx++; + const step = progressSteps[currentStepIdx]; + const stepTextEl = botContentDiv.querySelector('#iaProgressStepText'); + const barFillEl = botContentDiv.querySelector('#iaProgressBarFill'); + if (stepTextEl && barFillEl) { + stepTextEl.textContent = step.text; + barFillEl.style.width = `${step.progress}%`; + } + } + }, 1500); + try { // Filtrar mensagens para enviar apenas o histórico necessário ao OpenRouter, removendo dados base64 pesados de mídias const messagesPayload = chat.messages.map(m => { @@ -1939,17 +2028,13 @@ const initApp = () => { if (parsed.content) { assistantContent += parsed.content; - // Renderizar markdown em progresso (com o cursor no final) - botContentDiv.innerHTML = marked.parse(assistantContent) + ''; - - // Colorir códigos em tempo real - botContentDiv.querySelectorAll('pre code').forEach((block) => { - hljs.highlightElement(block); - }); - - scrollToBottom(); + // Mantemos o progresso visível sem renderizar o texto parcial } else if (parsed.type === 'media_status') { isMediaResponse = true; + if (progressTimer) { + clearInterval(progressTimer); + progressTimer = null; + } botContentDiv.innerHTML = `
@@ -1959,6 +2044,10 @@ const initApp = () => { scrollToBottom(); } else if (parsed.type === 'image') { isMediaResponse = true; + if (progressTimer) { + clearInterval(progressTimer); + progressTimer = null; + } mediaMsgData = { type: 'image', url: parsed.url, originalPrompt: parsed.originalPrompt }; assistantContent = `
@@ -1980,6 +2069,10 @@ const initApp = () => { scrollToBottom(); } else if (parsed.type === 'audio') { isMediaResponse = true; + if (progressTimer) { + clearInterval(progressTimer); + progressTimer = null; + } mediaMsgData = { type: 'audio', url: parsed.url, originalPrompt: parsed.originalPrompt }; const audioId = 'audio_' + Date.now(); assistantContent = ` @@ -2016,6 +2109,10 @@ const initApp = () => { scrollToBottom(); } else if (parsed.type === 'video') { isMediaResponse = true; + if (progressTimer) { + clearInterval(progressTimer); + progressTimer = null; + } mediaMsgData = { type: 'video', url: parsed.url, originalPrompt: parsed.originalPrompt }; assistantContent = `
@@ -2047,8 +2144,22 @@ const initApp = () => { const cursor = botContentDiv.querySelector('.typing-cursor'); if (cursor) cursor.remove(); + if (progressTimer) { + clearInterval(progressTimer); + progressTimer = null; + } + // Parse final limpo do markdown (se for texto normal) if (!isMediaResponse) { + const stepTextEl = botContentDiv.querySelector('#iaProgressStepText'); + const barFillEl = botContentDiv.querySelector('#iaProgressBarFill'); + if (stepTextEl && barFillEl) { + stepTextEl.textContent = "Concluído com sucesso!"; + barFillEl.style.width = "100%"; + } + // Aguardar um instante para o feedback visual de conclusão ser percebido + await new Promise(resolve => setTimeout(resolve, 400)); + botContentDiv.innerHTML = marked.parse(assistantContent); botContentDiv.querySelectorAll('pre code').forEach((block) => { hljs.highlightElement(block); @@ -2127,10 +2238,14 @@ const initApp = () => { } catch (error) { console.error('Erro na resposta do chat:', error); + if (progressTimer) { + clearInterval(progressTimer); + progressTimer = null; + } const cursor = botContentDiv.querySelector('.typing-cursor'); if (cursor) cursor.remove(); - botContentDiv.innerHTML += ` + botContentDiv.innerHTML = `

@@ -2157,6 +2272,10 @@ const initApp = () => { // Botões de Modos Rápidos de Prompt (Rodapé) document.querySelectorAll('.btn-prompt-mode').forEach(btn => { btn.addEventListener('click', () => { + if (window.innerWidth <= 768) { + if (promptModesBar) promptModesBar.classList.remove('show'); + if (btnToggleTools) btnToggleTools.classList.remove('active'); + } if (btn.dataset.prompt) { handleSendMessage(btn.dataset.prompt); return; diff --git a/public/index.html b/public/index.html index 3d568a2..0db6f22 100644 --- a/public/index.html +++ b/public/index.html @@ -255,6 +255,12 @@ +