mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-17 05:47:52 +00:00
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:
parent
03732de610
commit
7e3b4551ec
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user