Spaces:
Runtime error
Runtime error
| # remove warnings | |
| import warnings | |
| import argparse | |
| warnings.filterwarnings("ignore") | |
| from flask import Flask, render_template, jsonify, request | |
| import json | |
| import os | |
| from datetime import datetime | |
| import glob | |
| import re | |
| from together import Together | |
| app = Flask(__name__) | |
| # Initialize Together client (will be set when API key is available) | |
| together_client = None | |
| def get_together_client(): | |
| """Get or initialize the Together client""" | |
| global together_client | |
| if together_client is None: | |
| api_key = os.getenv("TOGETHER_API_KEY") | |
| if not api_key: | |
| raise ValueError("TOGETHER_API_KEY environment variable is required") | |
| together_client = Together(api_key=api_key) | |
| return together_client | |
| def generate_quiz_content(topic): | |
| """Generate quiz content using Together AI""" | |
| client = get_together_client() | |
| prompt = f"""Create a comprehensive quiz about "{topic}" in JSON format. The quiz should have exactly: | |
| - 2 concept cards that explain key concepts | |
| - 8 multiple choice questions with 4 options each | |
| Format the response as a valid JSON object with this exact structure: | |
| {{ | |
| "info": {{ | |
| "title": "Quiz Title", | |
| "description": "Brief description of the quiz", | |
| "created_date": "{datetime.now().strftime('%Y-%m-%d')}", | |
| "difficulty": "Beginner|Intermediate|Advanced", | |
| "tags": ["tag1", "tag2", "tag3"] | |
| }}, | |
| "cards": [ | |
| {{ | |
| "type": "concept", | |
| "content": {{ | |
| "heading": "Concept Title", | |
| "sentence1": "First explanatory sentence", | |
| "sentence2": "Second explanatory sentence", | |
| "sentence3": "Third explanatory sentence" | |
| }} | |
| }}, | |
| {{ | |
| "type": "quiz", | |
| "question": "Question text?", | |
| "choices": ["Option A", "Option B", "Option C", "Option D"], | |
| "answer": "Correct option text", | |
| "justification": "Explanation of why this is correct" | |
| }} | |
| ] | |
| }} | |
| Make sure: | |
| 1. All questions are educational and factual | |
| 2. Each question has exactly 4 choices | |
| 3. The answer matches exactly one of the choices | |
| 4. Justifications are clear and educational | |
| 5. Concept cards provide valuable background information | |
| 6. The JSON is valid and properly formatted | |
| Topic: {topic}""" | |
| try: | |
| response = client.chat.completions.create( | |
| model="meta-llama/Meta-Llama-3-8B-Instruct-Lite", | |
| messages=[{"role": "user", "content": prompt}], | |
| max_tokens=3000, | |
| temperature=0.7, | |
| ) | |
| content = response.choices[0].message.content.strip() | |
| # Extract JSON from the response (in case there's extra text) | |
| json_match = re.search(r"\{.*\}", content, re.DOTALL) | |
| if json_match: | |
| content = json_match.group() | |
| # Parse and validate JSON | |
| quiz_data = json.loads(content) | |
| # Validate structure | |
| if not all(key in quiz_data for key in ["info", "cards"]): | |
| raise ValueError("Invalid quiz structure") | |
| # Count concepts and questions | |
| concepts = [ | |
| card for card in quiz_data["cards"] if card.get("type") == "concept" | |
| ] | |
| questions = [card for card in quiz_data["cards"] if card.get("type") == "quiz"] | |
| # If we don't have exactly 2 concepts or 8 questions, adjust | |
| while len(concepts) < 2: | |
| concepts.append( | |
| { | |
| "type": "concept", | |
| "content": { | |
| "heading": f"{topic} Concept {len(concepts) + 1}", | |
| "sentence1": f"This is an important concept related to {topic}.", | |
| "sentence2": f"Understanding this concept is crucial for mastering {topic}.", | |
| "sentence3": f"This knowledge will help you better understand {topic} applications.", | |
| }, | |
| } | |
| ) | |
| while len(questions) < 8: | |
| questions.append( | |
| { | |
| "type": "quiz", | |
| "question": f"What is an important aspect of {topic}?", | |
| "choices": ["Option A", "Option B", "Option C", "Option D"], | |
| "answer": "Option A", | |
| "justification": f"This is a fundamental aspect of {topic}.", | |
| } | |
| ) | |
| # Rebuild cards with correct counts | |
| quiz_data["cards"] = concepts[:2] + questions[:8] | |
| return quiz_data | |
| except Exception as e: | |
| print(f"Error generating quiz content: {e}") | |
| # Return a fallback quiz structure | |
| return { | |
| "info": { | |
| "title": f"{topic} Quiz", | |
| "description": f"A comprehensive quiz about {topic}", | |
| "created_date": datetime.now().strftime("%Y-%m-%d"), | |
| "difficulty": "Intermediate", | |
| "tags": [topic.lower()], | |
| }, | |
| "cards": [ | |
| { | |
| "type": "concept", | |
| "content": { | |
| "heading": f"Introduction to {topic}", | |
| "sentence1": f"{topic} is an important subject with many applications.", | |
| "sentence2": f"Understanding {topic} requires studying its fundamental principles.", | |
| "sentence3": f"This quiz will test your knowledge of {topic} concepts.", | |
| }, | |
| }, | |
| { | |
| "type": "concept", | |
| "content": { | |
| "heading": f"Key Principles of {topic}", | |
| "sentence1": f"The key principles of {topic} form the foundation of the subject.", | |
| "sentence2": f"These principles are essential for practical applications of {topic}.", | |
| "sentence3": f"Mastering these principles will improve your understanding of {topic}.", | |
| }, | |
| }, | |
| ] | |
| + [ | |
| { | |
| "type": "quiz", | |
| "question": f"What is a fundamental aspect of {topic}?", | |
| "choices": [ | |
| f"Basic principles of {topic}", | |
| f"Advanced applications only", | |
| f"Historical context only", | |
| f"None of the above", | |
| ], | |
| "answer": f"Basic principles of {topic}", | |
| "justification": f"Understanding basic principles is fundamental to learning {topic}.", | |
| } | |
| ] | |
| * 8, # Repeat to get 8 questions | |
| } | |
| def load_quiz_data(filename="ai_quiz.json"): | |
| """Load quiz data from JSON file""" | |
| try: | |
| filepath = os.path.join("data", filename) | |
| with open(filepath, "r") as f: | |
| return json.load(f) | |
| except FileNotFoundError: | |
| return { | |
| "info": { | |
| "title": "Quiz Not Found", | |
| "description": "The quiz file could not be loaded.", | |
| }, | |
| "cards": [], | |
| } | |
| def get_all_quizzes(): | |
| """Get metadata for all quiz files in the data directory""" | |
| quiz_files = glob.glob("data/*.json") | |
| quizzes = [] | |
| for file_path in quiz_files: | |
| try: | |
| with open(file_path, "r") as f: | |
| quiz_data = json.load(f) | |
| filename = os.path.basename(file_path) | |
| # Extract metadata | |
| info = quiz_data.get("info", {}) | |
| quiz_count = len( | |
| [ | |
| card | |
| for card in quiz_data.get("cards", []) | |
| if card.get("type") == "quiz" | |
| ] | |
| ) | |
| quiz_info = { | |
| "filename": filename, | |
| "title": info.get("title", "Untitled Quiz"), | |
| "description": info.get("description", "No description available"), | |
| "created_date": info.get("created_date", "2024-01-01"), | |
| "difficulty": info.get("difficulty", "Unknown"), | |
| "tags": info.get("tags", []), | |
| "question_count": quiz_count, | |
| } | |
| quizzes.append(quiz_info) | |
| except (json.JSONDecodeError, FileNotFoundError): | |
| continue | |
| # Sort by date (newest first) | |
| quizzes.sort( | |
| key=lambda x: datetime.strptime(x["created_date"], "%Y-%m-%d"), reverse=True | |
| ) | |
| return quizzes | |
| def index_page(): | |
| """Serve the main quiz page with quiz selection grid""" | |
| return render_template("index.html") | |
| def get_quizzes_list(): | |
| """API endpoint to get all available quizzes""" | |
| quizzes = get_all_quizzes() | |
| return jsonify(quizzes) | |
| def get_quiz_data(filename): | |
| """API endpoint to get quiz data""" | |
| quiz_data = load_quiz_data(filename) | |
| return jsonify(quiz_data) | |
| def get_default_quiz(): | |
| """API endpoint to get the default quiz data""" | |
| quiz_data = load_quiz_data() | |
| return jsonify(quiz_data) | |
| def generate_quiz(): | |
| """API endpoint to generate and save a new quiz""" | |
| try: | |
| data = request.get_json() | |
| topic = data.get("topic", "").strip() | |
| if not topic: | |
| return jsonify({"error": "Topic is required"}), 400 | |
| # Generate quiz content | |
| quiz_data = generate_quiz_content(topic) | |
| # Create filename from topic | |
| safe_topic = re.sub(r"[^a-zA-Z0-9\s]", "", topic).strip() | |
| safe_topic = re.sub(r"\s+", "_", safe_topic).lower() | |
| filename = f"{safe_topic}_quiz.json" | |
| # Ensure data directory exists | |
| data_dir = "data" | |
| if not os.path.exists(data_dir): | |
| os.makedirs(data_dir) | |
| # Save to file | |
| filepath = os.path.join(data_dir, filename) | |
| with open(filepath, "w") as f: | |
| json.dump(quiz_data, f, indent=2) | |
| return jsonify( | |
| { | |
| "success": True, | |
| "filename": filename, | |
| "message": f"Quiz about '{topic}' generated successfully!", | |
| } | |
| ) | |
| except ValueError as e: | |
| if "TOGETHER_API_KEY" in str(e): | |
| return jsonify({"error": "Together AI API key not configured"}), 500 | |
| return jsonify({"error": str(e)}), 400 | |
| except Exception as e: | |
| print(f"Error in generate_quiz: {e}") | |
| return jsonify({"error": "Failed to generate quiz"}), 500 | |
| if __name__ == "__main__": | |
| # args port | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--port", type=int, default=7860) | |
| args = parser.parse_args() | |
| app.run(debug=True, port=args.port) | |