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
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:
@@ -13,6 +13,6 @@ namespace Mindforge.API.Models.Requests
|
|||||||
public enum FlashcardDifficulty
|
public enum FlashcardDifficulty
|
||||||
{
|
{
|
||||||
Easy,
|
Easy,
|
||||||
Hard
|
Medium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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[]) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
Reference in New Issue
Block a user