faaizashiq's picture
Update backend/main.py
f671a54 verified
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()
@app.route('/', methods=['GET'])
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"
}
})
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({"status": "healthy", "service": "CodeCracker AI Backend"})
@app.route('/ping', methods=['GET'])
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})
@app.route('/api/level/generate', methods=['POST'])
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)
@app.route('/api/level/feedback', methods=['POST'])
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
@app.route('/api/player/evaluate', methods=['POST'])
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."})
@app.route('/api/hint/generate', methods=['POST'])
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)