Spaces:
Running
Running
| from flask import Flask, request, jsonify | |
| from flask_cors import CORS | |
| import os | |
| from dotenv import load_dotenv | |
| # Import our Agents | |
| from agents.puzzle_agent import PuzzleAgent | |
| from agents.adventure_agent import AdventureAgent | |
| from agents.adaptive_agent import AdaptiveAgent | |
| from agents.hint_agent import HintAgent | |
| # Load environment variables. Do not override space secrets (like GEMINI_API_KEY) already set in the environment. | |
| if os.environ.get("GEMINI_API_KEY"): | |
| load_dotenv(override=False) | |
| else: | |
| load_dotenv(override=True) | |
| app = Flask(__name__) | |
| CORS(app) # Enable CORS for all routes | |
| # Initialize Agents | |
| puzzle_agent = PuzzleAgent() | |
| adventure_agent = AdventureAgent() | |
| adaptive_agent = AdaptiveAgent() | |
| hint_agent = HintAgent() | |
| def home(): | |
| return jsonify({ | |
| "status": "online", | |
| "message": "CodeCracker AI Backend is Running!", | |
| "endpoints": { | |
| "health_check": "/health", | |
| "generate_level": "/api/level/generate", | |
| "feedback": "/api/level/feedback" | |
| } | |
| }) | |
| def health_check(): | |
| return jsonify({"status": "healthy", "service": "CodeCracker AI Backend"}) | |
| def ping(): | |
| """ | |
| Lightweight keep-alive endpoint. | |
| Call this every 25 minutes from an external cron (e.g. cron-job.org, UptimeRobot) | |
| to prevent the HF Space from going to sleep on the free tier. | |
| URL to ping: https://faaizashiq-codecracker-backend.hf.space/ping | |
| """ | |
| return jsonify({"pong": True}) | |
| def generate_level(): | |
| """ | |
| Generates a new level (Maze, Blockly, or Time Challenge). | |
| Expected JSON: { "mode": "maze", "difficulty": "Easy", "topic": "Space" (optional) } | |
| """ | |
| data = request.json or {} | |
| mode = data.get('mode', 'maze') | |
| difficulty = data.get('difficulty', 'Easy') | |
| topic = data.get('topic') | |
| try: | |
| if mode == 'maze': | |
| level_data = puzzle_agent.generate_level(difficulty, topic) | |
| else: | |
| level_data = adventure_agent.generate_level(mode, difficulty, topic) | |
| if not level_data: | |
| raise Exception("Empty level data returned") | |
| # Validate the response has the right shape before sending | |
| # The agent's own fallback already returns {"story_arc_title":..., "levels":[...]} | |
| # so this should always pass. Log a warning if it doesn't. | |
| if mode != 'maze' and 'levels' not in level_data: | |
| print(f"WARNING: agent returned data without 'levels' key: {list(level_data.keys())}") | |
| return jsonify(level_data) | |
| except Exception as e: | |
| print(f"CRITICAL ERROR /api/level/generate: {e}") | |
| # Return a mode-appropriate fallback — NOT a maze object for blockly requests | |
| if mode == 'blockly': | |
| fallback = { | |
| "story_arc_title": "System Offline Arc", | |
| "levels": [ | |
| { | |
| "type": "blockly", | |
| "level_id": f"emergency_{i}", | |
| "title": f"System Repair Part {i}", | |
| "story": "The AI systems are offline. Practice mode engaged.", | |
| "concept_tutorial": "Loops: Repeat actions without writing code over and over.", | |
| "problem": "Create a loop that prints 1, 2, 3.", | |
| "toolbox_categories": ["Loops", "Math", "Variables", "Text"], | |
| "initial_code": "", | |
| "validation_rules": { | |
| "required_concepts": ["loop"], | |
| "expected_output": "1\n2\n3" | |
| }, | |
| "hint_1": "Use the 'repeat' block.", | |
| "hint_2": "Set the number to 3." | |
| } | |
| for i in range(1, 6) | |
| ] | |
| } | |
| elif mode == 'time_challenge': | |
| fallback = { | |
| "story_arc_title": "System Reboot Arc", | |
| "levels": [ | |
| { | |
| "type": "time_challenge", | |
| "level_id": f"emergency_tc_{i}", | |
| "title": f"System Reboot Part {i}", | |
| "story": "Critical Error! Debug the startup sequence.", | |
| "concept_tutorial": "Syntax: Code must follow strict grammar rules.", | |
| "timer_seconds": 60, | |
| "buggy_code": "print('System Ready'\nstart_engine()", | |
| "task": "Fix the syntax error.", | |
| "solution_patch": "print('System Ready')\nstart_engine()" | |
| } | |
| for i in range(1, 6) | |
| ] | |
| } | |
| else: | |
| # maze fallback — single level is fine here | |
| fallback = { | |
| "story_arc_title": "Emergency Training", | |
| "levels": [{ | |
| "type": "maze", | |
| "level_id": "emergency_fallback", | |
| "title": "System Practice", | |
| "story": "System offline. Practice mode engaged.", | |
| "grid_layout": [[2, 0, 3]], | |
| "maze_rows": 1, | |
| "maze_cols": 3, | |
| "ordered_steps": [], | |
| "cell_values": {}, | |
| "maze_rule": {"type": "none"}, | |
| "allowed_blocks": ["move_right"], | |
| "toolbox_xml": '<category name="Movement" colour="230"><block type="move_right"></block></category>', | |
| "theme_palette": {"wall_color": "#8E24AA", "path_color": "#020817", "accent": "#FF477E"} | |
| }] | |
| } | |
| return jsonify(fallback) | |
| def level_feedback(): | |
| """ | |
| Receives user feedback (1-5 stars) on a level. | |
| Expected JSON: { "level_data": {...}, "rating": 5 } | |
| """ | |
| data = request.json or {} | |
| level_data = data.get('level_data') | |
| rating = data.get('rating') | |
| developer_feedback = data.get('developer_feedback') | |
| if not level_data or not rating: | |
| return jsonify({"message": "Invalid data ignored"}), 400 | |
| try: | |
| # Teach the agent! | |
| if level_data.get('type') == 'maze': | |
| puzzle_agent.learn_from_feedback(level_data, int(rating), developer_feedback) | |
| else: | |
| adventure_agent.learn_from_feedback(level_data, int(rating), developer_feedback) | |
| return jsonify({"status": "learned", "message": "Thanks for the feedback!"}) | |
| except Exception as e: | |
| print(f"Feedback Error: {e}") | |
| return jsonify({"status": "ignored"}), 200 | |
| def evaluate_player(): | |
| """ | |
| Evaluates player history to adjust difficulty. | |
| Expected JSON: { "history": [ { "result": "success", "time_taken": 30 }, ... ] } | |
| """ | |
| data = request.json or {} | |
| history = data.get('history', []) | |
| try: | |
| evaluation = adaptive_agent.evaluate_user(history) | |
| return jsonify(evaluation) | |
| except Exception as e: | |
| print(f"CRITICAL ERROR /api/player/evaluate: {e}") | |
| # Fallback: Default to current or Easy | |
| return jsonify({"difficulty": "Easy", "reason": "System requires calibration."}) | |
| def generate_hint(): | |
| """ | |
| Generates a context-aware hint. | |
| Expected JSON: { "mode": "maze|blockly", "level_context": {...}, "user_state": {...} } | |
| """ | |
| data = request.json or {} | |
| mode = data.get('mode', 'blockly') | |
| user_state = data.get('user_state', {}) | |
| try: | |
| hint = hint_agent.generate_hint( | |
| mode=mode, | |
| level_context=data.get('level_context', {}), | |
| user_state=user_state | |
| ) | |
| return jsonify({"hint": hint}) | |
| except Exception as e: | |
| print(f"CRITICAL ERROR /api/hint/generate: {e}") | |
| return jsonify({"hint": "Try checking your logic step-by-step."}) | |
| if __name__ == '__main__': | |
| port = int(os.environ.get('PORT', 5000)) | |
| app.run(host='0.0.0.0', port=port, debug=True) | |