story-generator/src/pages/student-dashboard/CreateStoryPage.tsx
Lucas Santana fa8073dcee feat: adiciona suporte a múltiplos idiomas na geração de histórias
- 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
2025-02-01 09:38:59 -03:00

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>
);
}