diff --git a/src/types/story-generator.ts b/src/types/story-generator.ts index 7fa3b36..b441efe 100644 --- a/src/types/story-generator.ts +++ b/src/types/story-generator.ts @@ -23,6 +23,7 @@ export interface GeneratedStory { content: { pages: { text: string; + text_syllables: string; image?: string; }[]; }; diff --git a/supabase/functions/generate-story/index.ts b/supabase/functions/generate-story/index.ts index b64258e..dfbc1cd 100644 --- a/supabase/functions/generate-story/index.ts +++ b/supabase/functions/generate-story/index.ts @@ -44,6 +44,7 @@ interface StoryResponse { content: { pages: Array<{ text: string; + text_syllables: string; imagePrompt: string; keywords?: string[]; phonemes?: string[]; @@ -128,6 +129,7 @@ serve(async (req) => { console.log('[GPT] Prompt construído:', prompt); console.log('[GPT] Iniciando geração da história...'); + const completion = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [ @@ -136,15 +138,131 @@ serve(async (req) => { content: "Você é um contador de histórias infantis especializado em conteúdo educativo." }, { - role: "user", + role: "user", content: prompt } ], - temperature: 0.7, - max_tokens: 1000, - response_format: { type: "json_object" } + store: true, + response_format: { + type: "json_schema", + json_schema: { + name: "story", + schema: { + type: "object", + properties: { + title: { + type: "string", + description: "Título da história" + }, + content: { + type: "object", + properties: { + pages: { + type: "array", + items: { + type: "object", + properties: { + text: { + type: "string", + description: "Texto completo da página" + }, + text_syllables: { + type: "string", + description: "Texto separado em sílabas" + }, + imagePrompt: { + type: "string", + description: "Descrição para gerar imagem" + }, + keywords: { + type: "array", + items: { + type: "string" + } + }, + phonemes: { + type: "array", + items: { + type: "string" + } + }, + syllablePatterns: { + type: "array", + items: { + type: "string", + enum: ["CV", "CVC", "CCVC"] + } + } + }, + required: ["text", "text_syllables", "imagePrompt", "keywords", "phonemes", "syllablePatterns"], + additionalProperties: false + } + } + }, + required: ["pages"], + additionalProperties: false + }, + metadata: { + type: "object", + properties: { + targetAge: { + type: "number" + }, + difficulty: { + type: "string" + }, + exerciseWords: { + type: "object", + properties: { + pronunciation: { + type: "array", + items: { + type: "string" + } + }, + formation: { + type: "array", + items: { + type: "string" + } + }, + completion: { + type: "array", + items: { + type: "string" + } + } + }, + required: ["pronunciation", "formation", "completion"], + additionalProperties: false + } + }, + required: ["targetAge", "difficulty", "exerciseWords"], + additionalProperties: false + } + }, + required: ["title", "content", "metadata"], + additionalProperties: false + }, + strict: true + } + }, + temperature: 0.7 }); + if (completion.choices[0].finish_reason === "length") { + throw new Error("Resposta incompleta do modelo"); + } + + const story_response = completion.choices[0].message; + + if (story_response.refusal) { + console.log(story_response.refusal); + throw new Error("História recusada pelo modelo"); + } else if (!story_response.content) { + throw new Error("Sem conteúdo na resposta"); + } + console.log('[GPT] História gerada:', completion.choices[0].message); const storyContent = JSON.parse(completion.choices[0].message.content || '{}') as StoryResponse; @@ -236,6 +354,7 @@ serve(async (req) => { story_id: payload.story_id, page_number: index + 1, text: page.text, + text_syllables: page.text_syllables, image_url: page.image, image_path: page.image_path })) @@ -313,9 +432,6 @@ serve(async (req) => { ); } catch (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, @@ -364,16 +480,16 @@ function buildPrompt(base: StoryPrompt, voice?: string) { - ${selectedLanguage.instructions} - Linguagem clara e envolvente - 3-8 páginas de conteúdo - - Cada página deve ter um texto curto e sugestão para uma imagem + - Cada página deve ter um texto curto, o mesmo texto separado em sílabas e uma sugestão para uma imagem - Evitar conteúdo sensível ou inadequado - Incluir elementos de ${base.theme_id} - Ambientado em ${base.setting_id} - Personagem principal baseado em ${base.character_id} - - A resposta precisa ser em JSON + - Use a jornada no héroi para escrever as histórias. Requisitos específicos para exercícios: 1. Para o exercício de completar frases: - - Selecione 5-8 palavras importantes do texto + - Selecione 8-12 palavras importantes do texto - Escolha palavras que sejam substantivos, verbos ou adjetivos - Evite artigos, preposições ou palavras muito simples - As palavras devem ser relevantes para o contexto da história @@ -395,6 +511,7 @@ function buildPrompt(base: StoryPrompt, voice?: string) { "pages": [ { "text": "Texto da página com frases completas...", + "text_syllables": "Texto da página com frases completas separadas em sílabas em ${selectedLanguage.language}...", "imagePrompt": "Descrição para gerar imagem...", "keywords": ["palavra1", "palavra2"], "phonemes": ["fonema1", "fonema2"],