Skip to content

Commit 919c5b8

Browse files
committed
Add loading for flashcard and quizzes
1 parent 8e04538 commit 919c5b8

File tree

66 files changed

+5092
-656
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+5092
-656
lines changed

client/package-lock.json

Lines changed: 431 additions & 443 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/components/ChatTab.tsx

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -35,54 +35,13 @@ const ChatTab = ({ uploadedFiles, documentIds }: ChatTabProps) => {
3535
setIsLoading(true);
3636
setError(null);
3737
try {
38-
// Check if documents are ready for chat
39-
const documentStatuses = await Promise.all(
40-
documentIds.map(async (docId) => {
41-
try {
42-
const content = await apiService.getDocumentContent(docId);
43-
return {
44-
id: docId,
45-
name: content.originalName || 'Unknown Document',
46-
status: content.status,
47-
summaryStatus: content.summaryStatus,
48-
isReady: content.status === 'PROCESSED' || content.summaryStatus === 'PROCESSED'
49-
};
50-
} catch (error) {
51-
return {
52-
id: docId,
53-
name: 'Unknown Document',
54-
status: 'ERROR',
55-
summaryStatus: 'ERROR',
56-
isReady: false
57-
};
58-
}
59-
})
60-
);
61-
62-
const readyDocuments = documentStatuses.filter(doc => doc.isReady);
63-
const processingDocuments = documentStatuses.filter(doc =>
64-
doc.status === 'PROCESSING' || doc.summaryStatus === 'PROCESSING' ||
65-
doc.status === 'UPLOADED' || doc.summaryStatus === 'UPLOADED'
66-
);
67-
68-
if (readyDocuments.length === 0) {
69-
if (processingDocuments.length > 0) {
70-
setError(`Documents are still being processed. Please wait for processing to complete before starting a chat session.`);
71-
} else {
72-
setError('No processed documents available for chat. Please upload and process documents first.');
73-
}
74-
setIsLoading(false);
75-
return;
76-
}
77-
78-
// Create chat session with ready documents only
79-
const readyDocumentIds = readyDocuments.map(doc => doc.id);
80-
const response = await apiService.createChatSession(readyDocumentIds);
38+
// Create chat session immediately with all provided documents
39+
const response = await apiService.createChatSession(documentIds);
8140
setSessionId(response.sessionId);
8241
setMessages(response.messages);
8342
} catch (error) {
8443
console.error('Failed to create chat session:', error);
85-
setError('Failed to create chat session. Please make sure your documents are fully processed.');
44+
setError('Failed to create chat session. Please try again.');
8645
} finally {
8746
setIsLoading(false);
8847
}
@@ -92,18 +51,7 @@ const ChatTab = ({ uploadedFiles, documentIds }: ChatTabProps) => {
9251
initializeChatSession();
9352
}, [documentIds, sessionId]);
9453

95-
// Poll for document processing updates when documents are still being processed
96-
useEffect(() => {
97-
if (error && error.includes('still being processed') && !sessionId) {
98-
const interval = setInterval(() => {
99-
// Retry chat session creation
100-
setError(null);
101-
setSessionId(null); // This will trigger the useEffect above
102-
}, 30000); // Poll every 30 seconds
103-
104-
return () => clearInterval(interval);
105-
}
106-
}, [error, sessionId]);
54+
// Remove polling logic since we don't wait for processing anymore
10755

