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; feedback: { structure: string; content: string; language: string; }; strengths: string[]; improvements: string[]; suggestions: string; criteria_scores: { adequacy: number; // Adequação ao tema/gênero coherence: number; // Coerência textual cohesion: number; // Coesão vocabulary: number; // Vocabulário grammar: number; // Gramática/ortografia }; } 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 supabaseKey = Deno.env.get('SUPABASE_ANON_KEY') if (!supabaseUrl || !supabaseKey) { throw new Error('Variáveis de ambiente do Supabase não configuradas') } const supabaseClient = createClient(supabaseUrl, supabaseKey) // 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 "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 "suggestions": string, // Sugestões específicas "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/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", schema: { type: "object", required: ["overall_score", "feedback", "strengths", "improvements", "suggestions", "criteria_scores"], properties: { overall_score: { type: "number", minimum: 0, maximum: 100 }, feedback: { type: "object", required: ["structure", "content", "language"], properties: { structure: { type: "string" }, content: { type: "string" }, language: { type: "string" } } }, strengths: { type: "array", items: { type: "string" } }, improvements: { type: "array", items: { type: "string" } }, suggestions: { type: "string" }, criteria_scores: { type: "object", required: ["adequacy", "coherence", "cohesion", "vocabulary", "grammar"], properties: { adequacy: { type: "number" }, coherence: { type: "number" }, cohesion: { type: "number" }, vocabulary: { type: "number" }, grammar: { type: "number" } } } } } } }); 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...') const { error: saveError } = await supabaseClient .from('essay_analyses') .insert({ essay_id, overall_score: analysis.overall_score, feedback: analysis.feedback, strengths: analysis.strengths, improvements: analysis.improvements, suggestions: analysis.suggestions, criteria_scores: analysis.criteria_scores }) if (saveError) { console.error('Erro ao salvar análise:', saveError) return new Response( JSON.stringify({ error: 'Erro ao salvar análise', details: saveError.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' } } ) } })