import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' import { getCorsHeaders } from '../_shared/cors.ts' import { OpenAI } from "https://deno.land/x/openai@v4.24.0/mod.ts"; interface EssayAnalysisRequest { essay_id: string; content: string; type_id: string; genre_id: string; } interface EssayAnalysisResponse { overall_score: number; suggestions: string; feedback: { structure: string; content: string; language: string; }; strengths: string[]; improvements: string[]; criteria_scores: { adequacy: number; coherence: number; cohesion: number; vocabulary: number; grammar: number; }; } interface EssayType { id: string; slug: string; title: string; description: string; } interface EssayGenre { id: string; type_id: string; slug: string; title: string; description: string; requirements: { min_words: number; max_words: number; required_elements: string[]; }; } serve(async (req) => { console.log(`[${new Date().toISOString()}] Nova requisição recebida`) // Handle CORS if (req.method === 'OPTIONS') { console.log('Requisição OPTIONS - CORS preflight') return new Response('ok', { headers: getCorsHeaders(req) }) } try { // Criar cliente Supabase console.log('Inicializando cliente Supabase...') const supabaseUrl = Deno.env.get('SUPABASE_URL') const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') if (!supabaseUrl || !supabaseServiceKey) { throw new Error('Variáveis de ambiente do Supabase não configuradas') } const supabaseClient = createClient(supabaseUrl, supabaseServiceKey, { auth: { persistSession: false, autoRefreshToken: false, } }) // Criar cliente OpenAI console.log('Inicializando cliente OpenAI...') const openaiKey = Deno.env.get('OPENAI_API_KEY') if (!openaiKey) { throw new Error('OPENAI_API_KEY não configurada') } const openai = new OpenAI({ apiKey: openaiKey, }); // Obter dados da requisição console.log('Obtendo dados da requisição...') const requestData = await req.json() console.log('Dados recebidos:', JSON.stringify(requestData, null, 2)) const { essay_id, content, type_id, genre_id }: EssayAnalysisRequest = requestData // Validar dados obrigatórios if (!essay_id || !content || !type_id || !genre_id) { console.error('Dados obrigatórios faltando:', { essay_id, content: !!content, type_id, genre_id }) return new Response( JSON.stringify({ error: 'Dados obrigatórios não fornecidos', details: { essay_id: !essay_id, content: !content, type_id: !type_id, genre_id: !genre_id } }), { status: 400, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Buscar informações do tipo e gênero console.log('Buscando informações do tipo e gênero...') const { data: typeData, error: typeError } = await supabaseClient .from('essay_types') .select('*') .eq('id', type_id) .single() if (typeError) { console.error('Erro ao buscar tipo:', typeError) } const { data: genreData, error: genreError } = await supabaseClient .from('essay_genres') .select('*') .eq('id', genre_id) .single() if (genreError) { console.error('Erro ao buscar gênero:', genreError) } if (typeError || genreError || !typeData || !genreData) { return new Response( JSON.stringify({ error: 'Tipo ou gênero não encontrado', details: { typeError: typeError?.message, genreError: genreError?.message } }), { status: 404, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } const essayType: EssayType = typeData const essayGenre: EssayGenre = genreData console.log('Tipo e gênero encontrados:', { type: essayType.title, genre: essayGenre.title }) // Construir prompt para a análise console.log('Construindo prompt para análise...') const prompt = `Você é um professor especialista em análise de textos. Analise a redação a seguir considerando que é do tipo "${essayType.title}" e gênero "${essayGenre.title}". Requisitos específicos do gênero: - Mínimo de palavras: ${essayGenre.requirements.min_words} - Máximo de palavras: ${essayGenre.requirements.max_words} - Elementos obrigatórios: ${essayGenre.requirements.required_elements.join(', ')} Texto para análise: ${content} Forneça uma análise detalhada considerando: 1. Adequação ao tipo e gênero textual 2. Coerência e coesão textual 3. Vocabulário e linguagem 4. Gramática e ortografia 5. Elementos obrigatórios do gênero 6. Pontos fortes 7. Pontos a melhorar 8. Sugestões específicas para aprimoramento Responda em formato JSON seguindo exatamente esta estrutura: { "overall_score": number, // 0 a 100 "suggestions": string, // Sugestões específicas "feedback": { "structure": string, // Feedback sobre estrutura e organização "content": string, // Feedback sobre conteúdo e ideias "language": string // Feedback sobre linguagem e gramática }, "strengths": string[], // Lista de pontos fortes "improvements": string[], // Lista de pontos a melhorar "criteria_scores": { "adequacy": number, // 0 a 100 - Adequação ao tema/gênero "coherence": number, // 0 a 100 - Coerência textual "cohesion": number, // 0 a 100 - Coesão "vocabulary": number, // 0 a 100 - Vocabulário "grammar": number // 0 a 100 - Gramática e ortografia } }` // Realizar análise com OpenAI console.log('Enviando requisição para OpenAI...') try { const completion = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [ { role: "system", content: "Você é um professor especialista em análise de textos, com vasta experiência em avaliação de redações." }, { role: "user", content: prompt } ], response_format: { type: "json_schema", json_schema: { type: "object", required: ["overall_score", "suggestions", "feedback", "strengths", "improvements", "criteria_scores"], properties: { overall_score: { type: "number", minimum: 0, maximum: 100, description: "Pontuação geral da redação (0-100)" }, suggestions: { type: "string", description: "Sugestões específicas para aprimoramento" }, feedback: { type: "object", required: ["structure", "content", "language"], properties: { structure: { type: "string", description: "Feedback sobre estrutura e organização" }, content: { type: "string", description: "Feedback sobre conteúdo e ideias" }, language: { type: "string", description: "Feedback sobre linguagem e gramática" } } }, strengths: { type: "array", items: { type: "string", description: "Ponto forte da redação" }, minItems: 1, description: "Lista de pontos fortes da redação" }, improvements: { type: "array", items: { type: "string", description: "Ponto a melhorar na redação" }, minItems: 1, description: "Lista de pontos a melhorar na redação" }, criteria_scores: { type: "object", required: ["adequacy", "coherence", "cohesion", "vocabulary", "grammar"], properties: { adequacy: { type: "number", minimum: 0, maximum: 100, description: "Adequação ao tema/gênero (0-100)" }, coherence: { type: "number", minimum: 0, maximum: 100, description: "Coerência textual (0-100)" }, cohesion: { type: "number", minimum: 0, maximum: 100, description: "Coesão textual (0-100)" }, vocabulary: { type: "number", minimum: 0, maximum: 100, description: "Vocabulário (0-100)" }, grammar: { type: "number", minimum: 0, maximum: 100, description: "Gramática e ortografia (0-100)" } } } } } } }); console.log('Resposta recebida da OpenAI') const analysis: EssayAnalysisResponse = JSON.parse(completion.choices[0].message.content) console.log('Análise gerada:', JSON.stringify(analysis, null, 2)) // Salvar análise no banco console.log('Salvando análise no banco...') // Primeiro, criar a análise principal const { data: analysisData, error: analysisError } = await supabaseClient .from('essay_analyses') .insert({ essay_id, overall_score: analysis.overall_score, suggestions: analysis.suggestions }) .select() .single() if (analysisError) { console.error('Erro ao salvar análise principal:', analysisError) return new Response( JSON.stringify({ error: 'Erro ao salvar análise', details: analysisError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar feedback const { error: feedbackError } = await supabaseClient .from('essay_analysis_feedback') .insert({ analysis_id: analysisData.id, structure_feedback: analysis.feedback.structure, content_feedback: analysis.feedback.content, language_feedback: analysis.feedback.language }) if (feedbackError) { console.error('Erro ao salvar feedback:', feedbackError) return new Response( JSON.stringify({ error: 'Erro ao salvar feedback', details: feedbackError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar pontos fortes const strengths = analysis.strengths.map(strength => ({ analysis_id: analysisData.id, strength })) const { error: strengthsError } = await supabaseClient .from('essay_analysis_strengths') .insert(strengths) if (strengthsError) { console.error('Erro ao salvar pontos fortes:', strengthsError) return new Response( JSON.stringify({ error: 'Erro ao salvar pontos fortes', details: strengthsError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar pontos a melhorar const improvements = analysis.improvements.map(improvement => ({ analysis_id: analysisData.id, improvement })) const { error: improvementsError } = await supabaseClient .from('essay_analysis_improvements') .insert(improvements) if (improvementsError) { console.error('Erro ao salvar pontos a melhorar:', improvementsError) return new Response( JSON.stringify({ error: 'Erro ao salvar pontos a melhorar', details: improvementsError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar notas por critério const { error: scoresError } = await supabaseClient .from('essay_analysis_scores') .insert({ analysis_id: analysisData.id, adequacy: analysis.criteria_scores.adequacy, coherence: analysis.criteria_scores.coherence, cohesion: analysis.criteria_scores.cohesion, vocabulary: analysis.criteria_scores.vocabulary, grammar: analysis.criteria_scores.grammar }) if (scoresError) { console.error('Erro ao salvar notas:', scoresError) return new Response( JSON.stringify({ error: 'Erro ao salvar notas', details: scoresError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Atualizar status da redação console.log('Atualizando status da redação...') const { error: updateError } = await supabaseClient .from('student_essays') .update({ status: 'analyzed' }) .eq('id', essay_id) if (updateError) { console.error('Erro ao atualizar status:', updateError) return new Response( JSON.stringify({ error: 'Erro ao atualizar status da redação', details: updateError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } console.log('Análise concluída com sucesso') // Retornar análise return new Response( JSON.stringify(analysis), { headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } catch (openaiError) { console.error('Erro na chamada da OpenAI:', openaiError) return new Response( JSON.stringify({ error: 'Erro ao gerar análise', details: openaiError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } } catch (error) { console.error('Erro geral na função:', error) return new Response( JSON.stringify({ error: 'Erro interno do servidor', details: error.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } })