faaizashiq commited on
Commit
d14d6bd
·
verified ·
1 Parent(s): 9d913f9

Update backend/main.py

Browse files
Files changed (1) hide show
  1. backend/main.py +134 -86
backend/main.py CHANGED
@@ -1,6 +1,5 @@
1
- from fastapi import FastAPI, Request
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from fastapi.responses import JSONResponse
4
  import os
5
  from dotenv import load_dotenv
6
 
@@ -11,19 +10,10 @@ from agents.adaptive_agent import AdaptiveAgent
11
  from agents.hint_agent import HintAgent
12
 
13
  # Load environment variables
14
- load_dotenv(override=True)
15
 
16
- # Initialize FastAPI instead of Flask
17
- app = FastAPI()
18
-
19
- # Enable CORS for all routes
20
- app.add_middleware(
21
- CORSMiddleware,
22
- allow_origins=["*"],
23
- allow_credentials=True,
24
- allow_methods=["*"],
25
- allow_headers=["*"],
26
- )
27
 
28
  # Initialize Agents
29
  puzzle_agent = PuzzleAgent()
@@ -31,79 +21,139 @@ adventure_agent = AdventureAgent()
31
  adaptive_agent = AdaptiveAgent()
32
  hint_agent = HintAgent()
33
 
34
- @app.get('/')
35
  def home():
36
- return {
37
  "status": "online",
38
- "message": "CodeCracker AI Backend is Running on FastAPI!",
39
  "endpoints": {
40
  "health_check": "/health",
41
  "generate_level": "/api/level/generate",
42
  "feedback": "/api/level/feedback"
43
  }
44
- }
45
 
46
- @app.get('/health')
47
  def health_check():
48
- return {"status": "healthy", "service": "CodeCracker AI Backend"}
49
 
50
- @app.get('/ping')
51
  def ping():
52
- """ Keep-alive endpoint """
53
- return {"pong": True}
 
 
 
 
 
54
 
55
- @app.post('/api/level/generate')
56
- async def generate_level(request: Request):
57
- """ Generates a new level (Maze, Blockly, or Time Challenge). """
58
- try:
59
- data = await request.json()
60
- except:
61
- data = {}
62
-
63
  mode = data.get('mode', 'maze')
64
  difficulty = data.get('difficulty', 'Easy')
65
- topic = data.get('topic')
66
-
67
  try:
68
  if mode == 'maze':
69
- # Puzzle mode: curriculum mazes
70
  level_data = puzzle_agent.generate_level(difficulty, topic)
71
  else:
72
  level_data = adventure_agent.generate_level(mode, difficulty, topic)
73
-
74
  if not level_data:
75
- raise Exception("Empty level data returned")
76
-
77
- return JSONResponse(content=level_data)
78
-
 
 
 
 
 
 
79
  except Exception as e:
80
  print(f"CRITICAL ERROR /api/level/generate: {e}")
81
- # Final Safety Net: Return a basic Maze fallback if everything else explodes
82
- fallback = {
83
- "type": "maze",
84
- "level_id": "emergency_fallback",
85
- "title": "System Practice",
86
- "story": "System offline. Practice mode engaged.",
87
- "grid_layout": [[2,0,3]],
88
- "allowed_blocks": ["move_forward"],
89
- "tutorial_text": "Move to the goal."
90
- }
91
- return JSONResponse(content=fallback)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- @app.post('/api/level/feedback')
94
- async def level_feedback(request: Request):
95
- """ Receives user feedback (1-5 stars) on a level. """
96
- try:
97
- data = await request.json()
98
- except:
99
- data = {}
100
-
101
  level_data = data.get('level_data')
102
  rating = data.get('rating')
103
  developer_feedback = data.get('developer_feedback')
104
 
105
  if not level_data or not rating:
106
- return JSONResponse(content={"message": "Invalid data ignored"}, status_code=400)
107
 
108
  try:
109
  # Teach the agent!
@@ -112,51 +162,49 @@ async def level_feedback(request: Request):
112
  else:
113
  adventure_agent.learn_from_feedback(level_data, int(rating), developer_feedback)
114
 
115
- return {"status": "learned", "message": "Thanks for the feedback!"}
116
  except Exception as e:
117
  print(f"Feedback Error: {e}")
118
- return {"status": "ignored"}
119
 
120
- @app.post('/api/player/evaluate')
121
- async def evaluate_player(request: Request):
122
- """ Evaluates player history to adjust difficulty. """
123
- try:
124
- data = await request.json()
125
- except:
126
- data = {}
127
-
128
  history = data.get('history', [])
129
 
130
  try:
131
  evaluation = adaptive_agent.evaluate_user(history)
132
- return evaluation
133
  except Exception as e:
134
  print(f"CRITICAL ERROR /api/player/evaluate: {e}")
