fixing stuff
Some checks failed
Mindforge Web Build and Deploy (internal) / Build Mindforge Web Image (push) Failing after 1m57s
Mindforge Web Build and Deploy (internal) / Deploy Mindforge Web (internal) (push) Has been skipped
Mindforge API Build and Deploy / Build Mindforge API Image (push) Successful in 2m11s
Mindforge API Build and Deploy / Deploy Mindforge API (internal) (push) Successful in 8s

This commit is contained in:
2026-06-11 19:34:52 -03:00
parent 82dda0395d
commit b29ae991c8
8 changed files with 155 additions and 16 deletions

View File

@@ -13,6 +13,6 @@ namespace Mindforge.API.Models.Requests
public enum FlashcardDifficulty public enum FlashcardDifficulty
{ {
Easy, Easy,
Hard Medium
} }
} }

View File

@@ -36,9 +36,9 @@ namespace Mindforge.API.Services
throw new UserException("Selecione ao menos um arquivo para gerar flashcards."); 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 var uniqueFilePaths = request.FilePaths
@@ -166,7 +166,7 @@ namespace Mindforge.API.Services
{ {
var difficultyInstruction = difficulty switch 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." _ => "Crie perguntas diretas e objetivas para facilitar memorizacao inicial."
}; };

View File

