chore: time slider added
Browse files- Frontend/src/pages/quize.tsx +55 -30
Frontend/src/pages/quize.tsx
CHANGED
|
@@ -6,13 +6,13 @@ import {
|
|
| 6 |
Upload,
|
| 7 |
X,
|
| 8 |
Loader2,
|
|
|
|
| 9 |
} from "lucide-react";
|
| 10 |
import MCQQuizPage from "../components/quize/mcq";
|
| 11 |
import API from "../api/api"; // Import your Axios instance
|
| 12 |
import * as pdfjsLib from "pdfjs-dist";
|
| 13 |
|
| 14 |
// Initialize PDF worker
|
| 15 |
-
// In a Vite setup, pointing to a CDN is often the most stable way to avoid build config issues
|
| 16 |
pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;
|
| 17 |
|
| 18 |
const CodingQuizPage = ({ onBack }: any) => (
|
|
@@ -34,9 +34,12 @@ const ResumeGeneratedQuize: React.FC = () => {
|
|
| 34 |
const [uploadedFile, setUploadedFile] = useState<string | null>(null);
|
| 35 |
const [fileError, setFileError] = useState<string | null>(null);
|
| 36 |
const [fileObject, setFileObject] = useState<File | null>(null);
|
| 37 |
-
const [isProcessing, setIsProcessing] = useState(false);
|
| 38 |
|
|
|
|
| 39 |
const [customPrompt, setCustomPrompt] = useState("");
|
|
|
|
|
|
|
| 40 |
const [quizData, setQuizData] = useState(null);
|
| 41 |
|
| 42 |
const resumeInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -98,8 +101,6 @@ const ResumeGeneratedQuize: React.FC = () => {
|
|
| 98 |
};
|
| 99 |
|
| 100 |
// ---------- GENERATE QUIZ ----------
|
| 101 |
-
// --- START: Replace your existing generateQuiz function ---
|
| 102 |
-
|
| 103 |
const generateQuiz = async () => {
|
| 104 |
if (!fileObject || !quizType) return;
|
| 105 |
|
|
@@ -108,6 +109,7 @@ const ResumeGeneratedQuize: React.FC = () => {
|
|
| 108 |
|
| 109 |
try {
|
| 110 |
// 1. Extract text on the client side
|
|
|
|
| 111 |
const extractedText = await extractTextFromPDF(fileObject);
|
| 112 |
|
| 113 |
if (!extractedText.trim()) {
|
|
@@ -118,41 +120,33 @@ const ResumeGeneratedQuize: React.FC = () => {
|
|
| 118 |
|
| 119 |
// 2. Prepare Payload matching Backend `Quiz_input` schema
|
| 120 |
const payload = {
|
| 121 |
-
// Ensure extractedText is cleaned up to prevent large string issues
|
| 122 |
parsed_doc: extractedText.trim(),
|
| 123 |
-
// Ensure user_prompt is always a valid string
|
| 124 |
user_prompt:
|
| 125 |
customPrompt.trim() || "Generate a quiz based on this content.",
|
| 126 |
};
|
| 127 |
|
| 128 |
// 3. Send to Backend
|
| 129 |
-
// The URL is now correct: /api/v1/quiz/resume
|
| 130 |
const response = await API.post("/quiz/resume", payload);
|
| 131 |
|
| 132 |
-
// Success
|
| 133 |
setQuizData(response.data);
|
| 134 |
setShowQuiz(true);
|
| 135 |
} catch (error: any) {
|
| 136 |
console.error("Quiz Generation Error:", error);
|
| 137 |
-
|
| 138 |
-
// Extract detailed error message from FastAPI response body
|
| 139 |
let errorMessage = "Failed to generate quiz. Check login status.";
|
|
|
|
| 140 |
if (error.response?.data?.detail) {
|
| 141 |
-
// Check if the server returned a validation error list or a simple string
|
| 142 |
if (typeof error.response.data.detail === "string") {
|
| 143 |
errorMessage = error.response.data.detail;
|
| 144 |
} else if (
|
| 145 |
Array.isArray(error.response.data.detail) &&
|
| 146 |
error.response.data.detail.length > 0
|
| 147 |
) {
|
| 148 |
-
// Pydantic validation error format
|
| 149 |
const firstError = error.response.data.detail[0];
|
| 150 |
errorMessage = `Validation Error: Field '${firstError.loc.join(
|
| 151 |
" -> "
|
| 152 |
)}' ${firstError.msg}`;
|
| 153 |
}
|
| 154 |
} else if (error.code === "ERR_BAD_REQUEST") {
|
| 155 |
-
// General network/Axios 400 error
|
| 156 |
errorMessage = "Server rejected the data. Are you logged in?";
|
| 157 |
}
|
| 158 |
|
|
@@ -162,12 +156,16 @@ const ResumeGeneratedQuize: React.FC = () => {
|
|
| 162 |
}
|
| 163 |
};
|
| 164 |
|
| 165 |
-
// --- END: Replace your existing generateQuiz function ---
|
| 166 |
-
|
| 167 |
// Load quiz UI
|
| 168 |
if (showQuiz) {
|
| 169 |
if (quizType === "mcq")
|
| 170 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
if (quizType === "coding")
|
| 172 |
return (
|
| 173 |
<CodingQuizPage data={quizData} onBack={() => setShowQuiz(false)} />
|
|
@@ -290,20 +288,47 @@ const ResumeGeneratedQuize: React.FC = () => {
|
|
| 290 |
2. Configure & Generate
|
| 291 |
</h3>
|
| 292 |
|
| 293 |
-
<div className="bg-slate-900/60 rounded-2xl shadow-2xl p-6 border border-slate-800 mb-8">
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
</div>
|
| 308 |
</div>
|
| 309 |
</div>
|
|
|
|
| 6 |
Upload,
|
| 7 |
X,
|
| 8 |
Loader2,
|
| 9 |
+
Clock,
|
| 10 |
} from "lucide-react";
|
| 11 |
import MCQQuizPage from "../components/quize/mcq";
|
| 12 |
import API from "../api/api"; // Import your Axios instance
|
| 13 |
import * as pdfjsLib from "pdfjs-dist";
|
| 14 |
|
| 15 |
// Initialize PDF worker
|
|
|
|
| 16 |
pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;
|
| 17 |
|
| 18 |
const CodingQuizPage = ({ onBack }: any) => (
|
|
|
|
| 34 |
const [uploadedFile, setUploadedFile] = useState<string | null>(null);
|
| 35 |
const [fileError, setFileError] = useState<string | null>(null);
|
| 36 |
const [fileObject, setFileObject] = useState<File | null>(null);
|
| 37 |
+
const [isProcessing, setIsProcessing] = useState(false);
|
| 38 |
|
| 39 |
+
// Configuration State
|
| 40 |
const [customPrompt, setCustomPrompt] = useState("");
|
| 41 |
+
const [duration, setDuration] = useState(15); // Default 15 minutes
|
| 42 |
+
|
| 43 |
const [quizData, setQuizData] = useState(null);
|
| 44 |
|
| 45 |
const resumeInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
| 101 |
};
|
| 102 |
|
| 103 |
// ---------- GENERATE QUIZ ----------
|
|
|
|
|
|
|
| 104 |
const generateQuiz = async () => {
|
| 105 |
if (!fileObject || !quizType) return;
|
| 106 |
|
|
|
|
| 109 |
|
| 110 |
try {
|
| 111 |
// 1. Extract text on the client side
|
| 112 |
+
console.log("Extracting text from PDF...");
|
| 113 |
const extractedText = await extractTextFromPDF(fileObject);
|
| 114 |
|
| 115 |
if (!extractedText.trim()) {
|
|
|
|
| 120 |
|
| 121 |
// 2. Prepare Payload matching Backend `Quiz_input` schema
|
| 122 |
const payload = {
|
|
|
|
| 123 |
parsed_doc: extractedText.trim(),
|
|
|
|
| 124 |
user_prompt:
|
| 125 |
customPrompt.trim() || "Generate a quiz based on this content.",
|
| 126 |
};
|
| 127 |
|
| 128 |
// 3. Send to Backend
|
|
|
|
| 129 |
const response = await API.post("/quiz/resume", payload);
|
| 130 |
|
|
|
|
| 131 |
setQuizData(response.data);
|
| 132 |
setShowQuiz(true);
|
| 133 |
} catch (error: any) {
|
| 134 |
console.error("Quiz Generation Error:", error);
|
|
|
|
|
|
|
| 135 |
let errorMessage = "Failed to generate quiz. Check login status.";
|
| 136 |
+
|
| 137 |
if (error.response?.data?.detail) {
|
|
|
|
| 138 |
if (typeof error.response.data.detail === "string") {
|
| 139 |
errorMessage = error.response.data.detail;
|
| 140 |
} else if (
|
| 141 |
Array.isArray(error.response.data.detail) &&
|
| 142 |
error.response.data.detail.length > 0
|
| 143 |
) {
|
|
|
|
| 144 |
const firstError = error.response.data.detail[0];
|
| 145 |
errorMessage = `Validation Error: Field '${firstError.loc.join(
|
| 146 |
" -> "
|
| 147 |
)}' ${firstError.msg}`;
|
| 148 |
}
|
| 149 |
} else if (error.code === "ERR_BAD_REQUEST") {
|
|
|
|
| 150 |
errorMessage = "Server rejected the data. Are you logged in?";
|
| 151 |
}
|
| 152 |
|
|
|
|
| 156 |
}
|
| 157 |
};
|
| 158 |
|
|
|
|
|
|
|
| 159 |
// Load quiz UI
|
| 160 |
if (showQuiz) {
|
| 161 |
if (quizType === "mcq")
|
| 162 |
+
return (
|
| 163 |
+
<MCQQuizPage
|
| 164 |
+
data={quizData}
|
| 165 |
+
onBack={() => setShowQuiz(false)}
|
| 166 |
+
totalTimeSeconds={duration * 60} // Pass user selected time
|
| 167 |
+
/>
|
| 168 |
+
);
|
| 169 |
if (quizType === "coding")
|
| 170 |
return (
|
| 171 |
<CodingQuizPage data={quizData} onBack={() => setShowQuiz(false)} />
|
|
|
|
| 288 |
2. Configure & Generate
|
| 289 |
</h3>
|
| 290 |
|
| 291 |
+
<div className="bg-slate-900/60 rounded-2xl shadow-2xl p-6 border border-slate-800 mb-8 space-y-6">
|
| 292 |
+
{/* Custom Prompt Input */}
|
| 293 |
+
<div>
|
| 294 |
+
<label className="text-lg font-semibold block mb-3 text-gray-200">
|
| 295 |
+
Custom Prompt/Instructions (Optional)
|
| 296 |
+
</label>
|
| 297 |
+
<textarea
|
| 298 |
+
rows={3}
|
| 299 |
+
value={customPrompt}
|
| 300 |
+
onChange={(e) => setCustomPrompt(e.target.value)}
|
| 301 |
+
placeholder="e.g., 'Focus on Python only'"
|
| 302 |
+
className="w-full p-3 rounded-lg bg-black/40 border border-slate-700 text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-600 transition"
|
| 303 |
+
></textarea>
|
| 304 |
+
<p className="text-gray-500 text-sm mt-2">
|
| 305 |
+
This prompt influences the quiz generation.
|
| 306 |
+
</p>
|
| 307 |
+
</div>
|
| 308 |
+
|
| 309 |
+
{/* NEW: Duration Slider */}
|
| 310 |
+
<div>
|
| 311 |
+
<label className="text-lg font-semibold block mb-3 text-gray-200 flex items-center justify-between">
|
| 312 |
+
<span className="flex items-center gap-2">
|
| 313 |
+
<Clock className="w-5 h-5 text-blue-400" /> Quiz Duration
|
| 314 |
+
</span>
|
| 315 |
+
<span className="text-blue-400 font-bold">{duration} min</span>
|
| 316 |
+
</label>
|
| 317 |
+
<input
|
| 318 |
+
type="range"
|
| 319 |
+
min="1"
|
| 320 |
+
max="60"
|
| 321 |
+
step="5"
|
| 322 |
+
value={duration}
|
| 323 |
+
onChange={(e) => setDuration(parseInt(e.target.value))}
|
| 324 |
+
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500"
|
| 325 |
+
/>
|
| 326 |
+
<div className="flex justify-between text-xs text-gray-500 mt-2">
|
| 327 |
+
<span>5 min</span>
|
| 328 |
+
<span>30 min</span>
|
| 329 |
+
<span>60 min</span>
|
| 330 |
+
</div>
|
| 331 |
+
</div>
|
| 332 |
</div>
|
| 333 |
</div>
|
| 334 |
</div>
|