135
- return {"difficulty": "Easy", "reason": "System requires calibration."}
 
136
 
137
- @app.post('/api/hint/generate')
138
- async def generate_hint(request: Request):
139
- """ Generates a context-aware hint. """
140
- try:
141
- data = await request.json()
142
- except:
143
- data = {}
144
-
145
  mode = data.get('mode', 'blockly')
146
  user_state = data.get('user_state', {})
147
-
148
  try:
149
  hint = hint_agent.generate_hint(
150
  mode=mode,
151
  level_context=data.get('level_context', {}),
152
  user_state=user_state
153
  )
154
- return {"hint": hint}
155
  except Exception as e:
156
  print(f"CRITICAL ERROR /api/hint/generate: {e}")
157
- return {"hint": "Try checking your logic step-by-step."}
158
 
159
  if __name__ == '__main__':
160
- import uvicorn
161
- port = int(os.environ.get('PORT', 7860))
162
- uvicorn.run(app, host='0.0.0.0', port=port)
 
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
 
3
  import os
4
  from dotenv import load_dotenv
5
 
 
10
  from agents.hint_agent import HintAgent
11
 
12
  # Load environment variables
13
+ load_dotenv(override=True) # backend/.env wins over stale shell GEMINI_API_KEY
14
 
15
+ app = Flask(__name__)
16
+ CORS(app) # Enable CORS for all routes
 
 
 
 
 
 
 
 
 
17
 
18
  # Initialize Agents
19
  puzzle_agent = PuzzleAgent()
 
21
  adaptive_agent = AdaptiveAgent()
22
  hint_agent = HintAgent()
23
 
24
+ @app.route('/', methods=['GET'])
25
  def home():
26
+ return jsonify({
27
  "status": "online",
28
+ "message": "CodeCracker AI Backend is Running!",
29
  "endpoints": {
30
  "health_check": "/health",
31
  "generate_level": "/api/level/generate",
32
  "feedback": "/api/level/feedback"
33
  }
34
+ })
35
 
36
+ @app.route('/health', methods=['GET'])
37
  def health_check():
38
+ return jsonify({"status": "healthy", "service": "CodeCracker AI Backend"})
39
 
40
+ @app.route('/ping', methods=['GET'])
41
  def ping():
42
+ """
43
+ Lightweight keep-alive endpoint.
44
+ Call this every 25 minutes from an external cron (e.g. cron-job.org, UptimeRobot)
45
+ to prevent the HF Space from going to sleep on the free tier.
46
+ URL to ping: https://faaizashiq-codecracker-backend.hf.space/ping
47
+ """
48
+ return jsonify({"pong": True})
49
 
50
+ @app.route('/api/level/generate', methods=['POST'])
51
+ def generate_level():
52
+ """
53
+ Generates a new level (Maze, Blockly, or Time Challenge).
54
+ Expected JSON: { "mode": "maze", "difficulty": "Easy", "topic": "Space" (optional) }
55
+ """
56
+ data = request.json or {}
 
57
  mode = data.get('mode', 'maze')
58
  difficulty = data.get('difficulty', 'Easy')
59
+ topic = data.get('topic')
60
+
61
  try:
62
  if mode == 'maze':
 
63
  level_data = puzzle_agent.generate_level(difficulty, topic)
64
  else:
65
  level_data = adventure_agent.generate_level(mode, difficulty, topic)
66
+
67
  if not level_data:
68
+ raise Exception("Empty level data returned")
69
+
70
+ # Validate the response has the right shape before sending
71
+ # The agent's own fallback already returns {"story_arc_title":..., "levels":[...]}
72
+ # so this should always pass. Log a warning if it doesn't.
73
+ if mode != 'maze' and 'levels' not in level_data:
74
+ print(f"WARNING: agent returned data without 'levels' key: {list(level_data.keys())}")
75
+
76
+ return jsonify(level_data)
77
+
78
  except Exception as e:
79
  print(f"CRITICAL ERROR /api/level/generate: {e}")
