Skip to content

Commit 9653ffa

Browse files
committed
teoricamente o quiz completo funciona
1 parent dae08c2 commit 9653ffa

File tree

11 files changed

+1019
-201
lines changed

11 files changed

+1019
-201
lines changed

frontend/src/app/(home)/create/page.tsx

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// /create/page.tsx
12
"use client";
23

34
import { useState } from "react";
@@ -6,7 +7,10 @@ import Questions from "./questions/questions";
67
import { useTheme } from "@/hook/useTheme";
78
import { useSubTopic } from "@/hook/useSubTopic";
89
import { useQuestion} from "@/hook/useQuestion";
10+
import { useQuiz } from "@/hook/useQuiz";
911
import SetQuiz from "./theme/set";
12+
import { AutomaticModeData } from "./questions/automatico/automatico";
13+
import { QuizPergunta } from "@/util/types/quiz";
1014

1115
interface ThemeData {
1216
id?: string;
@@ -38,11 +42,14 @@ export default function CreateQuiz() {
3842
const [themeData, setThemeData] = useState<ThemeData | null>(null);
3943
const [subtopicData, setSubtopicData] = useState<SubtopicData | null>(null);
4044
const [questionsData, setQuestionsData] = useState<QuestionData[]>([]);
45+
const [isAutomatic, setIsAutomatic] = useState(false);
46+
const [automaticData, setAutomaticData] = useState<AutomaticModeData | null>(null);
4147
const [saving, setSaving] = useState(false);
4248

4349
const { createTheme } = useTheme();
4450
const { createSubTopic } = useSubTopic();
4551
const { createQuestion } = useQuestion();
52+
const { createQuizFromText, createQuizFromAudio, createQuizFromDocument } = useQuiz();
4653

4754
const handleNext = () => {
4855
if (!themeData?.title) {
@@ -62,10 +69,37 @@ export default function CreateQuiz() {
6269
setCurrentStep("setup");
6370
};
6471

72+
const convertQuizToQuestions = (quizPerguntas: QuizPergunta[]): QuestionData[] => {
73+
return quizPerguntas.map((pergunta) => ({
74+
text: pergunta.pergunta,
75+
alternatives: pergunta.alternativas.map((alt) => ({
76+
text: alt.texto,
77+
correct: alt.correta,
78+
explanation: alt.explicacao,
79+
})),
80+
}));
81+
};
82+
6583
const handleFinish = async () => {
66-
if (questionsData.length === 0) {
67-
alert("Adicione pelo menos uma questão!");
68-
return;
84+
// Validação
85+
if (isAutomatic) {
86+
if (!automaticData) {
87+
alert("Preencha os dados para geração automática!");
88+
return;
89+
}
90+
if (automaticData.mode === "text" && !automaticData.text) {
91+
alert("Preencha o texto para geração!");
92+
return;
93+
}
94+
if ((automaticData.mode === "audio" || automaticData.mode === "document") && !automaticData.file) {
95+
alert("Selecione um arquivo para geração!");
96+
return;
97+
}
98+
} else {
99+
if (questionsData.length === 0) {
100+
alert("Adicione pelo menos uma questão!");
101+
return;
102+
}
69103
}
70104

71105
setSaving(true);
@@ -91,13 +125,58 @@ export default function CreateQuiz() {
91125
subTopicId = newSubTopic.id;
92126
}
93127

94-
// 3. Cria todas as questões
95-
for (const question of questionsData) {
96-
await createQuestion({
97-
text: question.text,
98-
sub_topic_id: subTopicId!,
99-
alternatives: question.alternatives,
100-
});
128+
// 3. Gera ou salva as questões
129+
if (isAutomatic && automaticData) {
130+
let generatedQuestions: QuestionData[] = [];
131+
let response;
132+
133+
if (automaticData.mode === "text" && automaticData.text) {
134+
response = await createQuizFromText({
135+
text: automaticData.text,
136+
num_questions: automaticData.num_questions,
137+
num_alternatives: automaticData.num_alternatives,
138+
theme_id: themeId,
139+
sub_topic_id: subTopicId
140+
});
141+
} else if (automaticData.mode === "audio" && automaticData.file) {
142+
response = await createQuizFromAudio({
143+
file: automaticData.file,
144+
num_questions: automaticData.num_questions,
145+
num_alternatives: automaticData.num_alternatives,
146+
theme_id: themeId,
147+
sub_topic_id: subTopicId
148+
});
149+
} else if (automaticData.mode === "document" && automaticData.file) {
150+
response = await createQuizFromDocument({
151+
file: automaticData.file,
152+
num_questions: automaticData.num_questions,
153+
num_alternatives: automaticData.num_alternatives,
154+
theme_id: themeId,
155+
sub_topic_id: subTopicId
156+
});
157+
}
158+
159+
if (response && response.perguntas) {
160+
generatedQuestions = convertQuizToQuestions(response.perguntas);
161+
162+
// Salva as questões geradas no banco
163+
for (const question of generatedQuestions) {
164+
await createQuestion({
165+
text: question.text,
166+
sub_topic_id: subTopicId!,
167+
alternatives: question.alternatives,
168+
});
169+
}
170+
}
171+
} else {
172+
// Salva as questões manuais
173+
for (const question of questionsData) {
174+
await createQuestion({
175+
text: question.text,
176+
sub_topic_id: subTopicId!,
177+
alternatives: question.alternatives,
178+
});
179+
}
101180
}
102181

103182
alert("Quiz criado com sucesso!");
@@ -123,6 +202,8 @@ export default function CreateQuiz() {
123202
{currentStep === "questions" && (
124203
<Questions
125204
onQuestionsChange={setQuestionsData}
205+
onAutomaticDataChange={setAutomaticData}
206+
onModeChange={(mode) => setIsAutomatic(mode === "automatic")}
126207
questions={questionsData}
127208
themeId={themeData?.id || ""}
128209
subTopicId={subtopicData?.id || ""}
@@ -144,7 +225,7 @@ export default function CreateQuiz() {
144225

145226
{currentStep === "questions" && (
146227
<Button onClick={handleFinish} disabled={saving} className="ml-auto">
147-
{saving ? "Salvando..." : "Finalizar"}
228+
{saving ? "Processando..." : "Finalizar"}
148229
</Button>
149230
)}
150231
</nav>
Lines changed: 158 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,165 @@
1+
// /create/question/automatico/audio.tsx
2+
"use client";
3+
4+
import { useState } from "react";
5+
16
interface AudioProps {
2-
onBack: () => void;
7+
onDataChange: (data: { file: File; num_questions: number; num_alternatives: number } | null) => void;
38
}
49

5-
export function Audio({ onBack }: AudioProps) {
10+
export function Audio({ onDataChange }: AudioProps) {
11+
const [audioFile, setAudioFile] = useState<File | null>(null);
12+
const [numQuestions, setNumQuestions] = useState(5);
13+
const [numAlternatives, setNumAlternatives] = useState(4);
14+
15+
const updateData = (file: File | null, newNumQuestions: number, newNumAlternatives: number) => {
16+
if (file && newNumQuestions >= 1 && newNumQuestions <= 50 && newNumAlternatives >= 2 && newNumAlternatives <= 6) {
17+
onDataChange({
18+
file: file,
19+
num_questions: newNumQuestions,
20+
num_alternatives: newNumAlternatives
21+
});
22+
} else {
23+
onDataChange(null);
24+
}
25+
};
26+
27+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
28+
const file = e.target.files?.[0];
29+
if (file) {
30+
const validTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/m4a'];
31+
if (!validTypes.includes(file.type) && !file.name.match(/\.(mp3|wav|ogg|m4a)$/i)) {
32+
alert("Por favor, selecione um arquivo de áudio válido (MP3, WAV, OGG, M4A).");
33+
return;
34+
}
35+
setAudioFile(file);
36+
updateData(file, numQuestions, numAlternatives);
37+
}
38+
};
39+
40+
const handleRemoveFile = () => {
41+
setAudioFile(null);
42+
updateData(null, numQuestions, numAlternatives);
43+
};
44+
45+
const handleQuestionsChange = (value: number) => {
46+
setNumQuestions(value);
47+
updateData(audioFile, value, numAlternatives);
48+
};
49+
50+
const handleAlternativesChange = (value: number) => {
51+
setNumAlternatives(value);
52+
updateData(audioFile, numQuestions, value);
53+
};
54+
655
return (
7-
<div>
8-
<h2>Gerar questões automaticamente a partir de um áudio</h2>
9-
10-
quantidade de alternartivas por questão
11-
<input
12-
type="number"
13-
/>
14-
numero de questoes
15-
<input
16-
type="number"
17-
/>
18-
<input type="file" accept="audio/*" />
56+
<div className="max-w-4xl mx-auto">
57+
<div className="mb-6">
58+
<h3 className="text-xl font-bold mb-2">Arquivo de Áudio</h3>
59+
<p className="text-gray-600">
60+
Envie um arquivo de áudio que será transcrito e usado como base para gerar as questões automaticamente
61+
</p>
62+
</div>
63+
64+
<div className="bg-layout-card border rounded-lg p-6 mb-6">
65+
<h4 className="font-semibold mb-4">Configurações</h4>
66+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
67+
<div>
68+
<label htmlFor="numQuestions" className="block text-sm font-medium mb-2">
69+
Número de Questões
70+
</label>
71+
<input
72+
id="numQuestions"
73+
type="number"
74+
min="1"
75+
max="50"
76+
value={numQuestions}
77+
onChange={(e) => handleQuestionsChange(Number(e.target.value))}
78+
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 outline-none transition-all"
79+
/>
80+
<p className="text-xs text-gray-500 mt-1">Entre 1 e 50 questões</p>
81+
</div>
82+
83+
<div>
84+
<label htmlFor="numAlternatives" className="block text-sm font-medium mb-2">
85+
Alternativas por Questão
86+
</label>
87+
<input
88+
id="numAlternatives"
89+
type="number"
90+
min="2"
91+
max="6"
92+
value={numAlternatives}
93+
onChange={(e) => handleAlternativesChange(Number(e.target.value))}
94+
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 outline-none transition-all"
95+
/>
96+
<p className="text-xs text-gray-500 mt-1">Entre 2 e 6 alternativas</p>
97+
</div>
98+
</div>
99+
</div>
100+
101+
<div className="bg-layout-card border rounded-lg p-6 mb-6">
102+
<label htmlFor="audioInput" className="block text-sm font-medium mb-2">
103+
Arquivo de Áudio
104+
</label>
105+
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-purple-400 transition-colors">
106+
<input
107+
id="audioInput"
108+
type="file"
109+
accept="audio/*,.mp3,.wav,.ogg,.m4a"
110+
onChange={handleFileChange}
111+
className="hidden"
112+
/>
113+
<label htmlFor="audioInput" className="cursor-pointer">
114+
<div className="flex flex-col items-center">
115+
<svg className="w-12 h-12 text-purple-500 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
116+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
117+
</svg>
118+
{audioFile ? (
119+
<div>
120+
<p className="text-sm font-medium text-gray-900">{audioFile.name}</p>
121+
<p className="text-xs text-gray-500 mt-1">
122+
{(audioFile.size / 1024 / 1024).toFixed(2)} MB
123+
</p>
124+
</div>
125+
) : (
126+
<div>
127+
<p className="text-sm font-medium text-gray-900">
128+
Clique para selecionar um arquivo de áudio
129+
</p>
130+
<p className="text-xs text-gray-500 mt-1">
131+
MP3, WAV, OGG ou M4A
132+
</p>
133+
</div>
134+
)}
135+
</div>
136+
</label>
137+
</div>
138+
{audioFile && (
139+
<div className="flex justify-end mt-2">
140+
<button
141+
onClick={handleRemoveFile}
142+
className="text-xs text-red-600 hover:text-red-700"
143+
>
144+
Remover arquivo
145+
</button>
146+
</div>
147+
)}
148+
</div>
149+
150+
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
151+
<div className="flex items-start gap-3">
152+
<svg className="w-5 h-5 text-yellow-600 mt-0.5 shrink-0" fill="currentColor" viewBox="0 0 20 20">
153+
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
154+
</svg>
155+
<div>
156+
<p className="text-sm font-medium text-yellow-900">Importante</p>
157+
<p className="text-sm text-yellow-800">
158+
O áudio será transcrito automaticamente. Para melhores resultados, use áudios com boa qualidade de som e fala clara. Clique em "Finalizar" para criar o quiz.
159+
</p>
160+
</div>
161+
</div>
162+
</div>
19163
</div>
20164
);
21165
}

0 commit comments

Comments
 (0)