@@ -30,6 +30,13 @@
transform: translateY(0); transform: translateY(0);
} }
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-secondary { .btn-secondary {
background: transparent; background: transparent;
color: var(--color-text-creamy); color: var(--color-text-creamy);

View File

@@ -9,10 +9,10 @@ import { Button } from './Button';
import './FlashcardComponent.css'; import './FlashcardComponent.css';
const minAmount = 10; const minAmount = 10;
const maxAmount = 30; const maxAmount = 50;
function difficultyLabel(difficulty: string) { function difficultyLabel(difficulty: string) {
return difficulty === 'Hard' ? 'Dificil' : 'Facil'; return difficulty === 'Medium' ? 'Medio' : 'Facil';
} }
export function FlashcardComponent() { export function FlashcardComponent() {
@@ -98,14 +98,14 @@ export function FlashcardComponent() {
<div className="radio-item"> <div className="radio-item">
<input <input
type="radio" type="radio"
id="difficulty-hard" id="difficulty-medium"
name="difficulty" name="difficulty"
value="Hard" value="Medium"
checked={difficulty === 'Hard'} checked={difficulty === 'Medium'}
onChange={() => setDifficulty('Hard')} onChange={() => setDifficulty('Medium')}
/> />
<label htmlFor="difficulty-hard" className="radio-label" title="Perguntas mais desafiadoras"> <label htmlFor="difficulty-medium" className="radio-label" title="Perguntas de nivel intermediario">
Dificil Medio
</label> </label>
</div> </div>
</div> </div>

View File

@@ -21,7 +21,7 @@ function groupLibrariesBySubject(libraries: FlashcardLibrarySummary[]) {
} }
function difficultyLabel(difficulty: string) { function difficultyLabel(difficulty: string) {
return difficulty === 'Hard' ? 'Dificil' : 'Facil'; return difficulty === 'Medium' ? 'Medio' : 'Facil';
} }
function shuffleCards(cards: FlashcardCard[]) { function shuffleCards(cards: FlashcardCard[]) {

View File

@@ -80,6 +80,21 @@
font-size: 1.05rem; 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 { .spaced-review-subject-header p {
margin: 0.35rem 0 0; margin: 0.35rem 0 0;
font-size: 0.9rem; font-size: 0.9rem;

View File

@@ -100,6 +100,8 @@ export function SpacedReviewComponent() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [selectedStatuses, setSelectedStatuses] = useState<FlashcardRagStatus[]>(['Red', 'Amber', 'Green', 'Grey']); const [selectedStatuses, setSelectedStatuses] = useState<FlashcardRagStatus[]>(['Red', 'Amber', 'Green', 'Grey']);
const [selectedLibraryIds, setSelectedLibraryIds] = useState<number[]>([]); const [selectedLibraryIds, setSelectedLibraryIds] = useState<number[]>([]);
const [selectedSubjects, setSelectedSubjects] = useState<string[]>([]);
const [selectedSubSubjects, setSelectedSubSubjects] = useState<string[]>([]);
const [startingSession, setStartingSession] = useState(false); const [startingSession, setStartingSession] = useState(false);
const [sessionCards, setSessionCards] = useState<FlashcardCard[]>([]); const [sessionCards, setSessionCards] = useState<FlashcardCard[]>([]);
const [sessionLibraries, setSessionLibraries] = useState<FlashcardRagLibrary[]>([]); const [sessionLibraries, setSessionLibraries] = useState<FlashcardRagLibrary[]>([]);
@@ -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(() => { const allRagLibraries = useMemo(() => {
if (!dashboard) { if (!dashboard) {
return []; return [];
@@ -210,6 +255,64 @@ export function SpacedReviewComponent() {
setSelectedLibraryIds([...selectedLibraryIds, libraryId]); 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 () => { const startSession = async () => {
if (selectedStatuses.length === 0) { if (selectedStatuses.length === 0) {
setError('Selecione ao menos um status para iniciar a revisao.'); setError('Selecione ao menos um status para iniciar a revisao.');
@@ -335,7 +438,14 @@ export function SpacedReviewComponent() {
{dashboard.subjects.map((subjectGroup) => ( {dashboard.subjects.map((subjectGroup) => (
<section key={subjectGroup.subject} className="spaced-review-subject"> <section key={subjectGroup.subject} className="spaced-review-subject">
<header className="spaced-review-subject-header"> <header className="spaced-review-subject-header">
<label className="spaced-review-subject-toggle">
<input
type="checkbox"
checked={selectedSubjects.includes(subjectGroup.subject)}
onChange={() => toggleSubject(subjectGroup.subject)}
/>
<h3>{subjectGroup.subject}</h3> <h3>{subjectGroup.subject}</h3>
</label>
<p>{summaryText( <p>{summaryText(
subjectGroup.summary.activeCount, subjectGroup.summary.activeCount,
subjectGroup.summary.greenPercentage, subjectGroup.summary.greenPercentage,
@@ -352,7 +462,14 @@ export function SpacedReviewComponent() {
{subjectGroup.subSubjects.map((subSubjectGroup) => ( {subjectGroup.subSubjects.map((subSubjectGroup) => (
<div key={`${subjectGroup.subject}::${subSubjectGroup.subSubject}`} className="spaced-review-subsubject-block"> <div key={`${subjectGroup.subject}::${subSubjectGroup.subSubject}`} className="spaced-review-subsubject-block">
<div className="spaced-review-subsubject-header"> <div className="spaced-review-subsubject-header">
<label className="spaced-review-subsubject-toggle">
<input
type="checkbox"
checked={selectedSubSubjects.includes(`${subjectGroup.subject}::${subSubjectGroup.subSubject}`)}
onChange={() => toggleSubSubject(subjectGroup.subject, subSubjectGroup.subSubject)}
/>
<strong>{subSubjectGroup.subSubject}</strong> <strong>{subSubjectGroup.subSubject}</strong>
</label>
<span>{summaryText( <span>{summaryText(
subSubjectGroup.summary.activeCount, subSubjectGroup.summary.activeCount,
subSubjectGroup.summary.greenPercentage, subSubjectGroup.summary.greenPercentage,

View File

@@ -26,7 +26,7 @@ export interface CheckFileResponse {
result: string; result: string;
} }
export type FlashcardDifficulty = 'Easy' | 'Hard'; export type FlashcardDifficulty = 'Easy' | 'Medium';
export interface GenerateFlashcardsRequest { export interface GenerateFlashcardsRequest {
filePaths: string[]; filePaths: string[];