Ajustes de UI: barra de progresso da IA e toggle de ferramentas no mobile
This commit is contained in:
+131
-12
@@ -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 = `
|
||||
<div class="ia-progress-container" id="iaProgressContainer">
|
||||
<div class="ia-progress-header">
|
||||
<div class="ia-progress-icon"></div>
|
||||
<div class="ia-progress-step-text" id="iaProgressStepText">${progressSteps[0].text}</div>
|
||||
</div>
|
||||
<div class="ia-progress-bar">
|
||||
<div class="ia-progress-bar-fill" id="iaProgressBarFill" style="width: ${progressSteps[0].progress}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
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) + '<span class="typing-cursor"></span>';
|
||||
|
||||
// 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 = `
|
||||
<div class="media-loading">
|
||||
<div class="media-spinner"></div>
|
||||
@@ -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 = `
|
||||
<div class="media-message-card">
|
||||
@@ -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 = `
|
||||
<div class="media-message-card">
|
||||
@@ -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 = `
|
||||
<p style="color: var(--error); margin-top: 8px; font-size: 13.5px; display: flex; align-items: center; gap: 6px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 16px; height: 16px;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user