update error

This commit is contained in:
5t4l1n
2025-07-28 00:51:24 +05:30
parent efd6708e5a
commit 7f6531b097
3 changed files with 400 additions and 352 deletions
+74 -52
View File
@@ -152,7 +152,7 @@ def get_db():
return client.openlearnx return client.openlearnx
# =================================================================== # ===================================================================
# ✅ ENHANCED DYNAMIC SCORING SYSTEM # ✅ ENHANCED DYNAMIC SCORING SYSTEM - CORRECTED VERSION
# =================================================================== # ===================================================================
def calculate_dynamic_score(code, language, problem): def calculate_dynamic_score(code, language, problem):
@@ -161,19 +161,29 @@ def calculate_dynamic_score(code, language, problem):
from contextlib import redirect_stdout, redirect_stderr from contextlib import redirect_stdout, redirect_stderr
import time import time
# Handle both old and new problem formats
test_cases = problem.get('test_cases', []) test_cases = problem.get('test_cases', [])
total_points = problem.get('total_points', 100) total_points = problem.get('total_points', 100)
# ✅ FIXED: Handle empty test cases properly
if not test_cases:
# Create a basic test case for simple execution
test_cases = [{
"input": "",
"expected_output": "",
"description": "Basic execution test",
"points": total_points
}]
start_time = time.time() start_time = time.time()
passed_tests = 0 passed_tests = 0
total_tests = len(test_cases) if test_cases else 1 total_tests = len(test_cases)
test_results = [] test_results = []
points_earned = 0 points_earned = 0
print(f"🧮 Enhanced Dynamic scoring - {total_tests} test cases, {total_points} total points") print(f"🧮 Enhanced Dynamic scoring - {total_tests} test cases, {total_points} total points")
try: try:
if test_cases:
for i, test_case in enumerate(test_cases): for i, test_case in enumerate(test_cases):
test_input = test_case.get('input', '') test_input = test_case.get('input', '')
expected_output = test_case.get('expected_output', '').strip() expected_output = test_case.get('expected_output', '').strip()
@@ -185,12 +195,19 @@ def calculate_dynamic_score(code, language, problem):
stdout_buffer = io.StringIO() stdout_buffer = io.StringIO()
stderr_buffer = io.StringIO() stderr_buffer = io.StringIO()
exec_globals = {"__builtins__": __builtins__} # ✅ ENHANCED: Better execution environment
exec_globals = {
"__builtins__": __builtins__,
"__name__": "__main__"
}
# Handle input simulation
if test_input: if test_input:
# Handle multiple input lines
input_lines = test_input.split('\n') if '\n' in test_input else [test_input] input_lines = test_input.split('\n') if '\n' in test_input else [test_input]
input_iter = iter(input_lines) input_iter = iter(input_lines)
exec_globals['input'] = lambda prompt='': next(input_iter, '') exec_globals['input'] = lambda prompt='': next(input_iter, '')
else:
exec_globals['input'] = lambda prompt='': ''
with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer): with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
exec(code, exec_globals) exec(code, exec_globals)
@@ -200,8 +217,20 @@ def calculate_dynamic_score(code, language, problem):
print(f"🔍 Test {i+1} - Actual: '{actual_output}', Expected: '{expected_output}'") print(f"🔍 Test {i+1} - Actual: '{actual_output}', Expected: '{expected_output}'")
# Enhanced comparison with tolerance for whitespace # ✅ ENHANCED: Better output comparison
if actual_output == expected_output or actual_output.replace(' ', '') == expected_output.replace(' ', ''): is_correct = False
if expected_output == "":
# For basic execution tests, just check if code runs without error
is_correct = stderr_content == ""
else:
# Compare outputs with tolerance for whitespace
is_correct = (
actual_output == expected_output or
actual_output.replace(' ', '') == expected_output.replace(' ', '') or
actual_output.lower().strip() == expected_output.lower().strip()
)
if is_correct:
passed_tests += 1 passed_tests += 1
points_earned += test_points points_earned += test_points
test_results.append({ test_results.append({
@@ -212,7 +241,7 @@ def calculate_dynamic_score(code, language, problem):
"actual_output": actual_output, "actual_output": actual_output,
"points_earned": test_points, "points_earned": test_points,
"description": test_case.get('description', f'Test case {i+1}'), "description": test_case.get('description', f'Test case {i+1}'),
"execution_time": time.time() - start_time "execution_time": round(time.time() - start_time, 3)
}) })
print(f"✅ Test {i+1} PASSED - {test_points} points earned") print(f"✅ Test {i+1} PASSED - {test_points} points earned")
else: else:
@@ -236,44 +265,12 @@ def calculate_dynamic_score(code, language, problem):
"passed": False, "passed": False,
"input": test_input, "input": test_input,
"expected_output": expected_output, "expected_output": expected_output,
"actual_output": f"Error: {str(e)}", "actual_output": f"Runtime Error: {str(e)}",
"points_earned": 0, "points_earned": 0,
"error": str(e), "error": str(e),
"description": test_case.get('description', f'Test case {i+1}'), "description": test_case.get('description', f'Test case {i+1}'),
"error_type": type(e).__name__ "error_type": type(e).__name__
}) })
else:
# Fallback: Basic execution test
try:
stdout_buffer = io.StringIO()
stderr_buffer = io.StringIO()
with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
exec(code, {"__builtins__": __builtins__})
passed_tests = 1
points_earned = total_points
test_results = [{
"test_number": 1,
"passed": True,
"input": "",
"expected_output": "Code should execute without errors",
"actual_output": stdout_buffer.getvalue().strip(),
"points_earned": total_points,
"description": "Basic execution test"
}]
except Exception as e:
test_results = [{
"test_number": 1,
"passed": False,
"input": "",
"expected_output": "Code should execute without errors",
"actual_output": f"Error: {str(e)}",
"points_earned": 0,
"error": str(e),
"description": "Basic execution test",
"error_type": type(e).__name__
}]
except Exception as e: except Exception as e:
print(f"❌ Scoring system error: {str(e)}") print(f"❌ Scoring system error: {str(e)}")
@@ -288,6 +285,7 @@ def calculate_dynamic_score(code, language, problem):
"description": "Scoring system error", "description": "Scoring system error",
"error_type": type(e).__name__ "error_type": type(e).__name__
}] }]
total_tests = 1
execution_time = time.time() - start_time execution_time = time.time() - start_time
final_score = int((points_earned / total_points) * 100) if total_points > 0 else 0 final_score = int((points_earned / total_points) * 100) if total_points > 0 else 0
@@ -303,7 +301,7 @@ def calculate_dynamic_score(code, language, problem):
'details': { 'details': {
'points_earned': points_earned, 'points_earned': points_earned,
'total_points': total_points, 'total_points': total_points,
'scoring_method': 'test_cases', 'scoring_method': 'enhanced_dynamic',
'language': language 'language': language
} }
} }
@@ -374,7 +372,7 @@ def generate_ai_quiz_direct():
def health_root(): def health_root():
return jsonify({ return jsonify({
"status": "OpenLearnX API running", "status": "OpenLearnX API running",
"version": "2.5.0 - ENHANCED ULTIMATE EDITION", "version": "2.6.0 - CORRECTED ULTIMATE EDITION",
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
"features": { "features": {
"mongodb": MONGO_SERVICE_AVAILABLE, "mongodb": MONGO_SERVICE_AVAILABLE,
@@ -384,13 +382,15 @@ def health_root():
"ai_quiz_service": AI_QUIZ_SERVICE_AVAILABLE, "ai_quiz_service": AI_QUIZ_SERVICE_AVAILABLE,
"docker": check_docker_availability(), "docker": check_docker_availability(),
"dynamic_scoring": True, "dynamic_scoring": True,
"ultimate_leaderboard_fix": True, "enhanced_scoring": True,
"exam_submission": True,
"adaptive_quiz": True, "adaptive_quiz": True,
"enhanced_security": True, "enhanced_security": True,
"ai_integration": AI_QUIZ_SERVICE_AVAILABLE "ai_integration": AI_QUIZ_SERVICE_AVAILABLE
}, },
"endpoints": { "endpoints": {
"exam": "/api/exam/*", "exam": "/api/exam/*",
"exam_submit": "/api/exam/submit-solution",
"quizzes": "/api/quizzes/*", "quizzes": "/api/quizzes/*",
"compiler": "/api/compiler/*", "compiler": "/api/compiler/*",
"ai_quiz": "/api/quizzes/generate-ai" if AI_QUIZ_SERVICE_AVAILABLE else "unavailable", "ai_quiz": "/api/quizzes/generate-ai" if AI_QUIZ_SERVICE_AVAILABLE else "unavailable",
@@ -410,9 +410,10 @@ def api_health():
"compiler": COMPILER_SERVICE_AVAILABLE, "compiler": COMPILER_SERVICE_AVAILABLE,
"ai_quiz_service": AI_QUIZ_SERVICE_AVAILABLE, "ai_quiz_service": AI_QUIZ_SERVICE_AVAILABLE,
"docker": check_docker_availability(), "docker": check_docker_availability(),
"ultimate_leaderboard_fix": True, "enhanced_scoring": True,
"exam_submission_fixed": True,
"adaptive_quiz": True, "adaptive_quiz": True,
"enhanced_version": "2.5.0" "enhanced_version": "2.6.0"
} }
# Enhanced MongoDB connection test # Enhanced MongoDB connection test
@@ -436,7 +437,6 @@ def api_health():
# AI service health check # AI service health check
if AI_QUIZ_SERVICE_AVAILABLE: if AI_QUIZ_SERVICE_AVAILABLE:
try: try:
# Quick test of AI service
services["ai_models_loaded"] = hasattr(ai_service, 'model_available') and ai_service.model_available services["ai_models_loaded"] = hasattr(ai_service, 'model_available') and ai_service.model_available
except Exception as e: except Exception as e:
services["ai_service_error"] = str(e) services["ai_service_error"] = str(e)
@@ -446,7 +446,7 @@ def api_health():
"services": services, "services": services,
"blueprints_registered": blueprints_registered, "blueprints_registered": blueprints_registered,
"blueprints_failed": blueprints_failed, "blueprints_failed": blueprints_failed,
"version": "2.5.0-enhanced" "version": "2.6.0-corrected"
}), 200 if status == "healthy" else 503 }), 200 if status == "healthy" else 503
# =================================================================== # ===================================================================
@@ -479,7 +479,14 @@ def not_found(e):
return jsonify({ return jsonify({
"error": "Not Found", "error": "Not Found",
"path": request.path, "path": request.path,
"method": request.method "method": request.method,
"available_endpoints": [
"/api/exam/submit-solution",
"/api/exam/create-exam",
"/api/exam/join-exam",
"/api/quizzes/*",
"/api/health"
]
}), 404 }), 404
@app.errorhandler(500) @app.errorhandler(500)
@@ -487,7 +494,8 @@ def internal_error(e):
logger.error(f"500 Error: {e}") logger.error(f"500 Error: {e}")
return jsonify({ return jsonify({
"error": "Internal Server Error", "error": "Internal Server Error",
"timestamp": datetime.now().isoformat() "timestamp": datetime.now().isoformat(),
"suggestion": "Check server logs for detailed error information"
}), 500 }), 500
# =================================================================== # ===================================================================
@@ -495,11 +503,23 @@ def internal_error(e):
# =================================================================== # ===================================================================
if __name__ == "__main__": if __name__ == "__main__":
print("🚀 Starting OpenLearnX Backend v2.5.0 - ENHANCED ULTIMATE EDITION") print("🚀 Starting OpenLearnX Backend v2.6.0 - CORRECTED ULTIMATE EDITION")
print("📚 Features: Enhanced Dynamic Scoring, AI Quiz Integration, Better Security") print("📚 Features: Enhanced Dynamic Scoring, AI Quiz Integration, Fixed Exam Submission")
print(f"🤖 AI Quiz Service: {'✅ Available' if AI_QUIZ_SERVICE_AVAILABLE else '❌ Unavailable'}") print(f"🤖 AI Quiz Service: {'✅ Available' if AI_QUIZ_SERVICE_AVAILABLE else '❌ Unavailable'}")
print(f"📊 MongoDB: {'✅ Available' if MONGO_SERVICE_AVAILABLE else '❌ Unavailable'}")
print(f"🔧 Enhanced Scoring: ✅ Available")
print("🌐 Server starting on http://0.0.0.0:5000") print("🌐 Server starting on http://0.0.0.0:5000")
# ✅ STARTUP VALIDATION
print("\n📋 Startup Validation:")
print(f" - Blueprints registered: {len(blueprints_registered)}")
if blueprints_failed:
print(f" - Blueprint failures: {len(blueprints_failed)}")
for prefix, error in blueprints_failed:
print(f"{prefix}: {error}")
print(f" - Database: {'✅ Connected' if MONGO_SERVICE_AVAILABLE else '❌ Disconnected'}")
print(f" - AI Service: {'✅ Ready' if AI_QUIZ_SERVICE_AVAILABLE else '❌ Not Available'}")
try: try:
app.run( app.run(
host="0.0.0.0", host="0.0.0.0",
@@ -511,3 +531,5 @@ if __name__ == "__main__":
print("\n👋 Server stopped by user") print("\n👋 Server stopped by user")
except Exception as e: except Exception as e:
print(f"❌ Server startup failed: {e}") print(f"❌ Server startup failed: {e}")
import traceback
traceback.print_exc()
+103 -15
View File
@@ -326,10 +326,10 @@ def start_exam():
print(f"❌ Error starting exam: {str(e)}") print(f"❌ Error starting exam: {str(e)}")
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
# ✅ MISSING ROUTE - This was causing the 404 error! # ✅ CRITICAL: The submit-solution route with enhanced debugging
@bp.route('/submit-solution', methods=['POST', 'OPTIONS']) @bp.route('/submit-solution', methods=['POST', 'OPTIONS'])
def submit_solution(): def submit_solution():
"""Submit coding solution for evaluation""" """Submit coding solution for evaluation - WITH DEBUG LOGGING"""
if request.method == "OPTIONS": if request.method == "OPTIONS":
response = jsonify({'status': 'ok'}) response = jsonify({'status': 'ok'})
response.headers.add("Access-Control-Allow-Origin", "*") response.headers.add("Access-Control-Allow-Origin", "*")
@@ -337,18 +337,81 @@ def submit_solution():
response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS") response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS")
return response return response
try:
# ✅ ENHANCED DEBUG LOGGING
print(f"🔍 ===== SUBMIT SOLUTION DEBUG =====")
print(f"🔍 Raw request data: {request.data}")
print(f"🔍 Content-Type: {request.headers.get('Content-Type')}")
print(f"🔍 Request form: {dict(request.form) if request.form else 'None'}")
print(f"🔍 Request args: {dict(request.args) if request.args else 'None'}")
print(f"🔍 Request method: {request.method}")
# Try to get JSON data
try: try:
data = request.get_json() data = request.get_json()
print(f"🔍 Parsed JSON data: {data}")
except Exception as json_error:
print(f"🔍 JSON parsing error: {json_error}")
data = None
# Check what we actually received
if not data:
print("❌ No JSON data received")
# Try alternative data sources
if request.form:
print("🔍 Trying to use form data instead...")
data = {
'exam_code': request.form.get('exam_code'),
'username': request.form.get('username'),
'code': request.form.get('code'),
'language': request.form.get('language', 'python'),
'problem_id': request.form.get('problem_id', 'problem_1')
}
print(f"🔍 Form data converted: {data}")
else:
return jsonify({
"success": False,
"error": "No JSON data received",
"debug_info": {
"content_type": request.headers.get('Content-Type'),
"raw_data": request.data.decode() if request.data else None,
"form_data": dict(request.form) if request.form else None,
"suggestion": "Make sure Content-Type is 'application/json' and data is valid JSON"
}
}), 400
# Extract fields with detailed logging
exam_code = data.get('exam_code') exam_code = data.get('exam_code')
username = data.get('username') username = data.get('username')
problem_id = data.get('problem_id', 'problem_1') problem_id = data.get('problem_id', 'problem_1')
code = data.get('code') code = data.get('code')
language = data.get('language', 'python') language = data.get('language', 'python')
if not all([exam_code, username, code]): print(f"🔍 Extracted fields:")
print(f" - exam_code: '{exam_code}' (type: {type(exam_code)})")
print(f" - username: '{username}' (type: {type(username)})")
print(f" - problem_id: '{problem_id}' (type: {type(problem_id)})")
print(f" - code: '{code[:50] if code else None}...' (length: {len(code) if code else 0})")
print(f" - language: '{language}' (type: {type(language)})")
# Enhanced validation with specific field checking
missing_fields = []
if not exam_code or str(exam_code).strip() == '':
missing_fields.append('exam_code')
if not username or str(username).strip() == '':
missing_fields.append('username')
if not code or str(code).strip() == '':
missing_fields.append('code')
if missing_fields:
print(f"❌ Missing fields: {missing_fields}")
return jsonify({ return jsonify({
"success": False, "success": False,
"error": "Missing required fields: exam_code, username, code" "error": f"Missing required fields: {', '.join(missing_fields)}",
"received_data": data,
"missing_fields": missing_fields,
"debug_info": "Check that your frontend is sending all required fields with non-empty values"
}), 400 }), 400
print(f"📝 Solution submission: {username} -> {exam_code} (Problem: {problem_id})") print(f"📝 Solution submission: {username} -> {exam_code} (Problem: {problem_id})")
@@ -356,8 +419,11 @@ def submit_solution():
# Find the exam # Find the exam
exam = db.exams.find_one({"exam_code": exam_code.upper()}) exam = db.exams.find_one({"exam_code": exam_code.upper()})
if not exam: if not exam:
print(f"❌ Exam not found: {exam_code}")
return jsonify({"success": False, "error": "Exam not found"}), 404 return jsonify({"success": False, "error": "Exam not found"}), 404
print(f"✅ Found exam: {exam['title']}")
# Find the specific problem (support both old and new format) # Find the specific problem (support both old and new format)
problem = None problem = None
if exam.get('problems'): if exam.get('problems'):
@@ -367,22 +433,29 @@ def submit_solution():
problem['id'] = 'problem_1' problem['id'] = 'problem_1'
if not problem: if not problem:
print(f"❌ Problem not found: {problem_id}")
return jsonify({"success": False, "error": "Problem not found"}), 404 return jsonify({"success": False, "error": "Problem not found"}), 404
print(f"✅ Found problem: {problem.get('title', 'Untitled')}")
# Use the enhanced dynamic scoring system from main.py # Use the enhanced dynamic scoring system from main.py
try: try:
from main import calculate_dynamic_score from main import calculate_dynamic_score
print(f"🧮 Running dynamic scoring system...")
result = calculate_dynamic_score(code, language, problem) result = calculate_dynamic_score(code, language, problem)
except ImportError: print(f"🏆 Scoring result: {result['score']}% ({result['passed_tests']}/{result['total_tests']} tests)")
except ImportError as e:
print(f"⚠️ Could not import scoring system from main.py: {e}")
# Fallback basic scoring if main function not available # Fallback basic scoring if main function not available
result = { result = {
'score': 50, # Default score 'score': 75, # Default score
'passed_tests': 1, 'passed_tests': 1,
'total_tests': 1, 'total_tests': 1,
'test_results': [{'passed': True, 'description': 'Basic test'}], 'test_results': [{'passed': True, 'description': 'Basic execution test', 'points_earned': 75}],
'execution_time': 0.1, 'execution_time': 0.1,
'details': {'points_earned': 50, 'total_points': 100} 'details': {'points_earned': 75, 'total_points': 100}
} }
print(f"🔄 Using fallback scoring: {result['score']}%")
# Create submission record # Create submission record
submission = { submission = {
@@ -404,22 +477,31 @@ def submit_solution():
# Save submission to submissions collection # Save submission to submissions collection
db.submissions.insert_one(submission) db.submissions.insert_one(submission)
print(f"💾 Submission saved to database")
# Update participant in exam # Update participant in exam
participant_update = { participant_update = {
"name": username,
"score": result['score'], "score": result['score'],
"completed": True, "completed": True,
"submission_time": datetime.now(), "submission_time": datetime.now(),
"language": language, "language": language,
"submission": code, "submission": code,
"test_results": result['test_results'] "test_results": result['test_results'],
"joined_at": datetime.now(),
"session_id": str(uuid.uuid4())
} }
exam_update_result = db.exams.update_one( exam_update_result = db.exams.update_one(
{"exam_code": exam_code.upper(), "participants.name": username}, {"exam_code": exam_code.upper(), "participants.name": username},
{"$set": {f"participants.$": {**participant_update, "name": username, "joined_at": datetime.now(), "session_id": str(uuid.uuid4())}}} {"$set": {f"participants.$": participant_update}}
) )
if exam_update_result.modified_count > 0:
print(f"✅ Updated participant {username} in exam")
else:
print(f"⚠️ Could not update participant {username} in exam - may not exist")
# Update participant leaderboard in separate collection # Update participant leaderboard in separate collection
participant_filter = {"exam_code": exam_code.upper(), "username": username} participant_filter = {"exam_code": exam_code.upper(), "username": username}
participant = db.participants.find_one(participant_filter) participant = db.participants.find_one(participant_filter)
@@ -449,6 +531,7 @@ def submit_solution():
} }
} }
) )
print(f"✅ Updated existing participant record")
else: else:
# Create new participant # Create new participant
new_participant = { new_participant = {
@@ -466,8 +549,9 @@ def submit_solution():
}] }]
} }
db.participants.insert_one(new_participant) db.participants.insert_one(new_participant)
print(f"✅ Created new participant record")
print(f"✅ Solution submitted: {result['score']}% ({result['passed_tests']}/{result['total_tests']} tests)") print(f"✅ Solution submitted successfully: {result['score']}% ({result['passed_tests']}/{result['total_tests']} tests)")
return jsonify({ return jsonify({
"success": True, "success": True,
@@ -479,7 +563,9 @@ def submit_solution():
"test_results": result['test_results'], "test_results": result['test_results'],
"execution_time": result['execution_time'], "execution_time": result['execution_time'],
"points_earned": result['details']['points_earned'], "points_earned": result['details']['points_earned'],
"total_points": result['details']['total_points'] "total_points": result['details']['total_points'],
"language": language,
"problem_id": problem_id
} }
}) })
@@ -487,7 +573,11 @@ def submit_solution():
print(f"❌ Submission error: {str(e)}") print(f"❌ Submission error: {str(e)}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return jsonify({"success": False, "error": str(e)}), 500 return jsonify({
"success": False,
"error": f"Server error: {str(e)}",
"error_type": type(e).__name__
}), 500
@bp.route("/leaderboard/<exam_code>", methods=["GET", "OPTIONS"]) @bp.route("/leaderboard/<exam_code>", methods=["GET", "OPTIONS"])
def get_leaderboard(exam_code): def get_leaderboard(exam_code):
@@ -654,7 +744,6 @@ def get_host_dashboard(exam_code):
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
# ✅ FIXED: Remove duplicate definition
@bp.route('/info/<exam_code>', methods=['GET', 'OPTIONS']) @bp.route('/info/<exam_code>', methods=['GET', 'OPTIONS'])
def get_exam_info(exam_code): def get_exam_info(exam_code):
"""Get detailed information about an exam for the host panel""" """Get detailed information about an exam for the host panel"""
@@ -805,7 +894,6 @@ def stop_exam():
print(f"❌ Error stopping exam: {str(e)}") print(f"❌ Error stopping exam: {str(e)}")
return jsonify({"success": False, "error": str(e)}), 500 return jsonify({"success": False, "error": str(e)}), 500
# ✅ FIXED: Remove duplicate definition
@bp.route('/upload-question', methods=['POST', 'OPTIONS']) @bp.route('/upload-question', methods=['POST', 'OPTIONS'])
def upload_question(): def upload_question():
"""Host uploads a custom question to their exam""" """Host uploads a custom question to their exam"""
+169 -231
View File
@@ -1,6 +1,6 @@
'use client' 'use client'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect, useCallback, useRef } from 'react'
import { useRouter } from 'next/navigation' import { useRouter, useParams } from 'next/navigation'
import { Trophy, Clock, Users, Send, RefreshCw, Play, Code, Wallet, Shield, TestTube } from 'lucide-react' import { Trophy, Clock, Users, Send, RefreshCw, Play, Code, Wallet, Shield, TestTube } from 'lucide-react'
interface Participant { interface Participant {
@@ -34,6 +34,12 @@ interface ExamSession {
} }
export default function EnhancedExamInterface() { export default function EnhancedExamInterface() {
const router = useRouter()
const params = useParams()
// ✅ FIXED: Get exam code from URL params
const examCode = params.examCode as string
const [examSession, setExamSession] = useState<ExamSession | null>(null) const [examSession, setExamSession] = useState<ExamSession | null>(null)
const [problem, setProblem] = useState<Problem | null>(null) const [problem, setProblem] = useState<Problem | null>(null)
const [selectedLanguage, setSelectedLanguage] = useState('python') const [selectedLanguage, setSelectedLanguage] = useState('python')
@@ -48,54 +54,23 @@ export default function EnhancedExamInterface() {
const [hasSubmitted, setHasSubmitted] = useState(false) const [hasSubmitted, setHasSubmitted] = useState(false)
const [examStats, setExamStats] = useState<any>({}) const [examStats, setExamStats] = useState<any>({})
const [timerInitialized, setTimerInitialized] = useState(false) const [timerInitialized, setTimerInitialized] = useState(false)
const router = useRouter()
// ✅ CRITICAL FIX: Use refs to prevent infinite loops
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const timerRef = useRef<NodeJS.Timeout | null>(null)
const refreshTimeoutRefs = useRef<NodeJS.Timeout[]>([])
const isInitializedRef = useRef(false)
const languageIcons: {[key: string]: string} = { const languageIcons: {[key: string]: string} = {
python: '🐍', python: '🐍',
java: '☕', java: '☕',
javascript: '🟨',
c: '⚡', c: '⚡',
bash: '💻' bash: '💻'
} }
useEffect(() => { // ✅ FIXED: Memoized functions to prevent recreation
const sessionData = localStorage.getItem('exam_session') const fetchProblem = useCallback(async (examCode: string) => {
if (!sessionData) {
router.push('/coding/join')
return
}
const session = JSON.parse(sessionData)
setExamSession(session)
// Fetch problem details
fetchProblem(session.exam_code)
// More frequent polling for real-time updates
const interval = setInterval(() => {
fetchLeaderboard(session.exam_code)
}, 2000)
return () => clearInterval(interval)
}, [router])
// Timer countdown
useEffect(() => {
if (!timerInitialized || timeRemaining <= 0) return
const timer = setInterval(() => {
setTimeRemaining(prev => {
const newTime = Math.max(0, prev - 1)
if (newTime === 0) {
alert('⏰ Time is up! Exam has ended.')
}
return newTime
})
}, 1000)
return () => clearInterval(timer)
}, [timerInitialized, timeRemaining])
const fetchProblem = async (examCode: string) => {
try { try {
const response = await fetch(`http://127.0.0.1:5000/api/exam/get-problem/${examCode}`) const response = await fetch(`http://127.0.0.1:5000/api/exam/get-problem/${examCode}`)
const data = await response.json() const data = await response.json()
@@ -109,24 +84,15 @@ export default function EnhancedExamInterface() {
} catch (error) { } catch (error) {
console.error('Failed to fetch problem:', error) console.error('Failed to fetch problem:', error)
} }
} }, [])
// ✅ ENHANCED: More aggressive leaderboard fetching with better debugging const fetchLeaderboard = useCallback(async (examCode: string) => {
const fetchLeaderboard = async (examCode: string) => {
try { try {
console.log('🏆 Fetching leaderboard for:', examCode) console.log('🏆 Fetching leaderboard for:', examCode)
// Add cache busting to prevent stale data
const response = await fetch(`http://127.0.0.1:5000/api/exam/leaderboard/${examCode}?t=${Date.now()}`) const response = await fetch(`http://127.0.0.1:5000/api/exam/leaderboard/${examCode}?t=${Date.now()}`)
const data = await response.json() const data = await response.json()
console.log('📦 Leaderboard data received:', {
success: data.success,
completed_count: data.leaderboard?.length || 0,
waiting_count: data.waiting_participants?.length || 0,
ultimate_fix_applied: data.ultimate_fix_applied
})
if (data.success) { if (data.success) {
setLeaderboard(data.leaderboard || []) setLeaderboard(data.leaderboard || [])
setWaitingParticipants(data.waiting_participants || []) setWaitingParticipants(data.waiting_participants || [])
@@ -146,41 +112,116 @@ export default function EnhancedExamInterface() {
} }
} }
// ✅ ENHANCED: Better user status checking // Check user status - only once to prevent loops
const currentUser = examSession?.student_name if (!hasSubmitted && examSession?.student_name) {
if (currentUser) { const userInCompleted = data.leaderboard.find((p: Participant) => p.name === examSession.student_name)
const userInCompleted = data.leaderboard.find((p: Participant) => p.name === currentUser) if (userInCompleted) {
const userInWaiting = data.waiting_participants.find((p: Participant) => p.name === currentUser) console.log('✅ User found in completed leaderboard')
console.log(`👤 User status check:`, {
username: currentUser,
in_completed: !!userInCompleted,
in_waiting: !!userInWaiting,
current_hasSubmitted: hasSubmitted,
user_score: userInCompleted?.score
})
if (userInCompleted && !hasSubmitted) {
console.log('✅ User found in completed leaderboard, updating hasSubmitted state')
setHasSubmitted(true) setHasSubmitted(true)
} }
} }
// Debug logging for leaderboard content
if (data.leaderboard.length > 0) {
console.log('🏆 Completed participants:', data.leaderboard.map((p: any) => `${p.name}: ${p.score}%`))
}
if (data.waiting_participants.length > 0) {
console.log('⏳ Waiting participants:', data.waiting_participants.map((p: any) => p.name))
}
} else {
console.error('❌ Leaderboard fetch failed:', data.error)
} }
} catch (error) { } catch (error) {
console.error('❌ Failed to fetch leaderboard:', error) console.error('❌ Failed to fetch leaderboard:', error)
} }
}, [hasSubmitted, examSession?.student_name, timerInitialized])
// ✅ FIXED: Initialization effect - runs only once
useEffect(() => {
if (!examCode || isInitializedRef.current) return
console.log('🚀 Initializing exam interface...')
isInitializedRef.current = true
// Initialize session
const sessionData = localStorage.getItem('exam_session')
if (!sessionData) {
const newSession = {
exam_code: examCode,
student_name: localStorage.getItem('student_name') || 'Anonymous',
exam_info: {}
} }
setExamSession(newSession)
} else {
const session = JSON.parse(sessionData)
if (session.exam_code !== examCode) {
session.exam_code = examCode
}
setExamSession(session)
}
// Fetch initial data
fetchProblem(examCode)
fetchLeaderboard(examCode)
return () => {
console.log('🛑 Cleaning up initialization effect')
}
}, [examCode, fetchProblem, fetchLeaderboard])
// ✅ FIXED: Separate effect for polling - controlled interval
useEffect(() => {
if (!examCode || !examSession) return
console.log('📡 Starting leaderboard polling...')
// Clear any existing interval
if (intervalRef.current) {
clearInterval(intervalRef.current)
}
// Set up polling interval - less aggressive
intervalRef.current = setInterval(() => {
fetchLeaderboard(examCode)
}, 5000) // ✅ REDUCED: Changed from 2000ms to 5000ms
return () => {
console.log('🛑 Cleaning up polling interval')
if (intervalRef.current) {
clearInterval(intervalRef.current)
intervalRef.current = null
}
}
}, [examCode, examSession, fetchLeaderboard])
// ✅ FIXED: Timer effect - separate and controlled
useEffect(() => {
if (!timerInitialized || timeRemaining <= 0) return
console.log('⏱️ Starting timer...')
// Clear any existing timer
if (timerRef.current) {
clearInterval(timerRef.current)
}
timerRef.current = setInterval(() => {
setTimeRemaining(prev => {
const newTime = Math.max(0, prev - 1)
if (newTime === 0) {
alert('⏰ Time is up! Exam has ended.')
}
return newTime
})
}, 1000)
return () => {
console.log('🛑 Cleaning up timer')
if (timerRef.current) {
clearInterval(timerRef.current)
timerRef.current = null
}
}
}, [timerInitialized, timeRemaining > 0]) // ✅ FIXED: Better dependency
// ✅ FIXED: Cleanup all timeouts on unmount
useEffect(() => {
return () => {
console.log('🛑 Component unmounting - cleaning up all timeouts')
refreshTimeoutRefs.current.forEach(timeout => clearTimeout(timeout))
refreshTimeoutRefs.current = []
}
}, [])
const handleLanguageChange = (language: string) => { const handleLanguageChange = (language: string) => {
setSelectedLanguage(language) setSelectedLanguage(language)
@@ -228,31 +269,41 @@ export default function EnhancedExamInterface() {
} }
} }
// ✅ COMPLETELY FIXED SUBMIT SOLUTION with aggressive leaderboard refresh // ✅ FIXED: Submit solution with controlled refresh
const submitSolution = async () => { const submitSolution = async () => {
if (!code.trim()) { if (!code.trim()) {
alert('Please write some code before submitting!') alert('Please write some code before submitting!')
return return
} }
if (!examSession?.student_name) {
alert('Student name is missing. Please refresh and try again.')
return
}
if (!confirm('Submit your solution? This cannot be undone.')) return if (!confirm('Submit your solution? This cannot be undone.')) return
setIsSubmitting(true) setIsSubmitting(true)
try { try {
console.log('📤 Submitting solution...') console.log('📤 Submitting solution...')
console.log('👤 Participant:', examSession?.student_name)
console.log('🔢 Exam Code:', examSession?.exam_code) const submissionData = {
exam_code: examCode,
username: examSession.student_name,
code: code,
language: selectedLanguage,
problem_id: "problem_1"
}
console.log('🔍 Submitting data:', submissionData)
const response = await fetch('http://127.0.0.1:5000/api/exam/submit-solution', { const response = await fetch('http://127.0.0.1:5000/api/exam/submit-solution', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
body: JSON.stringify({ 'Content-Type': 'application/json'
exam_code: examSession?.exam_code, },
language: selectedLanguage, body: JSON.stringify(submissionData)
code: code,
participant_name: examSession?.student_name || 'Anonymous'
})
}) })
const data = await response.json() const data = await response.json()
@@ -260,99 +311,35 @@ export default function EnhancedExamInterface() {
if (data.success) { if (data.success) {
setHasSubmitted(true) setHasSubmitted(true)
setTestResults(data.test_results || []) setTestResults(data.result?.test_results || [])
// ✅ ENHANCED: Detailed alert with proper test results formatting
let alertMessage = `🎉 Solution submitted successfully!\n\n` let alertMessage = `🎉 Solution submitted successfully!\n\n`
alertMessage += `📊 Overall Score: ${data.score}%\n` alertMessage += `📊 Overall Score: ${data.result?.score || 0}%\n`
alertMessage += `✅ Tests Passed: ${data.passed_tests}/${data.total_tests}\n` alertMessage += `✅ Tests Passed: ${data.result?.passed_tests || 0}/${data.result?.total_tests || 1}\n`
if (data.execution_time) { if (data.result?.execution_time) {
alertMessage += `⏱️ Execution Time: ${data.execution_time}s\n` alertMessage += `⏱️ Execution Time: ${data.result.execution_time}s\n`
} }
// Enhanced test results display in alert alertMessage += `\n🏆 Check the leaderboard for your ranking!`
if (data.test_results && data.test_results.length > 0) {
alertMessage += `\n📋 Detailed Test Results:\n`
alertMessage += `${'='.repeat(30)}\n`
data.test_results.forEach((test: any, i: number) => {
const status = test.passed ? '✅ PASSED' : '❌ FAILED'
const points = test.points_earned || 0
alertMessage += `Test ${i+1}: ${status} (+${points} points)\n`
if (test.description && test.description !== `Test case ${i+1}`) {
alertMessage += ` Description: ${test.description}\n`
}
if (test.input) {
alertMessage += ` Input: "${test.input}"\n`
}
if (test.expected_output) {
alertMessage += ` Expected: "${test.expected_output}"\n`
}
if (test.actual_output) {
alertMessage += ` Your Output: "${test.actual_output}"\n`
}
if (!test.passed && test.error) {
alertMessage += ` Error: ${test.error}\n`
}
alertMessage += `\n`
})
// Add summary
const totalPoints = data.test_results.reduce((sum: number, test: any) => sum + (test.points_earned || 0), 0)
const maxPoints = data.scoring_details?.total_points || 100
alertMessage += `📈 Points Earned: ${totalPoints}/${maxPoints}\n`
}
alertMessage += `\n🏆 Your score will appear in the leaderboard shortly!`
alert(alertMessage) alert(alertMessage)
// ✅ CRITICAL FIX: Aggressive leaderboard refresh sequence // ✅ FIXED: Controlled refresh sequence - clear previous timeouts
console.log('🔄 Starting aggressive leaderboard refresh sequence...') console.log('🔄 Starting controlled leaderboard refresh...')
// Immediate refresh // Clear any existing refresh timeouts
setTimeout(() => { refreshTimeoutRefs.current.forEach(timeout => clearTimeout(timeout))
console.log('🔄 Refresh 1/6 - Immediate') refreshTimeoutRefs.current = []
fetchLeaderboard(examSession!.exam_code)
}, 200)
// Quick follow-up // Single immediate refresh
setTimeout(() => { fetchLeaderboard(examCode)
console.log('🔄 Refresh 2/6 - Quick follow-up')
fetchLeaderboard(examSession!.exam_code)
}, 800)
// Medium delay // One follow-up refresh after 3 seconds
setTimeout(() => { const refreshTimeout = setTimeout(() => {
console.log('🔄 Refresh 3/6 - Medium delay') fetchLeaderboard(examCode)
fetchLeaderboard(examSession!.exam_code) }, 3000)
}, 2000)
// Longer delay refreshTimeoutRefs.current.push(refreshTimeout)
setTimeout(() => {
console.log('🔄 Refresh 4/6 - Longer delay')
fetchLeaderboard(examSession!.exam_code)
}, 4000)
// Extended delay
setTimeout(() => {
console.log('🔄 Refresh 5/6 - Extended delay')
fetchLeaderboard(examSession!.exam_code)
}, 7000)
// Final refresh
setTimeout(() => {
console.log('🔄 Refresh 6/6 - Final check')
fetchLeaderboard(examSession!.exam_code)
}, 10000)
} else { } else {
alert(`❌ Submission failed: ${data.error}`) alert(`❌ Submission failed: ${data.error}`)
@@ -366,7 +353,13 @@ export default function EnhancedExamInterface() {
} }
} }
// ✅ Enhanced Test Results Display Component // ✅ Manual refresh function
const manualRefresh = useCallback(() => {
console.log('🔄 Manual refresh triggered')
fetchLeaderboard(examCode)
}, [examCode, fetchLeaderboard])
// Test Results Display Component
const TestResultsDisplay = ({ results }: { results: any[] }) => { const TestResultsDisplay = ({ results }: { results: any[] }) => {
if (!results || results.length === 0) return null if (!results || results.length === 0) return null
@@ -439,46 +432,10 @@ export default function EnhancedExamInterface() {
</div> </div>
))} ))}
</div> </div>
{/* Summary */}
<div className="mt-4 p-3 bg-blue-900 bg-opacity-50 rounded">
<div className="flex justify-between text-sm">
<span>
Passed: {results.filter(r => r.passed).length}/{results.length} tests
</span>
<span>
Points: {results.reduce((sum, r) => sum + (r.points_earned || 0), 0)} total
</span>
</div>
</div>
</div> </div>
) )
} }
// Debug function for troubleshooting
const debugLeaderboard = async () => {
try {
const response = await fetch(`http://127.0.0.1:5000/api/exam/leaderboard/${examSession?.exam_code}`)
const data = await response.json()
console.log('🐛 DEBUG LEADERBOARD:', {
success: data.success,
completed_count: data.leaderboard?.length || 0,
waiting_count: data.waiting_participants?.length || 0,
my_name: examSession?.student_name,
in_completed: data.leaderboard?.find((p: any) => p.name === examSession?.student_name),
in_waiting: data.waiting_participants?.find((p: any) => p.name === examSession?.student_name),
ultimate_fix_applied: data.ultimate_fix_applied,
full_leaderboard: data.leaderboard,
full_waiting: data.waiting_participants
})
alert(`Debug Info:\nCompleted: ${data.leaderboard?.length || 0}\nWaiting: ${data.waiting_participants?.length || 0}\nCheck console for details`)
} catch (error) {
console.error('Debug error:', error)
}
}
const formatTime = (seconds: number) => { const formatTime = (seconds: number) => {
if (seconds < 0) return "00:00" if (seconds < 0) return "00:00"
@@ -514,7 +471,7 @@ export default function EnhancedExamInterface() {
<div className="max-w-7xl mx-auto flex justify-between items-center"> <div className="max-w-7xl mx-auto flex justify-between items-center">
<div> <div>
<h1 className="text-xl font-bold">{problem.title}</h1> <h1 className="text-xl font-bold">{problem.title}</h1>
<p className="text-gray-400">Code: {examSession.exam_code} | Participant: {examSession.student_name}</p> <p className="text-gray-400">Code: {examCode} | Participant: {examSession.student_name}</p>
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
@@ -669,7 +626,7 @@ export default function EnhancedExamInterface() {
)} )}
</div> </div>
{/* ✅ Enhanced Test Results Display */} {/* Test Results Display */}
{testResults.length > 0 && ( {testResults.length > 0 && (
<TestResultsDisplay results={testResults} /> <TestResultsDisplay results={testResults} />
)} )}
@@ -683,24 +640,13 @@ export default function EnhancedExamInterface() {
<h3 className="text-xl font-bold">Live Leaderboard</h3> <h3 className="text-xl font-bold">Live Leaderboard</h3>
</div> </div>
<div className="flex items-center space-x-2">
<button <button
onClick={() => fetchLeaderboard(examSession.exam_code)} onClick={manualRefresh}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded" className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded"
title="Refresh" title="Refresh"
> >
<RefreshCw className="h-4 w-4" /> <RefreshCw className="h-4 w-4" />
</button> </button>
{/* Debug button - remove in production */}
<button
onClick={debugLeaderboard}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded"
title="Debug"
>
🐛
</button>
</div>
</div> </div>
{/* Stats */} {/* Stats */}
@@ -713,14 +659,6 @@ export default function EnhancedExamInterface() {
<div className="text-2xl font-bold text-green-400">{Math.round(examStats.average_score || 0)}%</div> <div className="text-2xl font-bold text-green-400">{Math.round(examStats.average_score || 0)}%</div>
<div className="text-xs text-gray-400">Avg Score</div> <div className="text-xs text-gray-400">Avg Score</div>
</div> </div>
<div className="bg-gray-900 p-3 rounded">
<div className="text-2xl font-bold text-purple-400">{examStats.highest_score || 0}%</div>
<div className="text-xs text-gray-400">Top Score</div>
</div>
<div className="bg-gray-900 p-3 rounded">
<div className="text-2xl font-bold text-orange-400">{examStats.waiting_submissions || 0}</div>
<div className="text-xs text-gray-400">Working</div>
</div>
</div> </div>
{/* Leaderboard Display */} {/* Leaderboard Display */}