feat: implementa geração de histórias com IA

- 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
This commit is contained in:
Lucas Santana 2024-12-23 09:22:45 -03:00
parent 03732de610
commit 7e3b4551ec
3 changed files with 38 additions and 31 deletions

View File

@ -11,7 +11,6 @@ import { User, Theme } from './types';
import { AuthProvider } from './contexts/AuthContext' import { AuthProvider } from './contexts/AuthContext'
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Router } from './Router'
type AppStep = type AppStep =
| 'welcome' | 'welcome'
@ -26,7 +25,7 @@ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
staleTime: 1000 * 60 * 5, // 5 minutos staleTime: 1000 * 60 * 5, // 5 minutos
cacheTime: 1000 * 60 * 30, // 30 minutos gcTime: 1000 * 60 * 30, // 30 minutos (antes era cacheTime)
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}, },
}, },

View File

@ -8,8 +8,8 @@ import './index.css';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
staleTime: 1000 * 60 * 5, // 5 minutos staleTime: 1000 * 60 * 5,
cacheTime: 1000 * 60 * 30, // 30 minutos gcTime: 1000 * 60 * 30,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}, },
}, },

View File

@ -16,15 +16,16 @@ interface StoryPrompt {
serve(async (req) => { serve(async (req) => {
const { record } = await req.json() const { record } = await req.json()
console.log('[Request]', record)
try { try {
// Criar cliente Supabase
const supabase = createClient( const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '' 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([ const [themeResult, subjectResult, characterResult, settingResult] = await Promise.all([
supabase.from('story_themes').select('*').eq('id', record.theme_id).single(), supabase.from('story_themes').select('*').eq('id', record.theme_id).single(),
supabase.from('story_subjects').select('*').eq('id', record.subject_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() supabase.from('story_settings').select('*').eq('id', record.setting_id).single()
]) ])
// Log para debug console.log('[DB] Resultados das consultas:', {
console.log('Resultados das consultas:', {
theme: themeResult, theme: themeResult,
subject: subjectResult, subject: subjectResult,
character: characterResult, character: characterResult,
setting: settingResult setting: settingResult
}); })
// Verificar erros nas consultas
if (themeResult.error) throw new Error(`Erro ao buscar tema: ${themeResult.error.message}`); 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 (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 (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}`); 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 (!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 (!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}`); 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 character = characterResult.data;
const setting = settingResult.data; const setting = settingResult.data;
// Log dos dados recebidos console.log('[Validation] Categorias validadas com sucesso')
console.log('Record recebido:', record);
console.log('Dados encontrados:', {
theme,
subject,
character,
setting
});
// Construir o prompt para o GPT console.log('[GPT] Construindo prompt...')
const prompt = ` const prompt = `
Crie uma história educativa para crianças com as seguintes características: 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({ const completion = await openai.chat.completions.create({
model: "gpt-4o-mini", model: "gpt-4o-mini",
messages: [ messages: [
@ -114,27 +106,32 @@ serve(async (req) => {
], ],
temperature: 0.7, temperature: 0.7,
max_tokens: 1000 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( 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({ const imageResponse = await openai.images.generate({
prompt: `${page.image_prompt}. Style: children's book illustration, colorful, educational, safe for kids`, prompt: `${page.image_prompt}. Style: children's book illustration, colorful, educational, safe for kids`,
n: 1, n: 1,
size: "1024x1024" size: "1024x1024"
}); })
console.log(`[DALL-E] Imagem ${index + 1} gerada com sucesso`)
return { return {
text: page.text, text: page.text,
image: imageResponse.data[0].url 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 await supabase
.from('stories') .from('stories')
.update({ .update({
@ -152,16 +149,27 @@ serve(async (req) => {
}) })
.eq('id', record.id) .eq('id', record.id)
console.log('[DB] História salva com sucesso')
return new Response( 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' } } { headers: { 'Content-Type': 'application/json' } }
) )
} catch (error) { } 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( 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' }, headers: { 'Content-Type': 'application/json' },
status: 500 status: 500