80
+ # Return a mode-appropriate fallback NOT a maze object for blockly requests
81
+ if mode == 'blockly':
82
+ fallback = {
83
+ "story_arc_title": "System Offline Arc",
84
+ "levels": [
85
+ {
86
+ "type": "blockly",
87
+ "level_id": f"emergency_{i}",
88
+ "title": f"System Repair Part {i}",
89
+ "story": "The AI systems are offline. Practice mode engaged.",
90
+ "concept_tutorial": "Loops: Repeat actions without writing code over and over.",
91
+ "problem": "Create a loop that prints 1, 2, 3.",
92
+ "toolbox_categories": ["Loops", "Math", "Variables", "Text"],
93
+ "initial_code": "",
94
+ "validation_rules": {
95
+ "required_concepts": ["loop"],
96
+ "expected_output": "1\n2\n3"
97
+ },
98
+ "hint_1": "Use the 'repeat' block.",
99
+ "hint_2": "Set the number to 3."
100
+ }
101
+ for i in range(1, 6)
102
+ ]
103
+ }
104
+ elif mode == 'time_challenge':
105
+ fallback = {
106
+ "story_arc_title": "System Reboot Arc",
107
+ "levels": [
108
+ {
109
+ "type": "time_challenge",
110
+ "level_id": f"emergency_tc_{i}",
111
+ "title": f"System Reboot Part {i}",
112
+ "story": "Critical Error! Debug the startup sequence.",
113
+ "concept_tutorial": "Syntax: Code must follow strict grammar rules.",
114
+ "timer_seconds": 60,
115
+ "buggy_code": "print('System Ready'\nstart_engine()",
116
+ "task": "Fix the syntax error.",
117
+ "solution_patch": "print('System Ready')\nstart_engine()"
118
+ }
119
+ for i in range(1, 6)
120
+ ]
121
+ }
122
+ else:
123
+ # maze fallback — single level is fine here
124
+ fallback = {
125
+ "story_arc_title": "Emergency Training",
126
+ "levels": [{
127
+ "type": "maze",
128
+ "level_id": "emergency_fallback",
129
+ "title": "System Practice",
130
+ "story": "System offline. Practice mode engaged.",
131
+ "grid_layout": [[2, 0, 3]],
132
+ "maze_rows": 1,
133
+ "maze_cols": 3,
134
+ "ordered_steps": [],
135
+ "cell_values": {},
136
+ "maze_rule": {"type": "none"},
137
+ "allowed_blocks": ["move_right"],
138
+ "toolbox_xml": '<category name="Movement" colour="230"><block type="move_right"></block></category>',
139
+ "theme_palette": {"wall_color": "#8E24AA", "path_color": "#020817", "accent": "#FF477E"}
140
+ }]
141
+ }
142
+ return jsonify(fallback)
143
 
144
+ @app.route('/api/level/feedback', methods=['POST'])
145
+ def level_feedback():
146
+ """
147
+ Receives user feedback (1-5 stars) on a level.
148
+ Expected JSON: { "level_data": {...}, "rating": 5 }
149
+ """
150
+ data = request.json or {}
 
151
  level_data = data.get('level_data')
152
  rating = data.get('rating')
153
  developer_feedback = data.get('developer_feedback')
154
 
155
  if not level_data or not rating:
156
+ return jsonify({"message": "Invalid data ignored"}), 400
157
 
158
  try:
159
  # Teach the agent!
 
162
  else:
163
  adventure_agent.learn_from_feedback(level_data, int(rating), developer_feedback)
164
 
165
+ return jsonify({"status": "learned", "message": "Thanks for the feedback!"})
166
  except Exception as e:
167
  print(f"Feedback Error: {e}")
168
+ return jsonify({"status": "ignored"}), 200
169
 
170
+ @app.route('/api/player/evaluate', methods=['POST'])
171
+ def evaluate_player():
172
+ """
173
+ Evaluates player history to adjust difficulty.
174
+ Expected JSON: { "history": [ { "result": "success", "time_taken": 30 }, ... ] }
175
+ """
176
+ data = request.json or {}
 
177
  history = data.get('history', [])
178
 
179
  try:
180
  evaluation = adaptive_agent.evaluate_user(history)
181
+ return jsonify(evaluation)
182
  except Exception as e:
183
  print(f"CRITICAL ERROR /api/player/evaluate: {e}")
184
+ # Fallback: Default to current or Easy
185
+ return jsonify({"difficulty": "Easy", "reason": "System requires calibration."})
186
 
187
+ @app.route('/api/hint/generate', methods=['POST'])
188
+ def generate_hint():
189
+ """
190
+ Generates a context-aware hint.
191
+ Expected JSON: { "mode": "maze|blockly", "level_context": {...}, "user_state": {...} }
192
+ """
193
+ data = request.json or {}
 
194
  mode = data.get('mode', 'blockly')
195
  user_state = data.get('user_state', {})
196
+
197
  try:
198
  hint = hint_agent.generate_hint(
199
  mode=mode,
200
  level_context=data.get('level_context', {}),
201
  user_state=user_state
202
  )
203
+ return jsonify({"hint": hint})
204
  except Exception as e:
205
  print(f"CRITICAL ERROR /api/hint/generate: {e}")
206
+ return jsonify({"hint": "Try checking your logic step-by-step."})
207
 
208
  if __name__ == '__main__':
209
+ port = int(os.environ.get('PORT', 5000))
210
+ app.run(host='0.0.0.0', port=port, debug=True)