|
16 | 16 | from typing import Optional |
17 | 17 | import json |
18 | 18 | import re |
| 19 | +import requests |
19 | 20 |
|
20 | 21 | # Third-party imports |
21 | 22 | # ------------------- |
@@ -178,6 +179,109 @@ class UpdateStatusRequest(BaseModel): |
178 | 179 | new_state: Optional[str] = None |
179 | 180 |
|
180 | 181 |
|
| 182 | +def get_studyclues_book_id(course: CoursesValidator) -> str: |
| 183 | + """Get the StudyClues book ID for a given course. |
| 184 | +
|
| 185 | + :param course: The course object. |
| 186 | + :type course: CoursesValidator |
| 187 | + :return: The StudyClues book ID. |
| 188 | + :rtype: str |
| 189 | + """ |
| 190 | + # This is a placeholder implementation. You should replace this with your actual logic to get the book ID. |
| 191 | + # For example, you might have a mapping of course names to book IDs. |
| 192 | + course_to_book_id = { |
| 193 | + "csawesome2": 28, |
| 194 | + "thinkcspy": 29, |
| 195 | + "py4e-int": 30, |
| 196 | + "PTXSB": 28, |
| 197 | + # Add more mappings as needed |
| 198 | + } |
| 199 | + return course_to_book_id.get(course.base_course, 28) |
| 200 | + |
| 201 | + |
| 202 | +class StudyCluesQueryRequest(BaseModel): |
| 203 | + query: str |
| 204 | + conversation_id: Optional[int] = -1 |
| 205 | + coachMode: Optional[bool] = False |
| 206 | + |
| 207 | + |
| 208 | +@router.post("/studyclues_query") |
| 209 | +async def studyclues_query( |
| 210 | + request_data: StudyCluesQueryRequest, |
| 211 | + user=Depends(auth_manager), |
| 212 | + response_class=JSONResponse, |
| 213 | +): |
| 214 | + """Proxy a student query to StudyClues and return the response payload.""" |
| 215 | + |
| 216 | + api_base_domain = getattr( |
| 217 | + settings, |
| 218 | + "studyclues_api_base_url", |
| 219 | + "https://api.demo.learningclues.com/", |
| 220 | + ) |
| 221 | + query_studyclues_post_url = f"{api_base_domain.rstrip('/')}/studyclues/query" |
| 222 | + |
| 223 | + # Maybe cache the user_id for StudyClues in the future in Redis |
| 224 | + runestone_login_url = f"{api_base_domain}/auth/runestone_login" |
| 225 | + params = {"runestone_username": user.username} |
| 226 | + response = requests.get(runestone_login_url, params=params) |
| 227 | + if response.status_code != 200: |
| 228 | + rslogger.error( |
| 229 | + f"StudyClues login request failed with status {response.status_code}: {response.text}" |
| 230 | + ) |
| 231 | + return make_json_response( |
| 232 | + status=502, |
| 233 | + detail={ |
| 234 | + "success": False, |
| 235 | + "message": "StudyClues login request failed", |
| 236 | + "status_code": response.status_code, |
| 237 | + }, |
| 238 | + ) |
| 239 | + data = response.json() |
| 240 | + lc_user = data.get("user_id") |
| 241 | + |
| 242 | + course = await fetch_course(user.course_name) |
| 243 | + book_id = get_studyclues_book_id(course) |
| 244 | + studyclues_params = { |
| 245 | + "course_id": book_id, # todo: make this dynamic based on the basecourse |
| 246 | + "query": request_data.query, |
| 247 | + "num_passages": 20, |
| 248 | + "dry_run": False, |
| 249 | + "user_id": lc_user, |
| 250 | + "conversation_id": request_data.conversation_id, |
| 251 | + "coach_mode": request_data.coachMode, |
| 252 | + "source_filter": "GITHUB_FILE", |
| 253 | + } |
| 254 | + |
| 255 | + try: |
| 256 | + with requests.Session() as session: |
| 257 | + upstream_response = session.post( |
| 258 | + query_studyclues_post_url, |
| 259 | + json=studyclues_params, |
| 260 | + timeout=30, |
| 261 | + ) |
| 262 | + upstream_response.raise_for_status() |
| 263 | + studyclues_response = upstream_response.json() |
| 264 | + except requests.RequestException as err: |
| 265 | + rslogger.error(f"StudyClues request failed: {err}") |
| 266 | + return make_json_response( |
| 267 | + status=502, |
| 268 | + detail={"success": False, "message": "StudyClues request failed"}, |
| 269 | + ) |
| 270 | + except ValueError: |
| 271 | + rslogger.error("StudyClues returned non-JSON response") |
| 272 | + return make_json_response( |
| 273 | + status=502, |
| 274 | + detail={"success": False, "message": "Invalid StudyClues response"}, |
| 275 | + ) |
| 276 | + |
| 277 | + conversation_id = studyclues_response.get( |
| 278 | + "conversation_id", request_data.conversation_id |
| 279 | + ) |
| 280 | + return make_json_response( |
| 281 | + detail={"response": studyclues_response, "conversation_id": conversation_id} |
| 282 | + ) |
| 283 | + |
| 284 | + |
181 | 285 | @router.post("/update_submit") |
182 | 286 | async def update_submit( |
183 | 287 | request: Request, |
|
0 commit comments