Adicionado fluxo de revisão de transcrição de voz antes da análise de IA

This commit is contained in:
2026-05-27 23:59:45 +00:00
parent 3cd5a4479b
commit d405441dda
3 changed files with 168 additions and 56 deletions
+115 -28
View File
@@ -1993,6 +1993,14 @@ const initApp = () => {
const btnDiscardReport = document.getElementById('btnDiscardReport');
const btnSaveReport = document.getElementById('btnSaveReport');
// Novos elementos de transcrição
const recordStateTranscribed = document.getElementById('recordStateTranscribed');
const transcriptionTextarea = document.getElementById('transcriptionTextarea');
const btnDiscardTranscription = document.getElementById('btnDiscardTranscription');
const btnAnalyzeTranscription = document.getElementById('btnAnalyzeTranscription');
const recordProcessingText = document.getElementById('recordProcessingText');
const recordProcessingSubtext = document.getElementById('recordProcessingSubtext');
let mediaRecorder = null;
let audioChunks = [];
let recordingStartTime = null;
@@ -2002,6 +2010,40 @@ const initApp = () => {
let generatedTurma = 'não informada';
let generatedTags = [];
// Fechar modal - colocada no topo para evitar problemas de inicialização
function closeRecordModal() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
try {
mediaRecorder.stop();
} catch (e) {}
if (mediaRecorder.stream) {
mediaRecorder.stream.getTracks().forEach(t => t.stop());
}
}
clearInterval(timerInterval);
audioChunks = [];
generatedReport = null;
recordModal.style.display = 'none';
}
// Função para controlar a exibição dos estados
function showRecordState(state) {
recordStateRecording.style.display = 'none';
recordStateProcessing.style.display = 'none';
if (recordStateTranscribed) recordStateTranscribed.style.display = 'none';
recordStatePreview.style.display = 'none';
if (state === 'recording') {
recordStateRecording.style.display = 'flex';
} else if (state === 'processing') {
recordStateProcessing.style.display = 'flex';
} else if (state === 'transcribed') {
if (recordStateTranscribed) recordStateTranscribed.style.display = 'flex';
} else if (state === 'preview') {
recordStatePreview.style.display = 'flex';
}
}
// Abrir modal e iniciar gravação
if (fabRecord) {
fabRecord.addEventListener('click', async () => {
@@ -2009,9 +2051,7 @@ const initApp = () => {
generatedReport = null;
// Mostra estado inicial (gravando)
recordStateRecording.style.display = 'flex';
recordStateProcessing.style.display = 'none';
recordStatePreview.style.display = 'none';
showRecordState('recording');
recordModal.style.display = 'flex';
// Inicia timer
@@ -2056,20 +2096,25 @@ const initApp = () => {
const mins = Math.floor(elapsed / 60).toString().padStart(2, '0');
const secs = (elapsed % 60).toString().padStart(2, '0');
recordTimer.textContent = `${mins}:${secs}`;
};
}
// Parar gravação e processar
// Parar gravação e transcrever
if (btnStopRecord) {
btnStopRecord.addEventListener('click', async () => {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
mediaRecorder.stream.getTracks().forEach(t => t.stop());
try {
mediaRecorder.stop();
} catch (e) {}
if (mediaRecorder.stream) {
mediaRecorder.stream.getTracks().forEach(t => t.stop());
}
}
clearInterval(timerInterval);
// Mostra processando
recordStateRecording.style.display = 'none';
recordStateProcessing.style.display = 'flex';
// Mostra processando com texto de transcrição
if (recordProcessingText) recordProcessingText.textContent = 'Transcrevendo áudio...';
if (recordProcessingSubtext) recordProcessingSubtext.textContent = 'Aguarde a transcrição por Whisper';
showRecordState('processing');
// Envia áudio para servidor
const blob = new Blob(audioChunks, { type: 'audio/webm' });
@@ -2081,6 +2126,54 @@ const initApp = () => {
method: 'POST',
body: formData
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.error || 'Erro na transcrição');
}
const data = await res.json();
// Exibe o texto transcrito na área de texto para edição
if (transcriptionTextarea) {
transcriptionTextarea.value = data.transcribedText || '';
}
showRecordState('transcribed');
} catch (err) {
console.error('Erro ao processar observação:', err);
alert('Erro ao transcrever áudio: ' + err.message);
closeRecordModal();
}
});
}
// Ação de analisar transcrição
if (btnAnalyzeTranscription) {
btnAnalyzeTranscription.addEventListener('click', async () => {
const text = transcriptionTextarea ? transcriptionTextarea.value.trim() : '';
if (!text || text.length < 5) {
alert('Por favor, digite ou revise a transcrição (mínimo de 5 caracteres).');
return;
}
// Mostra processando com texto de análise
if (recordProcessingText) recordProcessingText.textContent = 'Analisando observação...';
if (recordProcessingSubtext) recordProcessingSubtext.textContent = 'Gerando relatório pedagógico estruturado';
showRecordState('processing');
try {
const res = await fetch('/api/observacao/analisar', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transcribedText: text })
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.error || 'Erro na análise');
}
const data = await res.json();
generatedReport = data.relatorio || data.report;
generatedCriancas = data.criancas;
@@ -2088,17 +2181,23 @@ const initApp = () => {
generatedTags = data.tags || [];
showReportPreview(data);
} catch (err) {
console.error('Erro ao processar observação:', err);
alert('Erro ao processar sua observação. Tente novamente.');
closeRecordModal();
console.error('Erro ao analisar observação:', err);
alert('Erro ao analisar observação: ' + err.message);
showRecordState('transcribed');
}
});
}
// Ação de descartar transcrição
if (btnDiscardTranscription) {
btnDiscardTranscription.addEventListener('click', () => {
closeRecordModal();
});
}
// Mostrar prévia do relatório
function showReportPreview(data) {
recordStateProcessing.style.display = 'none';
recordStatePreview.style.display = 'flex';
showRecordState('preview');
const now = new Date();
previewDateTime.textContent = now.toLocaleString('pt-BR', {
@@ -2123,7 +2222,7 @@ const initApp = () => {
${marked.parse(generatedReport || 'Relatório não disponível.')}
`;
reportContent.querySelectorAll('pre code').forEach(b => hljs.highlightElement(b));
};
}
// Descartar relatório
if (btnDiscardReport) {
@@ -2173,18 +2272,6 @@ const initApp = () => {
btnExitRecord.addEventListener('click', closeRecordModal);
}
// Fechar modal
function closeRecordModal() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
mediaRecorder.stream.getTracks().forEach(t => t.stop());
}
clearInterval(timerInterval);
audioChunks = [];
generatedReport = null;
recordModal.style.display = 'none';
};
// Fechar clicando fora
if (recordModal) {
recordModal.addEventListener('click', (e) => {