mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-16 21:37:51 +00:00
- Adiciona suporte para Português (Brasil), Inglês (EUA) e Espanhol (Espanha) - Implementa nova etapa de seleção de idioma no fluxo de criação - Adiciona instruções específicas por idioma no prompt da IA - Atualiza CHANGELOG.md para versão 1.3.0
230 lines
7.4 KiB
TypeScript
230 lines
7.4 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { ArrowLeft, Sparkles, Globe } from 'lucide-react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { StoryGenerator } from '../../components/story/StoryGenerator';
|
|
import { useSession } from '../../hooks/useSession';
|
|
import { TextCaseToggle } from '../../components/ui/text-case-toggle';
|
|
import { AdaptiveTitle, AdaptiveParagraph, AdaptiveText } from '../../components/ui/adaptive-text';
|
|
import { useUppercasePreference } from '../../hooks/useUppercasePreference';
|
|
import { useSpeechRecognition } from '@/features/voice-commands/hooks/useSpeechRecognition';
|
|
import { VoiceCommandButton } from '@/features/voice-commands/components/VoiceCommandButton';
|
|
import type { StoryChoices } from '@/components/story/StoryGenerator';
|
|
|
|
const LANGUAGE_OPTIONS = [
|
|
{ value: 'pt-BR', label: 'Português (Brasil)' },
|
|
{ value: 'en-US', label: 'Inglês (EUA)' },
|
|
{ value: 'es-ES', label: 'Espanhol (Espanha)' }
|
|
] as const;
|
|
|
|
export function CreateStoryPage() {
|
|
const navigate = useNavigate();
|
|
const { session } = useSession();
|
|
const [error, setError] = React.useState<string | null>(null);
|
|
const {
|
|
transcript: voiceTranscript,
|
|
start: startRecording,
|
|
stop: stopRecording,
|
|
status: recordingStatus,
|
|
error: voiceError,
|
|
isSupported: isVoiceSupported
|
|
} = useSpeechRecognition();
|
|
|
|
const { isUpperCase, toggleUppercase, isLoading } = useUppercasePreference(session?.user?.id);
|
|
|
|
const [inputMode, setInputMode] = useState<'voice' | 'form'>('form');
|
|
|
|
const [storyContext, setStoryContext] = useState('');
|
|
|
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
|
|
const [step, setStep] = useState(1);
|
|
|
|
const [choices, setChoices] = useState<StoryChoices>({
|
|
theme_id: null,
|
|
subject_id: null,
|
|
character_id: null,
|
|
setting_id: null,
|
|
context: '',
|
|
language_type: 'pt-BR'
|
|
});
|
|
|
|
// Manipuladores para gravação de voz
|
|
const handleStartRecording = () => {
|
|
setError(null);
|
|
startRecording();
|
|
};
|
|
|
|
const handleStopRecording = () => {
|
|
stopRecording();
|
|
};
|
|
|
|
// Atualizar status da interface baseado no status da gravação
|
|
useEffect(() => {
|
|
if (recordingStatus === 'recording') {
|
|
setInputMode('voice');
|
|
}
|
|
}, [recordingStatus]);
|
|
|
|
useEffect(() => {
|
|
if (inputMode === 'voice' && voiceTranscript) {
|
|
setStep(5);
|
|
setInputMode('voice');
|
|
}
|
|
}, [voiceTranscript, inputMode]);
|
|
|
|
if (!session) {
|
|
return (
|
|
<div className="text-center py-12">
|
|
<p className="text-gray-600">Você precisa estar logado para criar histórias.</p>
|
|
<button
|
|
onClick={() => navigate('/login')}
|
|
className="mt-4 text-purple-600 hover:text-purple-700"
|
|
>
|
|
Fazer login
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<button
|
|
onClick={() => navigate('/aluno/historias')}
|
|
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-6"
|
|
>
|
|
<ArrowLeft className="h-5 w-5" />
|
|
Voltar para histórias
|
|
</button>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center gap-3 mb-8">
|
|
<div className="p-2 bg-purple-100 rounded-lg">
|
|
<Sparkles className="h-6 w-6 text-purple-600" />
|
|
</div>
|
|
<div>
|
|
<AdaptiveTitle
|
|
text="Criar Nova História"
|
|
isUpperCase={isUpperCase}
|
|
className="text-2xl font-bold text-gray-900"
|
|
/>
|
|
<AdaptiveParagraph
|
|
text="Vamos criar uma história personalizada baseada nos seus interesses"
|
|
isUpperCase={isUpperCase}
|
|
className="text-gray-600"
|
|
/>
|
|
</div>
|
|
<TextCaseToggle
|
|
isUpperCase={isUpperCase}
|
|
onToggle={toggleUppercase}
|
|
isLoading={isLoading}
|
|
className="ml-auto"
|
|
/>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mb-6 p-4 bg-red-50 text-red-600 rounded-lg">
|
|
<AdaptiveText
|
|
text={error}
|
|
isUpperCase={isUpperCase}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{voiceError && (
|
|
<div className="mb-6 p-4 bg-red-50 text-red-600 rounded-lg">
|
|
<AdaptiveText
|
|
text={voiceError}
|
|
isUpperCase={isUpperCase}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-4">
|
|
{!isVoiceSupported && (
|
|
<div className="p-3 bg-yellow-50 text-yellow-700 rounded-lg mb-4">
|
|
Seu navegador não suporta gravação por voz
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-4 flex items-center gap-2">
|
|
<span className="text-sm font-medium">
|
|
Modo atual:
|
|
</span>
|
|
<span className="px-2 py-1 rounded-full text-xs bg-purple-100 text-purple-800">
|
|
{inputMode === 'voice' ? 'Voz' : 'Formulário'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="mb-8 space-y-4">
|
|
<div className="bg-purple-50 p-4 rounded-xl border border-purple-100">
|
|
<h3 className="text-sm font-medium text-purple-800 mb-3">
|
|
Descreva sua história por voz
|
|
</h3>
|
|
|
|
<VoiceCommandButton
|
|
onTranscriptUpdate={(transcript) => {
|
|
setInputMode('voice');
|
|
setStoryContext(transcript);
|
|
}}
|
|
onStart={handleStartRecording}
|
|
onStop={handleStopRecording}
|
|
disabled={isGenerating}
|
|
className="w-full justify-center py-3"
|
|
/>
|
|
|
|
{voiceTranscript && (
|
|
<div className="mt-4 p-3 bg-white rounded-lg border border-gray-200">
|
|
<p className="text-sm text-gray-700">{voiceTranscript}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex-1 h-px bg-gray-200" />
|
|
<span className="text-sm text-gray-500">ou</span>
|
|
<div className="flex-1 h-px bg-gray-200" />
|
|
</div>
|
|
</div>
|
|
|
|
<StoryGenerator
|
|
initialContext={inputMode === 'voice' ? voiceTranscript : storyContext}
|
|
onContextChange={(newContext) => {
|
|
if (inputMode === 'form') {
|
|
setStoryContext(newContext);
|
|
}
|
|
}}
|
|
inputMode={inputMode}
|
|
voiceTranscript={voiceTranscript || ''}
|
|
isGenerating={isGenerating}
|
|
setIsGenerating={setIsGenerating}
|
|
step={step}
|
|
setStep={setStep}
|
|
choices={choices}
|
|
setChoices={setChoices}
|
|
/>
|
|
|
|
<div className="mt-8 p-4 bg-purple-50 rounded-lg">
|
|
<h3 className="text-sm font-medium text-purple-900 mb-2">
|
|
Como funciona?
|
|
</h3>
|
|
<ol className="text-sm text-purple-700 space-y-2">
|
|
{[
|
|
'Conte-nos sobre seus interesses e preferências',
|
|
'Escolha personagens e cenários para sua história',
|
|
'Nossa IA criará uma história única e personalizada',
|
|
'Você poderá ler e praticar com sua nova história'
|
|
].map((text, index) => (
|
|
<AdaptiveText
|
|
key={index}
|
|
text={`${index + 1}. ${text}`}
|
|
isUpperCase={isUpperCase}
|
|
as="li"
|
|
/>
|
|
))}
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|