10856
const handleSendMessage = async () => {
10957
if (!inputMessage.trim() || !sessionId) return;

client/src/components/DashboardSection.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ interface UploadedFileWithId {
1313
documentId: string;
1414
}
1515

16+
interface QuizData {
17+
questions: any[];
18+
documentName: string;
19+
documentId: string;
20+
status?: 'UPLOADED' | 'PROCESSING' | 'PROCESSED' | 'ERROR' | 'PENDING';
21+
error?: string;
22+
}
23+
1624
interface DashboardSectionProps {
1725
uploadedFiles: UploadedFileWithId[];
1826
onFileUpload: (files: File[], documentIds: string[]) => void;
@@ -22,19 +30,14 @@ interface DashboardSectionProps {
2230

2331
const DashboardSection = ({ uploadedFiles, onFileUpload, onBackToHome, isLoadingDocuments = false }: DashboardSectionProps) => {
2432
const [activeTab, setActiveTab] = useState('upload');
25-
const [quizzes, setQuizzes] = useState([]);
26-
const [answers, setAnswers] = useState({}); // answers: { [documentId: string]: { [questionIndex: number]: string | number } }
27-
28-
console.log('DashboardSection render - uploadedFiles length:', uploadedFiles.length);
29-
console.log('DashboardSection render - uploadedFiles:', uploadedFiles.map(f => ({ name: f.file.name, id: f.documentId })));
33+
const [quizzes, setQuizzes] = useState<QuizData[]>([]);
34+
const [answers, setAnswers] = useState<{ [documentId: string]: { [questionIndex: number]: string | number } }>({});
3035

3136
// Extract files and document IDs with memoization to prevent unnecessary re-renders
3237
const files = useMemo(() => {
33-
console.log('DashboardSection - files memo recalculated');
3438
return uploadedFiles.map(item => item.file);
3539
}, [uploadedFiles]);
3640
const documentIds = useMemo(() => {
37-
console.log('DashboardSection - documentIds memo recalculated:', uploadedFiles.map(item => item.documentId));
3841
return uploadedFiles.map(item => item.documentId);
3942
}, [uploadedFiles]);
4043

client/src/components/FlashcardsTab.tsx

Lines changed: 93 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { useState, useEffect, useRef } from 'react';
33
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
44
import { Button } from '@/components/ui/button';
5-
import { BookOpen, RotateCcw, ChevronLeft, ChevronRight, Shuffle, Eye, EyeOff, Loader2 } from 'lucide-react';
5+
import { BookOpen, RotateCcw, ChevronLeft, ChevronRight, Shuffle, Eye, EyeOff, Loader2, XCircle } from 'lucide-react';
66
import { apiService, FlashcardModel, DocumentStatus } from '../lib/api';
77

88
interface FlashcardsTabProps {
@@ -32,12 +32,10 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
3232

3333
useEffect(() => {
3434
const newDocumentIds = documentIds.filter(id => !fetchedDocumentIds.current.has(id));
35-
if (newDocumentIds.length === 0) return;
3635

37-
// Mark as being processed to avoid duplicate processing
38-
newDocumentIds.forEach(id => processingDocumentIds.current.add(id));
36+
if (newDocumentIds.length === 0) return;
3937

40-
// First, add pending entries for new documents
38+
// Immediately add pending entries for new documents to show loading state
4139
const pendingEntries = newDocumentIds.map(documentId => ({
4240
id: documentId,
4341
title: 'Loading...',
@@ -46,32 +44,31 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
4644
source: 'Loading...',
4745
difficulty: 'Unknown',
4846
status: 'PENDING' as const,
49-
error: undefined,
47+
error: 'Preparing flashcards...',
5048
flashcards: []
5149
}));
5250

53-
setFlashcardDecks(prevDecks => [...prevDecks, ...pendingEntries]);
51+
setFlashcardDecks(prevDecks => {
52+
const existingDecks = prevDecks.filter(d => !newDocumentIds.includes(d.id));
53+
const newDecks = [...existingDecks, ...pendingEntries];
54+
return newDecks;
55+
});
5456

5557
const fetchFlashcards = async () => {
5658
const flashcardPromises = newDocumentIds.map(async (documentId) => {
5759
try {
58-
// Check if flashcard data is already available from automatic processing
5960
const documentContent = await apiService.getDocumentContent(documentId);
6061

61-
// If flashcards are ready and have data, use them directly
62+
// Simple status-based approach like SummaryTab
6263
if (documentContent.flashcardStatus === 'PROCESSED' && documentContent.flashcardData) {
63-
try {
64-
// The backend saves flashcard data directly as an array, not nested in response.flashcards
64+
// Parse flashcard data
6565
let flashcardsList = [];
6666

6767
if (Array.isArray(documentContent.flashcardData)) {
68-
// Direct array of flashcards
6968
flashcardsList = documentContent.flashcardData;
7069
} else if (documentContent.flashcardData.response && Array.isArray(documentContent.flashcardData.response.flashcards)) {
71-
// Nested in response.flashcards
7270
flashcardsList = documentContent.flashcardData.response.flashcards;
7371
} else if (documentContent.flashcardData.flashcards && Array.isArray(documentContent.flashcardData.flashcards)) {
74-
// Nested in flashcards property
7572
flashcardsList = documentContent.flashcardData.flashcards;
7673
}
7774

@@ -94,65 +91,32 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
9491
flashcards
9592
};
9693
}
97-
} catch (parseError) {
98-
console.warn('Failed to parse existing flashcard data:', parseError);
99-
}
10094
}
10195

102-
// Check if we should wait for automatic processing or make individual API call
103-
const shouldWaitForAutoProcessing =
104-
documentContent.status === 'PROCESSING' ||
105-
documentContent.summaryStatus === 'PROCESSING' ||
106-
documentContent.quizStatus === 'PROCESSING' ||
107-
documentContent.flashcardStatus === 'PROCESSING' ||
108-
documentContent.summaryStatus === 'UPLOADED' ||
109-
documentContent.flashcardStatus === 'UPLOADED';
110-
111-
if (shouldWaitForAutoProcessing) {
112-
return {
113-
id: documentId,
114-
title: documentContent.originalName || 'Unknown Document',
115-
description: 'Flashcards are being generated automatically...',
116-
cardCount: 0,
117-
source: documentContent.originalName || 'Unknown Document',
118-
difficulty: 'Unknown',
119-
status: 'PROCESSING',
120-
error: 'Flashcards are being generated automatically...',
121-
flashcards: []
122-
};
123-
}
96+
// Return status based on flashcardStatus
97+
let status: 'PROCESSING' | 'PENDING' | 'ERROR' | 'PROCESSED' = 'PENDING';
98+
let errorMessage = undefined;
12499

125-
// Only make individual API call if automatic processing failed
126-
if (documentContent.flashcardStatus === 'ERROR') {
127-
const res = await apiService.getFlashcardsForDocument(documentId);
128-
129-
if (res.status === 'PROCESSING') {
130-
pollFlashcardStatus(documentId);
131-
}
132-
133-
return {
134-
id: documentId,
135-
title: res.documentName || 'Unknown Document',
136-
description: res.status === 'PROCESSED' ? `${res.flashcards.length} flashcards generated from your document` : 'Failed to generate flashcards',
137-
cardCount: res.flashcards.length,
138-
source: res.documentName || 'Unknown Document',
139-
difficulty: res.flashcards.length > 10 ? 'Advanced' : 'Beginner',
140-
status: res.status,
141-
error: res.error,
142-
flashcards: res.flashcards
143-
};
100+
if (documentContent.flashcardStatus === 'PROCESSING') {
101+
status = 'PROCESSING';
102+
errorMessage = 'AI is generating your flashcards...';
103+
} else if (documentContent.flashcardStatus === 'ERROR') {
104+
status = 'ERROR';
105+
errorMessage = 'Failed to generate flashcards';
106+
} else if (documentContent.flashcardStatus === 'UPLOADED') {
107+
status = 'PENDING';
108+
errorMessage = 'Waiting for flashcard generation to start...';
144109
}
145110

146-
// Default: wait for automatic processing
147111
return {
148112
id: documentId,
149113
title: documentContent.originalName || 'Unknown Document',
150-
description: 'Waiting for automatic flashcard generation...',
114+
description: errorMessage || 'Processing...',
151115
cardCount: 0,
152116
source: documentContent.originalName || 'Unknown Document',
153117
difficulty: 'Unknown',
154-
status: 'PROCESSING',
155-
error: 'Waiting for automatic flashcard generation...',
118+
status,
119+
error: errorMessage,
156120
flashcards: []
157121
};
158122

@@ -173,7 +137,6 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
173137

174138
const results = await Promise.all(flashcardPromises);
175139

176-
// Mark as fetched and remove from processing
177140
newDocumentIds.forEach(id => {
178141
fetchedDocumentIds.current.add(id);
179142
processingDocumentIds.current.delete(id);
@@ -192,15 +155,11 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
192155
error: result.error,
193156
flashcards: result.flashcards
194157
}));
195-
return [...existingDecks, ...mappedResults];
158+
const newDecks = [...existingDecks, ...mappedResults];
159+
return newDecks;
196160
});
197161
};
198162

199-
// Start automatic polling for documents with flashcard data
200-
for (const documentId of newDocumentIds) {
201-
pollFlashcardStatus(documentId);
202-
}
203-
204163
fetchFlashcards();
205164
}, [documentIds.join(",")]); // Keep dependencies minimal to avoid infinite loops
206165

@@ -329,10 +288,14 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
329288
// Filter ready flashcards
330289
const readyFlashcards = flashcardDecks.filter(deck => deck.status === 'PROCESSED' && deck.flashcards && deck.flashcards.length > 0);
331290

332-
// Display pending documents or errors
333-
const pendingDocuments = flashcardDecks.filter(deck =>
334-
(deck.status !== 'PROCESSED' && deck.status !== 'PROCESSING' && deck.status !== 'ERROR')
335-
);
291+
// Display processing documents
292+
const processingDocuments = flashcardDecks.filter(deck => deck.status === 'PROCESSING');
293+
294+
// Display pending documents
295+
const pendingDocuments = flashcardDecks.filter(deck => deck.status === 'PENDING');
296+
297+
// Display error documents
298+
const errorDocuments = flashcardDecks.filter(deck => deck.status === 'ERROR');
336299

337300
return (
338301
<>
@@ -516,22 +479,48 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
516479
</div>
517480
)}
518481

482+
{processingDocuments.length > 0 && (
483+
<div className="space-y-4">
484+
<h3 className="text-lg font-semibold text-gray-900">Flashcards Being Generated</h3>
485+
<div className="grid gap-6 md:grid-cols-2">
486+
{processingDocuments.map((deck) => (
487+
<Card key={deck.id} className="border-blue-200 bg-blue-50">
488+
<CardContent className="pt-6">
489+
<div className="flex items-center justify-between">
490+
<div className="flex items-center space-x-3">
491+
<Loader2 className="h-5 w-5 text-blue-600 animate-spin" />
492+
<div>
493+
<h4 className="font-medium text-gray-900">{deck.title}</h4>
494+
<p className="text-sm text-blue-600">AI is actively generating your flashcards...</p>
495+
</div>
496+
</div>
497+
<span className="px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-600">
498+
PROCESSING
499+
</span>
500+
</div>
501+
</CardContent>
502+
</Card>
503+
))}
504+
</div>
505+
</div>
506+
)}
507+
519508
{pendingDocuments.length > 0 && (
520509
<div className="space-y-4">
521510
<h3 className="text-lg font-semibold text-gray-900">Pending Flashcard Decks</h3>
522511
<div className="grid gap-6 md:grid-cols-2">
523512
{pendingDocuments.map((deck) => (
524-
<Card key={deck.id} className="border-gray-200 bg-gray-50">
513+
<Card key={deck.id} className="border-yellow-200 bg-yellow-50">
525514
<CardContent className="pt-6">
526515
<div className="flex items-center justify-between">
527516
<div className="flex items-center space-x-3">
528-
<Loader2 className="h-5 w-5 text-gray-600 animate-spin" />
517+
<Loader2 className="h-5 w-5 text-yellow-600 animate-spin" />
529518
<div>
530519
<h4 className="font-medium text-gray-900">{deck.title}</h4>
531-
<p className="text-sm text-gray-600">Flashcard generation hasn't started yet</p>
520+
<p className="text-sm text-yellow-600">Waiting for flashcard generation to start...</p>
532521
</div>
533522
</div>
534-
<span className="px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-600">
523+
<span className="px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-600">
535524
PENDING
536525
</span>
537526
</div>
@@ -542,7 +531,33 @@ const FlashcardsTab = ({ uploadedFiles, documentIds }: FlashcardsTabProps) => {
542531
</div>
543532
)}
544533

545-
{readyFlashcards.length === 0 && pendingDocuments.length === 0 && (
534+
{errorDocuments.length > 0 && (
535+
<div className="space-y-4">
536+
<h3 className="text-lg font-semibold text-gray-900">Failed Flashcard Decks</h3>
537+
<div className="grid gap-6 md:grid-cols-2">
538+
{errorDocuments.map((deck) => (
539+
<Card key={deck.id} className="border-red-200 bg-red-50">
540+
<CardContent className="pt-6">
541+
<div className="flex items-center justify-between">
542+
<div className="flex items-center space-x-3">
543+
<XCircle className="h-5 w-5 text-red-600" />
544+
<div>
545+
<h4 className="font-medium text-gray-900">{deck.title}</h4>
546+
<p className="text-sm text-red-600">{deck.error || 'Failed to generate flashcards'}</p>
547+
</div>
548+
</div>
549+
<span className="px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-600">
550+
ERROR
551+
</span>
552+
</div>
553+
</CardContent>
554+
</Card>
555+
))}
556+
</div>
557+
</div>
558+
)}
559+
560+
{readyFlashcards.length === 0 && processingDocuments.length === 0 && pendingDocuments.length === 0 && errorDocuments.length === 0 && (
546561
<Card className="text-center py-12">
547562
<CardContent>
548563
<BookOpen className="h-16 w-16 text-gray-400 mx-auto mb-4" />

0 commit comments

Comments
 (0)