Spaces:
Sleeping
Sleeping
| # modules/flashcard_generator.py | |
| """Enhanced Flashcard Generator Module""" | |
| from typing import Dict, List, Optional | |
| from modules.api_utils import ( | |
| fetch_wikipedia_summary, | |
| fetch_wikibooks_content, | |
| fetch_wikipedia_categories, | |
| fetch_related_topics, | |
| ) | |
| import random | |
| import hashlib # For generating unique IDs | |
| def _generate_item_id(content: str) -> str: | |
| """Generates a unique ID for a flashcard based on its content.""" | |
| return hashlib.sha256(content.encode()).hexdigest() | |
| def generate_flashcard_types(topic: str, difficulty: str) -> Dict: | |
| """Generate different types of flashcards based on difficulty""" | |
| summary_data = fetch_wikipedia_summary(topic) | |
| if not summary_data: | |
| return {"error": "Topic not found", "status": False} | |
| title = summary_data.get("title", topic) | |
| extract = summary_data.get("extract", "") | |
| description = summary_data.get("description", "") | |
| # Also try to get Wikibooks content for educational material | |
| wikibooks_content = fetch_wikibooks_content(topic) | |
| flashcard_types = { | |
| "Easy": { | |
| "front": f"What is {title}?", | |
| "back": extract[:200] + "..." if len(extract) > 200 else extract, | |
| "type": "definition", | |
| }, | |
| "Medium": { | |
| "front": f"Explain the key concepts of {title}", | |
| "back": extract[:300] + "..." if len(extract) > 300 else extract, | |
| "type": "conceptual", | |
| }, | |
| "Hard": { | |
| "front": f"How does {title} relate to its field and what are its applications?", | |
| "back": extract | |
| + ( | |
| f"\n\nAdditional context: {wikibooks_content[:200]}" | |
| if wikibooks_content | |
| else "" | |
| ), | |
| "type": "analytical", | |
| }, | |
| } | |
| flashcard = flashcard_types.get(difficulty, flashcard_types["Easy"]) | |
| # Add categories as tags | |
| categories = fetch_wikipedia_categories(title) | |
| # Generate item_id | |
| item_id = _generate_item_id(flashcard["front"] + flashcard["back"]) | |
| return { | |
| "item_id": item_id, # Add unique ID | |
| "front": flashcard["front"], | |
| "back": flashcard["back"], | |
| "hint": description, | |
| "topic": title, | |
| "difficulty": difficulty, | |
| "type": flashcard["type"], | |
| "tags": categories[:3] if categories else [], | |
| "status": True, | |
| } | |
| def generate_flashcard(topic: str, difficulty: str = "Medium", adaptive_engine=None) -> Dict: | |
| """Generate an enhanced flashcard for a given topic, prioritizing review items.""" | |
| # First, check for items due for review if adaptive engine is provided | |
| if adaptive_engine: | |
| review_item_ids = adaptive_engine.get_items_due_for_review(topic=topic, limit=1) | |
| if review_item_ids: | |
| # If a review item is found, we need to reconstruct the flashcard data | |
| # This assumes that the item_id is sufficient to retrieve the original flashcard content | |
| # For simplicity, we'll generate a new one for that topic, ideally, you'd load it from storage | |
| # For this implementation, we'll try to generate a flashcard for that specific item's topic | |
| # A more robust solution would store flashcard content with the item_id in the AdaptiveEngine data | |
| # For now, let's just use the topic associated with the item_id in adaptive engine | |
| # This requires adaptive engine to store topic with item_id | |
| item_data = adaptive_engine.data["item_performance"].get(review_item_ids[0]) | |
| if item_data and item_data.get("topic"): | |
| return generate_flashcard_types(item_data["topic"], difficulty) | |
| # If no review items, or if adaptive_engine is not provided, generate a new flashcard | |
| return generate_flashcard_types(topic, difficulty) | |
| def generate_flashcard_deck( | |
| topics: List[str], difficulty: str = "Medium", adaptive_engine=None | |
| ) -> List[Dict]: | |
| """Generate a deck of flashcards, incorporating spaced repetition.""" | |
| deck = [] | |
| # Prioritize review items if adaptive engine is provided | |
| if adaptive_engine: | |
| review_item_ids = adaptive_engine.get_items_due_for_review(limit=len(topics) * 2) # Get more than needed | |
| for item_id in review_item_ids: | |
| item_data = adaptive_engine.data["item_performance"].get(item_id) | |
| if item_data and item_data.get("topic"): | |
| card = generate_flashcard_types(item_data["topic"], item_data.get("difficulty", "Medium")) | |
| if card.get("status") and card["item_id"] == item_id: # Ensure the generated card matches the item_id | |
| deck.append(card) | |
| if len(deck) >= len(topics): # Fill up to requested deck size | |
| break | |
| # Fill the rest of the deck with new flashcards if needed | |
| current_deck_size = len(deck) | |
| if current_deck_size < len(topics): | |
| new_topics_needed = len(topics) - current_deck_size | |
| new_topics = random.sample(topics, min(new_topics_needed, len(topics))) # Pick random topics for new cards | |
| for topic_name in new_topics: | |
| card = generate_flashcard_types(topic_name, difficulty) | |
| if card.get("status") and card not in deck: # Avoid duplicates | |
| deck.append(card) | |
| if len(deck) >= len(topics): | |
| break | |
| # Ensure all cards have a unique card_number and total_cards | |
| for i, card in enumerate(deck): | |
| card["card_number"] = i + 1 | |
| card["total_cards"] = len(deck) | |
| random.shuffle(deck) # Shuffle the final deck | |
| return deck | |
| def generate_smart_deck( | |
| main_topic: str, deck_size: int = 10, difficulty: str = "Medium", adaptive_engine=None | |
| ) -> List[Dict]: | |
| """Generate a smart deck with related topics, incorporating spaced repetition.""" | |
| # Start with the main topic | |
| topics_pool = [main_topic] | |
| # Add related topics | |
| related = fetch_related_topics(main_topic, deck_size - 1) | |
| topics_pool.extend(related) | |
| # Add some random topics to ensure variety | |
| topics_pool.extend(random.sample(topics_pool, min(5, len(topics_pool)))) # Add some random from pool | |
| # Remove duplicates and shuffle | |
| topics_pool = list(set(topics_pool)) | |
| random.shuffle(topics_pool) | |
| # Generate the deck using the new generate_flashcard_deck | |
| deck = generate_flashcard_deck(topics_pool[:deck_size], difficulty, adaptive_engine) | |
| return deck | |