fix: hoisting do closeRecordModal no frontend e fallback Groq/Gemini no Whisper/Relatório

This commit is contained in:
2026-05-27 17:26:57 +00:00
parent d6260747d1
commit 056d211494
2 changed files with 136 additions and 38 deletions
+3 -3
View File
@@ -2042,7 +2042,7 @@ const initApp = () => {
} }
// Atualizar timer // Atualizar timer
const updateTimer = () => { function updateTimer() {
if (!recordingStartTime) return; if (!recordingStartTime) return;
const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000); const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
const mins = Math.floor(elapsed / 60).toString().padStart(2, '0'); const mins = Math.floor(elapsed / 60).toString().padStart(2, '0');
@@ -2088,7 +2088,7 @@ const initApp = () => {
} }
// Mostrar prévia do relatório // Mostrar prévia do relatório
const showReportPreview = (data) => { function showReportPreview(data) {
recordStateProcessing.style.display = 'none'; recordStateProcessing.style.display = 'none';
recordStatePreview.style.display = 'flex'; recordStatePreview.style.display = 'flex';
@@ -2166,7 +2166,7 @@ const initApp = () => {
} }
// Fechar modal // Fechar modal
const closeRecordModal = () => { function closeRecordModal() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') { if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop(); mediaRecorder.stop();
mediaRecorder.stream.getTracks().forEach(t => t.stop()); mediaRecorder.stream.getTracks().forEach(t => t.stop());
+133 -35
View File
@@ -230,28 +230,56 @@ app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio'
// Salva arquivo temporário a partir do buffer // Salva arquivo temporário a partir do buffer
fs.writeFileSync(tempPath, req.file.buffer); fs.writeFileSync(tempPath, req.file.buffer);
// Transcreve via OpenAI Whisper let transcribedText = '';
const whisperFormData = new FormData();
whisperFormData.append('file', fs.createReadStream(tempPath));
whisperFormData.append('model', 'whisper-1');
whisperFormData.append('language', 'pt');
whisperFormData.append('response_format', 'text');
const whisperRes = await fetch('https://api.openai.com/v1/audio/transcriptions', { if (process.env.OPENAI_API_KEY) {
method: 'POST', console.log('[Smart Transcription] Usando OpenAI Whisper...');
headers: { const whisperFormData = new FormData();
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` whisperFormData.append('file', fs.createReadStream(tempPath));
}, whisperFormData.append('model', 'whisper-1');
body: whisperFormData whisperFormData.append('language', 'pt');
}); whisperFormData.append('response_format', 'text');
if (!whisperRes.ok) { const whisperRes = await fetch('https://api.openai.com/v1/audio/transcriptions', {
const errText = await whisperRes.text(); method: 'POST',
console.error('Whisper error:', errText); headers: {
throw new Error('Erro na transcrição'); 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
},
body: whisperFormData
});
if (!whisperRes.ok) {
const errText = await whisperRes.text();
console.error('OpenAI Whisper error:', errText);
throw new Error('Erro na transcrição via OpenAI');
}
transcribedText = await whisperRes.text();
} else if (process.env.GROQ_API_KEY) {
console.log('[Smart Transcription] Usando Groq Whisper como fallback...');
const whisperFormData = new FormData();
whisperFormData.append('file', fs.createReadStream(tempPath));
whisperFormData.append('model', 'whisper-large-v3');
whisperFormData.append('language', 'pt');
whisperFormData.append('response_format', 'text');
const whisperRes = await fetch('https://api.groq.com/openai/v1/audio/transcriptions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.GROQ_API_KEY}`
},
body: whisperFormData
});
if (!whisperRes.ok) {
const errText = await whisperRes.text();
console.error('Groq Whisper error:', errText);
throw new Error('Erro na transcrição via Groq');
}
transcribedText = await whisperRes.text();
} else {
throw new Error('Nenhuma chave de API configurada para transcrição de áudio (OPENAI_API_KEY ou GROQ_API_KEY).');
} }
const transcribedText = await whisperRes.text();
fs.unlinkSync(tempPath); // Limpa tmp fs.unlinkSync(tempPath); // Limpa tmp
if (!transcribedText || transcribedText.trim().length < 5) { if (!transcribedText || transcribedText.trim().length < 5) {
@@ -286,26 +314,96 @@ ${transcribedText}
Retorne APENAS o JSON válido:`; Retorne APENAS o JSON válido:`;
const reportRes = await fetch('https://openrouter.ai/api/v1/chat/completions', { let rawContent = '';
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
messages: [{ role: 'user', content: reportPrompt }],
temperature: 0.5,
max_tokens: 1500
})
});
if (!reportRes.ok) { // Tentar OpenRouter
throw new Error('Erro ao gerar relatório'); try {
console.log('[Smart Report] Tentando gerar relatório via OpenRouter...');
const reportRes = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
messages: [{ role: 'user', content: reportPrompt }],
temperature: 0.5,
max_tokens: 1500
})
});
if (reportRes.ok) {
const reportData = await reportRes.json();
rawContent = reportData.choices?.[0]?.message?.content || '';
} else {
console.warn('[Smart Report] Falha no OpenRouter:', await reportRes.text());
}
} catch (err) {
console.error('[Smart Report] Erro de conexão com OpenRouter:', err.message);
} }
const reportData = await reportRes.json(); // Fallback para Groq se OpenRouter falhar
const rawContent = reportData.choices?.[0]?.message?.content || ''; if (!rawContent && process.env.GROQ_API_KEY) {
try {
console.log('[Smart Report] Usando Groq como fallback para gerar relatório...');
const reportRes = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.GROQ_MODEL || 'llama-3.3-70b-versatile',
messages: [{ role: 'user', content: reportPrompt }],
temperature: 0.5,
max_tokens: 1500
})
});
if (reportRes.ok) {
const reportData = await reportRes.json();
rawContent = reportData.choices?.[0]?.message?.content || '';
} else {
console.warn('[Smart Report] Falha no Groq:', await reportRes.text());
}
} catch (err) {
console.error('[Smart Report] Erro de conexão com Groq:', err.message);
}
}
// Fallback para Google Gemini se OpenRouter e Groq falharem
if (!rawContent && process.env.GOOGLE_AI_API_KEY) {
try {
console.log('[Smart Report] Usando Google Gemini como fallback para gerar relatório...');
const reportRes = await fetch('https://generativelanguage.googleapis.com/v1beta/openai/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.GOOGLE_AI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash',
messages: [{ role: 'user', content: reportPrompt }],
temperature: 0.5,
max_tokens: 1500
})
});
if (reportRes.ok) {
const reportData = await reportRes.json();
rawContent = reportData.choices?.[0]?.message?.content || '';
} else {
console.warn('[Smart Report] Falha no Google Gemini:', await reportRes.text());
}
} catch (err) {
console.error('[Smart Report] Erro de conexão com Google Gemini:', err.message);
}
}
if (!rawContent) {
throw new Error('Falha ao processar o relatório pedagógico em todos os provedores de IA (OpenRouter, Groq e Google Gemini).');
}
// Parse JSON da resposta // Parse JSON da resposta
let parsedData = { criancas: null, turma: 'não informada', tags: [], relatorio: rawContent }; let parsedData = { criancas: null, turma: 'não informada', tags: [], relatorio: rawContent };