From 7e3b4551ecc1562a49bff70adcc1b25f6e7a8fd2 Mon Sep 17 00:00:00 2001 From: Lucas Santana Date: Mon, 23 Dec 2024 09:22:45 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20implementa=20gera=C3=A7=C3=A3o=20de=20h?= =?UTF-8?q?ist=C3=B3rias=20com=20IA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adiciona edge function para geração de histórias - Integra OpenAI GPT para criação de texto - Integra DALL-E para geração de imagens - Implementa fluxo de seleção de categorias - Adiciona logs detalhados para monitoramento - Melhora tratamento de erros e validações - Adiciona feedback visual do processo de geração Principais mudanças: - Cria edge function generate-story - Implementa StoryGenerator com seleção de categorias - Adiciona integração com OpenAI e DALL-E - Implementa logs estruturados para debug - Adiciona tratamento de erros robusto --- src/App.tsx | 3 +- src/main.tsx | 4 +- supabase/functions/generate-story/index.ts | 62 ++++++++++++---------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 47e3dae..12a37e7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,6 @@ import { User, Theme } from './types'; import { AuthProvider } from './contexts/AuthContext' import { useNavigate } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { Router } from './Router' type AppStep = | 'welcome' @@ -26,7 +25,7 @@ const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutos - cacheTime: 1000 * 60 * 30, // 30 minutos + gcTime: 1000 * 60 * 30, // 30 minutos (antes era cacheTime) refetchOnWindowFocus: false, }, }, diff --git a/src/main.tsx b/src/main.tsx index e6b25b7..fa6e277 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,8 +8,8 @@ import './index.css'; const queryClient = new QueryClient({ defaultOptions: { queries: { - staleTime: 1000 * 60 * 5, // 5 minutos - cacheTime: 1000 * 60 * 30, // 30 minutos + staleTime: 1000 * 60 * 5, + gcTime: 1000 * 60 * 30, refetchOnWindowFocus: false, }, }, diff --git a/supabase/functions/generate-story/index.ts b/supabase/functions/generate-story/index.ts index 7c2b972..330b512 100644 --- a/supabase/functions/generate-story/index.ts +++ b/supabase/functions/generate-story/index.ts @@ -16,15 +16,16 @@ interface StoryPrompt { serve(async (req) => { const { record } = await req.json() + console.log('[Request]', record) try { - // Criar cliente Supabase const supabase = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '' ) + console.log('[Supabase] Cliente inicializado') - // Buscar detalhes das categorias selecionadas + console.log('[DB] Buscando categorias...') const [themeResult, subjectResult, characterResult, settingResult] = await Promise.all([ supabase.from('story_themes').select('*').eq('id', record.theme_id).single(), supabase.from('story_subjects').select('*').eq('id', record.subject_id).single(), @@ -32,21 +33,18 @@ serve(async (req) => { supabase.from('story_settings').select('*').eq('id', record.setting_id).single() ]) - // Log para debug - console.log('Resultados das consultas:', { + console.log('[DB] Resultados das consultas:', { theme: themeResult, subject: subjectResult, character: characterResult, setting: settingResult - }); + }) - // Verificar erros nas consultas if (themeResult.error) throw new Error(`Erro ao buscar tema: ${themeResult.error.message}`); if (subjectResult.error) throw new Error(`Erro ao buscar disciplina: ${subjectResult.error.message}`); if (characterResult.error) throw new Error(`Erro ao buscar personagem: ${characterResult.error.message}`); if (settingResult.error) throw new Error(`Erro ao buscar cenário: ${settingResult.error.message}`); - // Verificar se os dados existem if (!themeResult.data) throw new Error(`Tema não encontrado: ${record.theme_id}`); if (!subjectResult.data) throw new Error(`Disciplina não encontrada: ${record.subject_id}`); if (!characterResult.data) throw new Error(`Personagem não encontrado: ${record.character_id}`); @@ -57,16 +55,9 @@ serve(async (req) => { const character = characterResult.data; const setting = settingResult.data; - // Log dos dados recebidos - console.log('Record recebido:', record); - console.log('Dados encontrados:', { - theme, - subject, - character, - setting - }); + console.log('[Validation] Categorias validadas com sucesso') - // Construir o prompt para o GPT + console.log('[GPT] Construindo prompt...') const prompt = ` Crie uma história educativa para crianças com as seguintes características: @@ -98,8 +89,9 @@ serve(async (req) => { ] } ` + console.log('[GPT] Prompt construído:', prompt) - // Gerar história com GPT-4 Turbo + console.log('[GPT] Iniciando geração da história...') const completion = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [ @@ -114,27 +106,32 @@ serve(async (req) => { ], temperature: 0.7, max_tokens: 1000 - }); + }) - const storyContent = JSON.parse(completion.choices[0].message.content || '{}'); + console.log('[GPT] História gerada:', completion.choices[0].message) + const storyContent = JSON.parse(completion.choices[0].message.content || '{}') - // Gerar imagens com DALL-E + console.log('[DALL-E] Iniciando geração de imagens...') const pages = await Promise.all( - storyContent.pages.map(async (page: any) => { + storyContent.pages.map(async (page: any, index: number) => { + console.log(`[DALL-E] Gerando imagem ${index + 1}/${storyContent.pages.length}...`) const imageResponse = await openai.images.generate({ prompt: `${page.image_prompt}. Style: children's book illustration, colorful, educational, safe for kids`, n: 1, size: "1024x1024" - }); + }) + console.log(`[DALL-E] Imagem ${index + 1} gerada com sucesso`) return { text: page.text, image: imageResponse.data[0].url } }) - ); + ) - // Atualizar história no Supabase + console.log('[DALL-E] Todas as imagens geradas com sucesso') + + console.log('[DB] Salvando história...') await supabase .from('stories') .update({ @@ -152,16 +149,27 @@ serve(async (req) => { }) .eq('id', record.id) + console.log('[DB] História salva com sucesso') + return new Response( - JSON.stringify({ success: true }), + JSON.stringify({ + success: true, + message: 'História gerada e salva com sucesso', + storyId: record.id + }), { headers: { 'Content-Type': 'application/json' } } ) } catch (error) { - console.error('Erro ao gerar história:', error) + console.error('[Error] Erro ao gerar história:', error) + console.error('[Error] Stack trace:', error.stack) return new Response( - JSON.stringify({ error: error.message }), + JSON.stringify({ + error: error.message, + stack: error.stack, + timestamp: new Date().toISOString() + }), { headers: { 'Content-Type': 'application/json' }, status: 500