diff --git a/Mindforge.API/Models/Requests/FlashcardGenerateRequest.cs b/Mindforge.API/Models/Requests/FlashcardGenerateRequest.cs
index b08d18b..41449eb 100644
--- a/Mindforge.API/Models/Requests/FlashcardGenerateRequest.cs
+++ b/Mindforge.API/Models/Requests/FlashcardGenerateRequest.cs
@@ -13,6 +13,6 @@ namespace Mindforge.API.Models.Requests
public enum FlashcardDifficulty
{
Easy,
- Hard
+ Medium
}
}
diff --git a/Mindforge.API/Services/FlashcardService.cs b/Mindforge.API/Services/FlashcardService.cs
index e6d040a..bde6744 100644
--- a/Mindforge.API/Services/FlashcardService.cs
+++ b/Mindforge.API/Services/FlashcardService.cs
@@ -36,9 +36,9 @@ namespace Mindforge.API.Services
throw new UserException("Selecione ao menos um arquivo para gerar flashcards.");
}
- if (request.Amount is < 10 or > 30)
+ if (request.Amount is < 10 or > 50)
{
- throw new UserException("A quantidade de flashcards deve estar entre 10 e 30 por arquivo.");
+ throw new UserException("A quantidade de flashcards deve estar entre 10 e 50 por arquivo.");
}
var uniqueFilePaths = request.FilePaths
@@ -166,7 +166,7 @@ namespace Mindforge.API.Services
{
var difficultyInstruction = difficulty switch
{
- FlashcardDifficulty.Hard => "Crie perguntas mais desafiadoras, focando diferencas finas e cobrancas de prova.",
+ FlashcardDifficulty.Medium => "Crie perguntas de nivel intermediario, combinando conceitos e exigindo raciocĂnio moderado.",
_ => "Crie perguntas diretas e objetivas para facilitar memorizacao inicial."
};
diff --git a/Mindforge.Web/src/components/Button.css b/Mindforge.Web/src/components/Button.css
index 5e93a4d..64f5d55 100644
--- a/Mindforge.Web/src/components/Button.css
+++ b/Mindforge.Web/src/components/Button.css
@@ -30,6 +30,13 @@
transform: translateY(0);
}
+.btn:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: none;
+}
+
.btn-secondary {
background: transparent;
color: var(--color-text-creamy);
diff --git a/Mindforge.Web/src/components/FlashcardComponent.tsx b/Mindforge.Web/src/components/FlashcardComponent.tsx
index 58916e1..d487135 100644
--- a/Mindforge.Web/src/components/FlashcardComponent.tsx
+++ b/Mindforge.Web/src/components/FlashcardComponent.tsx
@@ -9,10 +9,10 @@ import { Button } from './Button';
import './FlashcardComponent.css';
const minAmount = 10;
-const maxAmount = 30;
+const maxAmount = 50;
function difficultyLabel(difficulty: string) {
- return difficulty === 'Hard' ? 'Dificil' : 'Facil';
+ return difficulty === 'Medium' ? 'Medio' : 'Facil';
}
export function FlashcardComponent() {
@@ -98,14 +98,14 @@ export function FlashcardComponent() {
setDifficulty('Hard')}
+ value="Medium"
+ checked={difficulty === 'Medium'}
+ onChange={() => setDifficulty('Medium')}
/>
-
diff --git a/Mindforge.Web/src/components/FlashcardReviewComponent.tsx b/Mindforge.Web/src/components/FlashcardReviewComponent.tsx
index df6a49d..6bc9c60 100644
--- a/Mindforge.Web/src/components/FlashcardReviewComponent.tsx
+++ b/Mindforge.Web/src/components/FlashcardReviewComponent.tsx
@@ -21,7 +21,7 @@ function groupLibrariesBySubject(libraries: FlashcardLibrarySummary[]) {
}
function difficultyLabel(difficulty: string) {
- return difficulty === 'Hard' ? 'Dificil' : 'Facil';
+ return difficulty === 'Medium' ? 'Medio' : 'Facil';
}
function shuffleCards(cards: FlashcardCard[]) {
diff --git a/Mindforge.Web/src/components/SpacedReviewComponent.css b/Mindforge.Web/src/components/SpacedReviewComponent.css
index 0bf325f..d4b7e20 100644
--- a/Mindforge.Web/src/components/SpacedReviewComponent.css
+++ b/Mindforge.Web/src/components/SpacedReviewComponent.css
@@ -80,6 +80,21 @@
font-size: 1.05rem;
}
+.spaced-review-subject-toggle,
+.spaced-review-subsubject-toggle {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ cursor: pointer;
+}
+
+.spaced-review-subject-toggle input[type="checkbox"],
+.spaced-review-subsubject-toggle input[type="checkbox"] {
+ width: 16px;
+ height: 16px;
+ accent-color: var(--color-accent);
+}
+
.spaced-review-subject-header p {
margin: 0.35rem 0 0;
font-size: 0.9rem;
diff --git a/Mindforge.Web/src/components/SpacedReviewComponent.tsx b/Mindforge.Web/src/components/SpacedReviewComponent.tsx
index 227c90e..3edeb60 100644
--- a/Mindforge.Web/src/components/SpacedReviewComponent.tsx
+++ b/Mindforge.Web/src/components/SpacedReviewComponent.tsx
@@ -100,6 +100,8 @@ export function SpacedReviewComponent() {
const [error, setError] = useState(null);
const [selectedStatuses, setSelectedStatuses] = useState(['Red', 'Amber', 'Green', 'Grey']);
const [selectedLibraryIds, setSelectedLibraryIds] = useState([]);
+ const [selectedSubjects, setSelectedSubjects] = useState([]);
+ const [selectedSubSubjects, setSelectedSubSubjects] = useState([]);
const [startingSession, setStartingSession] = useState(false);
const [sessionCards, setSessionCards] = useState([]);
const [sessionLibraries, setSessionLibraries] = useState([]);
@@ -161,6 +163,49 @@ export function SpacedReviewComponent() {
};
}, []);
+ const startSession = async () => {
+ if (selectedStatuses.length === 0) {
+ setError('Selecione ao menos um status para iniciar a revisao.');
+ return;
+ }
+
+ if (selectedLibraryIds.length === 0) {
+ setError('Selecione ao menos um arquivo para iniciar a revisao.');
+ return;
+ }
+
+ if (selectedRagLibraries.length === 0) {
+ setError('Nenhum arquivo encontrado com os filtros atuais. Ajuste os status ou os arquivos selecionados.');
+ return;
+ }
+
+ setStartingSession(true);
+ setError(null);
+
+ try {
+ const ragByLibraryId = new Map(selectedRagLibraries.map((library) => [library.libraryId, library]));
+ const libraryIds = Array.from(new Set(selectedRagLibraries.map((library) => library.libraryId)));
+ const response = await MindforgeApiService.createFlashcardReviewSession({ libraryIds });
+ const allowedLibraryIds = new Set(libraryIds);
+ const filteredCards = response.cards.filter((card) => allowedLibraryIds.has(card.libraryId));
+ const orderedCards = orderCardsForSession(filteredCards, ragByLibraryId);
+
+ if (orderedCards.length === 0) {
+ setError('Os filtros selecionados nao retornaram cards para revisar.');
+ return;
+ }
+
+ setSessionLibraries(selectedRagLibraries);
+ setSessionCards(orderedCards);
+ setCurrentIndex(0);
+ setShowAnswer(false);
+ } catch (err: any) {
+ setError(err?.message || 'Falha ao iniciar revisao espacada.');
+ } finally {
+ setStartingSession(false);
+ }
+ };
+
const allRagLibraries = useMemo(() => {
if (!dashboard) {
return [];
@@ -210,6 +255,64 @@ export function SpacedReviewComponent() {
setSelectedLibraryIds([...selectedLibraryIds, libraryId]);
};
+ const toggleSubject = (subject: string) => {
+ if (selectedSubjects.includes(subject)) {
+ setSelectedSubjects(selectedSubjects.filter((s) => s !== subject));
+ const subjectGroup = dashboard?.subjects.find((s) => s.subject === subject);
+ if (subjectGroup) {
+ const libraryIdsToRemove = subjectGroup.subSubjects.flatMap((ss) =>
+ ss.libraries.map((lib) => lib.libraryId));
+ setSelectedLibraryIds((current) => current.filter((id) => !libraryIdsToRemove.includes(id)));
+ const subSubjectKeysToRemove = subjectGroup.subSubjects.map((ss) => `${subject}::${ss.subSubject}`);
+ setSelectedSubSubjects((current) => current.filter((key) => !subSubjectKeysToRemove.includes(key)));
+ }
+ return;
+ }
+
+ setSelectedSubjects([...selectedSubjects, subject]);
+ const subjectGroup = dashboard?.subjects.find((s) => s.subject === subject);
+ if (subjectGroup) {
+ const libraryIdsToAdd = subjectGroup.subSubjects.flatMap((ss) =>
+ ss.libraries.map((lib) => lib.libraryId));
+ setSelectedLibraryIds((current) => [...new Set([...current, ...libraryIdsToAdd])]);
+ const subSubjectKeysToAdd = subjectGroup.subSubjects.map((ss) => `${subject}::${ss.subSubject}`);
+ setSelectedSubSubjects((current) => [...new Set([...current, ...subSubjectKeysToAdd])]);
+ }
+ };
+
+ const toggleSubSubject = (subject: string, subSubject: string) => {
+ const key = `${subject}::${subSubject}`;
+ if (selectedSubSubjects.includes(key)) {
+ setSelectedSubSubjects(selectedSubSubjects.filter((k) => k !== key));
+ const subjectGroup = dashboard?.subjects.find((s) => s.subject === subject);
+ const subSubjectGroup = subjectGroup?.subSubjects.find((ss) => ss.subSubject === subSubject);
+ if (subSubjectGroup) {
+ const libraryIdsToRemove = subSubjectGroup.libraries.map((lib) => lib.libraryId);
+ setSelectedLibraryIds((current) => current.filter((id) => !libraryIdsToRemove.includes(id)));
+ }
+ const parentSubjectStillSelected = selectedSubSubjects.some((k) =>
+ k.startsWith(`${subject}::`) && k !== key);
+ if (!parentSubjectStillSelected) {
+ setSelectedSubjects((current) => current.filter((s) => s !== subject));
+ }
+ return;
+ }
+
+ setSelectedSubSubjects([...selectedSubSubjects, key]);
+ const subjectGroup = dashboard?.subjects.find((s) => s.subject === subject);
+ const subSubjectGroup = subjectGroup?.subSubjects.find((ss) => ss.subSubject === subSubject);
+ if (subSubjectGroup) {
+ const libraryIdsToAdd = subSubjectGroup.libraries.map((lib) => lib.libraryId);
+ setSelectedLibraryIds((current) => [...new Set([...current, ...libraryIdsToAdd])]);
+ }
+ const allSubSubjects = subjectGroup?.subSubjects.map((ss) => `${subject}::${ss.subSubject}`) || [];
+ const allSelected = allSubSubjects.every((k) =>
+ k === key || selectedSubSubjects.includes(k));
+ if (allSelected && subject && !selectedSubjects.includes(subject)) {
+ setSelectedSubjects((current) => [...current, subject]);
+ }
+ };
+
const startSession = async () => {
if (selectedStatuses.length === 0) {
setError('Selecione ao menos um status para iniciar a revisao.');
@@ -335,7 +438,14 @@ export function SpacedReviewComponent() {
{dashboard.subjects.map((subjectGroup) => (