diff --git a/docs/voice-features.md b/docs/voice-features.md new file mode 100644 index 0000000..daf956c --- /dev/null +++ b/docs/voice-features.md @@ -0,0 +1,25 @@ +## Geração por Voz + +### Como usar: +1. Clique no ícone de microfone +2. Fale sua descrição por 15-120 segundos +3. Confira a transcrição +4. Ajuste se necessário +5. Envie para gerar a história + +### Requisitos: +- Navegador moderno (Chrome, Edge, Safari 14+) +- Microfone habilitado +- Conexão estável + +## Segurança + +- Gravações temporárias são excluídas após 1h +- Transcrições são validadas contra conteúdo sensível +- Dados de áudio não são armazenados permanentemente + +## Limitações Conhecidas + +- Acento pode afetar precisão da transcrição +- Ruído ambiente pode interferir na qualidade +- Suporte limitado a sotaques regionais \ No newline at end of file diff --git a/src/components/story/StoryGenerator.tsx b/src/components/story/StoryGenerator.tsx index 1ab109a..f4d4b27 100644 --- a/src/components/story/StoryGenerator.tsx +++ b/src/components/story/StoryGenerator.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { supabase } from '../../lib/supabase'; import { useSession } from '../../hooks/useSession'; @@ -29,26 +29,35 @@ interface StoryChoices { context?: string; } -export function StoryGenerator() { - const navigate = useNavigate(); - const { session } = useSession(); - const { themes, subjects, characters, settings, isLoading } = useStoryCategories(); - const [step, setStep] = React.useState(1); - const [choices, setChoices] = React.useState({ - theme_id: null, - subject_id: null, - character_id: null, - setting_id: null, - context: '' - }); - const [isGenerating, setIsGenerating] = React.useState(false); - const [error, setError] = React.useState(null); - const [generationStatus, setGenerationStatus] = React.useState< - 'idle' | 'creating' | 'generating-images' | 'saving' - >('idle'); - const { trackStoryGenerated } = useStudentTracking(); - const startTime = React.useRef(Date.now()); +interface StoryGeneratorProps { + initialContext?: string; + onContextChange: (context: string) => void; + inputMode: 'voice' | 'text' | 'form'; + voiceTranscript: string; + isGenerating: boolean; + setIsGenerating: (value: boolean) => void; + step: number; + setStep: (step: number) => void; + choices: StoryChoices; + setChoices: React.Dispatch>; +} +export function StoryGenerator({ + initialContext = '', + onContextChange, + inputMode, + voiceTranscript, + isGenerating, + setIsGenerating, + step, + setStep, + choices, + setChoices +}: StoryGeneratorProps) { + // 1. Obter dados da API + const { themes, subjects, characters, settings, isLoading } = useStoryCategories(); + + // 2. Definir steps com os dados obtidos const steps: StoryStep[] = [ { title: 'Escolha o Tema', @@ -71,11 +80,45 @@ export function StoryGenerator() { key: 'setting_id' }, { - title: 'Adicione um Contexto (Opcional)', + title: 'Contexto da História (Opcional)', isContextStep: true } ]; + // 3. useEffect que depende dos dados + useEffect(() => { + if (inputMode === 'voice' && voiceTranscript && themes) { + setStep(steps.length); + setChoices(prev => ({ + ...prev, + theme_id: 'auto', + subject_id: 'auto', + character_id: 'auto', + setting_id: 'auto' + })); + } + }, [inputMode, voiceTranscript, steps.length, themes, setStep, setChoices]); + + useEffect(() => { + setChoices(prev => ({ + ...prev, + context: inputMode === 'voice' ? voiceTranscript : initialContext + })); + }, [voiceTranscript, initialContext, inputMode]); + + const handleContextChange = (e: React.ChangeEvent) => { + onContextChange(e.target.value); + }; + + const navigate = useNavigate(); + const { session } = useSession(); + const [error, setError] = React.useState(null); + const [generationStatus, setGenerationStatus] = React.useState< + 'idle' | 'creating' | 'generating-images' | 'saving' + >('idle'); + const { trackStoryGenerated } = useStudentTracking(); + const startTime = React.useRef(Date.now()); + const currentStep = steps[step - 1]; const isLastStep = step === steps.length; @@ -86,17 +129,16 @@ export function StoryGenerator() { } }; - const handleContextChange = (event: React.ChangeEvent) => { - setChoices(prev => ({ ...prev, context: event.target.value })); - }; - - const handleBack = () => { - if (step > 1) { - setStep(prev => prev - 1); - } - }; - const handleGenerate = async () => { + // Validação apenas para modo voz + if (inputMode === 'voice' && !voiceTranscript) { + setError('Grave uma descrição por voz antes de enviar'); + return; + } + + // Contexto é opcional no formulário + const finalContext = inputMode === 'voice' ? voiceTranscript : initialContext; + if (!session?.user?.id) return; if (!choices.theme_id || !choices.subject_id || !choices.character_id || !choices.setting_id) { @@ -118,7 +160,7 @@ export function StoryGenerator() { subject_id: choices.subject_id, character_id: choices.character_id, setting_id: choices.setting_id, - context: choices.context || null, + context: finalContext, status: 'draft', content: { prompt: choices, @@ -142,7 +184,7 @@ export function StoryGenerator() { subject: selectedSubject, character: selectedCharacter, setting: selectedSetting, - context: choices.context, + context: finalContext, generation_time: Date.now() - startTime.current, word_count: 0, // será atualizado após a geração student_id: session.user.id, @@ -240,10 +282,10 @@ export function StoryGenerator() { {currentStep.isContextStep ? (