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
+33 -26
View File
@@ -342,7 +342,7 @@ async function initDatabase() {
}
}
// Rota: POST /api/observacao/transcrever — recebe áudio, transcreve via Whisper, gera relatório
// Rota: POST /api/observacao/transcrever — recebe áudio e transcreve via Whisper
app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio'), async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'Nenhum áudio recebido' });
@@ -355,18 +355,15 @@ app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio'
else if (req.file.mimetype.includes('wav')) ext = 'wav';
else if (req.file.mimetype.includes('mpeg')) ext = 'mp3';
}
const tempPath = `/tmp/obs_${Date.now()}.${ext}`;
try {
// Salva arquivo temporário a partir do buffer
fs.writeFileSync(tempPath, req.file.buffer);
let transcribedText = '';
const fileBlob = new Blob([req.file.buffer], { type: req.file.mimetype || 'audio/webm' });
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('file', fileBlob, `audio.${ext}`);
whisperFormData.append('model', 'whisper-1');
whisperFormData.append('language', 'pt');
whisperFormData.append('response_format', 'text');
@@ -386,9 +383,9 @@ app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio'
}
transcribedText = await whisperRes.text();
} else if (process.env.GROQ_API_KEY) {
console.log('[Smart Transcription] Usando Groq Whisper como fallback...');
console.log('[Smart Transcription] Usando Groq Whisper...');
const whisperFormData = new FormData();
whisperFormData.append('file', fs.createReadStream(tempPath));
whisperFormData.append('file', fileBlob, `audio.${ext}`);
whisperFormData.append('model', 'whisper-large-v3');
whisperFormData.append('language', 'pt');
whisperFormData.append('response_format', 'text');
@@ -411,34 +408,49 @@ app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio'
throw new Error('Nenhuma chave de API configurada para transcrição de áudio (OPENAI_API_KEY ou GROQ_API_KEY).');
}
fs.unlinkSync(tempPath); // Limpa tmp
if (!transcribedText || transcribedText.trim().length < 5) {
return res.json({ report: '⚠️ O áudio estava muito curto ou não pôde ser transcrito. Tente gravar por mais alguns segundos.' });
if (!transcribedText || transcribedText.trim().length < 2) {
return res.status(400).json({ error: 'O áudio não contém fala identificável.' });
}
// Gera relatório estruturado via GPT
// Envia transcrição + pedido de extrair: crianças, turma, tags, relatório
const reportPrompt = `Você é a Kemily, assistente pedagógica da professora Camila Martella Gasparini Reifonas.
res.json({ transcribedText });
Ela acabou de gravar uma observação em voz sobre seus alunos da educação infantil. Sua tarefa é ANALISAR a transcrição e retornar UM OBJETO JSON (sem markdown, sem código, apenas o JSON puro) com:
} catch (err) {
console.error('Erro transcrever:', err);
res.status(500).json({ error: err.message });
}
});
// Rota: POST /api/observacao/analisar — recebe texto transcrito e gera relatório pedagógico
app.post('/api/observacao/analisar', requireAuth, async (req, res) => {
const { transcribedText } = req.body;
if (!transcribedText || transcribedText.trim().length < 5) {
return res.status(400).json({ error: 'Texto muito curto ou inexistente para análise.' });
}
try {
const agentConfig = readAgentConfig();
const agentName = agentConfig.agentName || "Kemily";
const reportPrompt = `Você é a ${agentName}, assistente pedagógica da professora Camila Martella Gasparini Reifonas.
Ela acabou de registrar uma observação sobre seus alunos da educação infantil. Sua tarefa é ANALISAR o texto e retornar UM OBJETO JSON (sem markdown, sem código, apenas o JSON puro) com:
{
"criancas": ["lista de nomes de crianças mencionadas ou 'null' se não identificar nenhuma"],
"turma": "turma mencionada ou 'não informada'",
"tags": ["tag1", "tag2"] (social, cognitivo, motor, linguagem, emocional,轮的哪个),
"tags": ["tag1", "tag2"] (social, cognitivo, motor, linguagem, emocional, colaborativo, autónomo),
"relatorio": "...markdown estruturado do relatório pedagógico..."
}
REGRAS:
- Identifique crianças pelo nome próprio mencionado na fala
- Identifique crianças pelo nome próprio mencionado na fala/texto
- Se não conseguir identificar nomes, use "criancas": null
- Tags: escolha entre social, cognitivo, motor, linguagem, emocional, colaborativo, autónomo
- Relatório em português, linguagem formal-pedagógica, seções claras em **markdown**
- Foque em evidências observáveis
- Máximo 120 palavras no relatório
TRANSCRIÇÃO:
OBSERVAÇÃO REGISTRADA:
"""
${transcribedText}
"""
@@ -583,19 +595,14 @@ Retorne APENAS o JSON válido:`;
throw new Error('Nenhum JSON encontrado na resposta da IA');
}
} catch (parseErr) {
// Se não conseguir parsear JSON, retorna o texto como relatório
console.warn('Não foi possível parsear JSON da resposta da IA:', parseErr.message);
parsedData.relatorio = rawContent;
}
res.json({
transcribedText,
...parsedData
});
res.json(parsedData);
} catch (err) {
console.error('Erro transcrever/salvar:', err);
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
console.error('Erro ao analisar observação:', err);
res.status(500).json({ error: err.message });
}
});