diff --git a/public/app.js b/public/app.js index 98a680e..da1bd76 100644 --- a/public/app.js +++ b/public/app.js @@ -2042,7 +2042,7 @@ const initApp = () => { } // Atualizar timer - const updateTimer = () => { + function updateTimer() { if (!recordingStartTime) return; const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000); const mins = Math.floor(elapsed / 60).toString().padStart(2, '0'); @@ -2088,7 +2088,7 @@ const initApp = () => { } // Mostrar prévia do relatório - const showReportPreview = (data) => { + function showReportPreview(data) { recordStateProcessing.style.display = 'none'; recordStatePreview.style.display = 'flex'; @@ -2166,7 +2166,7 @@ const initApp = () => { } // Fechar modal - const closeRecordModal = () => { + function closeRecordModal() { if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); mediaRecorder.stream.getTracks().forEach(t => t.stop()); diff --git a/server.js b/server.js index 5a91c9f..11582ce 100644 --- a/server.js +++ b/server.js @@ -230,28 +230,56 @@ app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio' // Salva arquivo temporário a partir do buffer fs.writeFileSync(tempPath, req.file.buffer); - // Transcreve via OpenAI Whisper - const whisperFormData = new FormData(); - whisperFormData.append('file', fs.createReadStream(tempPath)); - whisperFormData.append('model', 'whisper-1'); - whisperFormData.append('language', 'pt'); - whisperFormData.append('response_format', 'text'); + let transcribedText = ''; - const whisperRes = await fetch('https://api.openai.com/v1/audio/transcriptions', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - }, - body: whisperFormData - }); + if (process.env.OPENAI_API_KEY) { + console.log('[Smart Transcription] Usando OpenAI Whisper...'); + const whisperFormData = new FormData(); + whisperFormData.append('file', fs.createReadStream(tempPath)); + whisperFormData.append('model', 'whisper-1'); + whisperFormData.append('language', 'pt'); + whisperFormData.append('response_format', 'text'); - if (!whisperRes.ok) { - const errText = await whisperRes.text(); - console.error('Whisper error:', errText); - throw new Error('Erro na transcrição'); + const whisperRes = await fetch('https://api.openai.com/v1/audio/transcriptions', { + method: 'POST', + headers: { + '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 if (!transcribedText || transcribedText.trim().length < 5) { @@ -286,26 +314,96 @@ ${transcribedText} Retorne APENAS o JSON válido:`; - 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 - }) - }); + let rawContent = ''; - if (!reportRes.ok) { - throw new Error('Erro ao gerar relatório'); + // Tentar OpenRouter + 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(); - const rawContent = reportData.choices?.[0]?.message?.content || ''; + // Fallback para Groq se OpenRouter falhar + 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 let parsedData = { criancas: null, turma: 'não informada', tags: [], relatorio: rawContent };