Skip to content

Commit fe2d81a

Browse files
committed
Add document type selection to DocumentManager and DocumentUploader; implement processDocuments function for improved document processing
1 parent a18fdca commit fe2d81a

File tree

5 files changed

+152
-17
lines changed

5 files changed

+152
-17
lines changed

frontend/src/components/DocumentManager.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const DocumentManager: React.FC = () => {
2020
const [isUploading, setIsUploading] = useState<boolean>(false);
2121
const [status, setStatus] = useState<string>('');
2222
const [error, setError] = useState<string>('');
23+
const [documentType, setDocumentType] = useState<DocumentType>(DOCUMENT_TYPES.SYLLABUS);
2324

2425
const functions = getFunctions();
2526

@@ -51,6 +52,10 @@ const DocumentManager: React.FC = () => {
5152
}
5253
};
5354

55+
const handleTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
56+
setDocumentType(e.target.value as DocumentType);
57+
};
58+
5459
const handleUpload = async (e: React.FormEvent) => {
5560
e.preventDefault();
5661

@@ -155,6 +160,22 @@ const DocumentManager: React.FC = () => {
155160

156161
<form onSubmit={handleUpload} style={styles.form}>
157162
<div style={styles.fileInputContainer}>
163+
<label htmlFor="documentType" style={styles.fileInputLabel}>
164+
Document Type:
165+
</label>
166+
<select
167+
id="documentType"
168+
value={documentType}
169+
onChange={handleTypeChange}
170+
style={styles.select}
171+
disabled={isUploading}
172+
>
173+
<option value={DOCUMENT_TYPES.SYLLABUS}>Syllabus</option>
174+
<option value={DOCUMENT_TYPES.TRANSCRIPT}>Transcript</option>
175+
<option value={DOCUMENT_TYPES.GRADES}>Grades</option>
176+
<option value={DOCUMENT_TYPES.OTHER}>Other</option>
177+
</select>
178+
158179
<label htmlFor="fileInput" style={styles.fileInputLabel}>
159180
Select Documents
160181
</label>
@@ -262,6 +283,13 @@ const styles = {
262283
fontWeight: 'bold',
263284
color: '#555',
264285
},
286+
select: {
287+
padding: '10px',
288+
borderRadius: '4px',
289+
border: '1px solid #ddd',
290+
fontSize: '16px',
291+
marginBottom: '15px',
292+
},
265293
fileInput: {
266294
padding: '10px 0',
267295
},

frontend/src/components/DocumentProcessingStatus.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,8 @@ const DocumentProcessingStatus: React.FC<DocumentProcessingStatusProps> = ({ onP
128128
setStatus('Processing documents...');
129129

130130
try {
131-
const formatDocumentsData = httpsCallable(functions, 'formatDocumentsData');
132-
const result = await formatDocumentsData({
133-
forceProcess: true // Add this flag to bypass any waiting checks
134-
});
131+
const processDocuments = httpsCallable(functions, 'processDocuments');
132+
const result = await processDocuments({});
135133

136134
if ((result.data as any).success) {
137135
setStatus('Documents processed successfully');
@@ -183,10 +181,10 @@ const DocumentProcessingStatus: React.FC<DocumentProcessingStatusProps> = ({ onP
183181
setError(null);
184182

185183
try {
186-
// We'll use the formatDocumentsData function to process all documents
187-
const formatDocumentsData = httpsCallable(functions, 'formatDocumentsData');
188-
const result = await formatDocumentsData({
189-
forceProcess: true // Add this flag to bypass any waiting checks
184+
// Use the processDocuments function to process documents
185+
const processDocuments = httpsCallable(functions, 'processDocuments');
186+
const result = await processDocuments({
187+
documentId: documentId // Pass the specific document ID to process
190188
});
191189

192190
if ((result.data as any).success) {

frontend/src/components/DocumentUploader.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
22
import { useAuth } from '../contexts/AuthContext';
33
import { getFunctions, httpsCallable } from 'firebase/functions';
44
import { getFirestore, collection, doc, getDoc, getDocs, onSnapshot, query, orderBy, limit } from 'firebase/firestore';
5+
import DOCUMENT_TYPES, { DocumentType } from '../constants/documentTypes';
56

67
interface Document {
78
id: string;
@@ -19,7 +20,7 @@ interface Prediction {
1920
const DocumentUploader: React.FC = () => {
2021
const { currentUser } = useAuth();
2122
const [file, setFile] = useState<File | null>(null);
22-
const [documentType, setDocumentType] = useState<string>('syllabus');
23+
const [documentType, setDocumentType] = useState<string>(DOCUMENT_TYPES.SYLLABUS);
2324
const [uploadProgress, setUploadProgress] = useState<number>(0);
2425
const [isUploading, setIsUploading] = useState<boolean>(false);
2526
const [documents, setDocuments] = useState<Document[]>([]);
@@ -37,7 +38,7 @@ const DocumentUploader: React.FC = () => {
3738
// Get user's documents
3839
const getUserDocuments = async () => {
3940
try {
40-
const getUserDocumentsFunction = httpsCallable(functions, 'get_user_documents');
41+
const getUserDocumentsFunction = httpsCallable(functions, 'getUserDocuments');
4142
const result = await getUserDocumentsFunction({});
4243
if (result.data && (result.data as any).success) {
4344
setDocuments((result.data as any).documents || []);
@@ -94,8 +95,8 @@ const DocumentUploader: React.FC = () => {
9495
const base64String = fileReader.result as string;
9596

9697
try {
97-
// Call upload_and_process_document cloud function
98-
const uploadAndProcessDocument = httpsCallable(functions, 'upload_and_process_document');
98+
// Call uploadDocument cloud function
99+
const uploadAndProcessDocument = httpsCallable(functions, 'uploadDocument');
99100
const result = await uploadAndProcessDocument({
100101
documentType: documentType,
101102
documentBase64: base64String
@@ -224,8 +225,8 @@ const DocumentUploader: React.FC = () => {
224225
credit_hours: syllabusData.credit_hours || '3'
225226
};
226227

227-
// Call predict_final_grade cloud function
228-
const predictFinalGrade = httpsCallable(functions, 'predict_final_grade');
228+
// Call predictFinalGrade cloud function
229+
const predictFinalGrade = httpsCallable(functions, 'predictFinalGrade');
229230
const result = await predictFinalGrade({ courseData });
230231

231232
const data = result.data as any;
@@ -236,7 +237,7 @@ const DocumentUploader: React.FC = () => {
236237
setIsUploading(false);
237238

238239
// Refresh documents list
239-
const getUserDocuments = httpsCallable(functions, 'get_user_documents');
240+
const getUserDocuments = httpsCallable(functions, 'getUserDocuments');
240241
const docsResult = await getUserDocuments({});
241242
if (docsResult.data && (docsResult.data as any).success) {
242243
setDocuments((docsResult.data as any).documents || []);
@@ -295,8 +296,10 @@ const DocumentUploader: React.FC = () => {
295296
style={styles.select}
296297
disabled={isUploading}
297298
>
298-
<option value="syllabus">Syllabus</option>
299-
<option value="transcript">Transcript</option>
299+
<option value={DOCUMENT_TYPES.SYLLABUS}>Syllabus</option>
300+
<option value={DOCUMENT_TYPES.TRANSCRIPT}>Transcript</option>
301+
<option value={DOCUMENT_TYPES.GRADES}>Grades</option>
302+
<option value={DOCUMENT_TYPES.OTHER}>Other</option>
300303
</select>
301304
</div>
302305

functions-node/index.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,107 @@ const pdfParse = require('pdf-parse');
44
const tmp = require('tmp');
55
const fs = require('fs');
66
const OpenAI = require('openai');
7+
const cors = require('cors')({ origin: true });
78
const { calculateCurrentGrade } = require('./calculateGrade');
89
const { predictFinalGrade } = require('./predictGrade');
910
const { formatDocumentsData } = require('./formatDocumentsData');
1011
const { DOCUMENT_TYPES, normalizeDocumentType } = require('./utils/documentUtils');
12+
const corsMiddleware = require('./utils/corsMiddleware');
1113

1214
// Initialize Firebase Admin
1315
admin.initializeApp();
1416

1517
// Export functions
1618
exports.calculateCurrentGrade = calculateCurrentGrade;
1719
exports.predictFinalGrade = predictFinalGrade;
20+
21+
/**
22+
* Cloud Function to trigger processing of documents
23+
* This handles the extraction and formatting of document data
24+
* Can process all documents or a specific document if documentId is provided
25+
*/
26+
exports.processDocuments = functions.https.onCall(async (data, context) => {
27+
try {
28+
// Ensure user is authenticated
29+
if (!context.auth) {
30+
throw new functions.https.HttpsError('unauthenticated', 'User must be authenticated');
31+
}
32+
33+
const userId = context.auth.uid;
34+
const specificDocumentId = data?.documentId;
35+
36+
console.log(`Processing documents for user ${userId}${specificDocumentId ? `, document ID: ${specificDocumentId}` : ''}`);
37+
38+
// Get documents that need processing
39+
const db = admin.firestore();
40+
const documentsRef = db.collection('users').doc(userId).collection('documents');
41+
42+
let snapshot;
43+
if (specificDocumentId) {
44+
// If a specific document ID is provided, only process that document
45+
const docRef = documentsRef.doc(specificDocumentId);
46+
const docSnapshot = await docRef.get();
47+
48+
if (!docSnapshot.exists) {
49+
throw new functions.https.HttpsError('not-found', 'Document not found');
50+
}
51+
52+
// Create a QuerySnapshot-like object with a single document
53+
snapshot = {
54+
empty: false,
55+
size: 1,
56+
docs: [docSnapshot],
57+
forEach: (callback) => callback(docSnapshot)
58+
};
59+
} else {
60+
// Otherwise, get all documents that need processing
61+
const query = documentsRef.where('status', 'in', ['uploaded', 'extracted']);
62+
snapshot = await query.get();
63+
}
64+
65+
if (snapshot.empty) {
66+
return {
67+
success: false,
68+
message: 'No documents found for processing'
69+
};
70+
}
71+
72+
console.log(`Found ${snapshot.size} documents to process`);
73+
74+
// Process each document
75+
const processPromises = [];
76+
snapshot.forEach(doc => {
77+
const data = doc.data();
78+
79+
// If document is uploaded but not extracted, extract text
80+
if (data.status === 'uploaded' && data.filePath) {
81+
processPromises.push(extractTextFromPdf(userId, doc.id, data.filePath));
82+
}
83+
// If document is extracted but not processed, update status
84+
else if (data.status === 'extracted') {
85+
const docRef = db.collection('users').doc(userId).collection('documents').doc(doc.id);
86+
processPromises.push(docRef.update({
87+
status: 'processing',
88+
processingStartedAt: admin.firestore.FieldValue.serverTimestamp()
89+
}));
90+
}
91+
});
92+
93+
await Promise.all(processPromises);
94+
95+
// Once all documents are processed, format the data
96+
const formattedData = await formatDocumentsData(userId, true);
97+
98+
return {
99+
success: true,
100+
message: 'Documents processed successfully',
101+
documentsProcessed: snapshot.size
102+
};
103+
} catch (error) {
104+
console.error('Error processing documents:', error);
105+
throw new functions.https.HttpsError('internal', `Processing failed: ${error.message}`);
106+
}
107+
});
18108
exports.formatDocumentsData = functions.https.onCall(async (data, context) => {
19109
try {
20110
// Ensure user is authenticated
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const cors = require('cors')({ origin: true });
2+
3+
/**
4+
* Middleware to handle CORS for Firebase Cloud Functions
5+
* @param {Function} handler - The function handler to wrap with CORS support
6+
* @returns {Function} A function that handles CORS and then calls the handler
7+
*/
8+
const corsMiddleware = (handler) => {
9+
return (req, res) => {
10+
return cors(req, res, () => {
11+
return handler(req, res);
12+
});
13+
};
14+
};
15+
16+
module.exports = corsMiddleware;

0 commit comments

Comments
 (0)