lanna_lalala;- commited on
Commit ·
7c45ed6
1
Parent(s): 02b47c0
try a thing
Browse files- app.py +2 -1
- utils/api.py +77 -33
app.py
CHANGED
|
@@ -12,11 +12,12 @@ from phase.Student_view import chatbot, lesson, quiz, game, teacherlink
|
|
| 12 |
from phase.Teacher_view import classmanage,studentlist,contentmanage
|
| 13 |
from phase.Student_view.games import profitpuzzle
|
| 14 |
from utils import db,api
|
| 15 |
-
import os
|
| 16 |
|
| 17 |
from utils.api import BACKEND
|
| 18 |
st.sidebar.caption(f"Backend URL: {BACKEND}")
|
| 19 |
|
|
|
|
| 20 |
DISABLE_DB = os.getenv("DISABLE_DB", "1") == "1"
|
| 21 |
|
| 22 |
try:
|
|
|
|
| 12 |
from phase.Teacher_view import classmanage,studentlist,contentmanage
|
| 13 |
from phase.Student_view.games import profitpuzzle
|
| 14 |
from utils import db,api
|
| 15 |
+
import os, requests
|
| 16 |
|
| 17 |
from utils.api import BACKEND
|
| 18 |
st.sidebar.caption(f"Backend URL: {BACKEND}")
|
| 19 |
|
| 20 |
+
|
| 21 |
DISABLE_DB = os.getenv("DISABLE_DB", "1") == "1"
|
| 22 |
|
| 23 |
try:
|
utils/api.py
CHANGED
|
@@ -1,21 +1,53 @@
|
|
| 1 |
# utils/api.py
|
| 2 |
-
import os, requests
|
|
|
|
|
|
|
| 3 |
|
| 4 |
# ---- Setup ----
|
| 5 |
BACKEND = (os.getenv("BACKEND_URL") or "").strip().rstrip("/")
|
| 6 |
if not BACKEND:
|
|
|
|
| 7 |
raise RuntimeError("BACKEND_URL is not set in Space secrets.")
|
| 8 |
|
| 9 |
-
|
|
|
|
|
|
|
| 10 |
DEFAULT_TIMEOUT = int(os.getenv("BACKEND_TIMEOUT", "30"))
|
| 11 |
|
| 12 |
_session = requests.Session()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
if TOKEN:
|
| 14 |
_session.headers["Authorization"] = f"Bearer {TOKEN}"
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def _req(method: str, path: str, **kw):
|
| 18 |
-
# make sure path starts with '/'
|
| 19 |
if not path.startswith("/"):
|
| 20 |
path = "/" + path
|
| 21 |
url = f"{BACKEND}{path}"
|
|
@@ -29,68 +61,80 @@ def _req(method: str, path: str, **kw):
|
|
| 29 |
body = r.text[:500]
|
| 30 |
except Exception:
|
| 31 |
pass
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
except requests.RequestException as e:
|
| 34 |
raise RuntimeError(f"{method} {path} failed: {e.__class__.__name__}: {e}") from e
|
| 35 |
return r
|
| 36 |
|
| 37 |
-
|
| 38 |
# ---- Health ----
|
| 39 |
def health():
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
# ---- Legacy agent endpoints (keep) ----
|
| 44 |
def start_agent(student_id: int, lesson_id: int, level_slug: str):
|
| 45 |
-
return _req("POST", "/agent/start",
|
| 46 |
-
|
| 47 |
|
| 48 |
def get_quiz(student_id: int, lesson_id: int, level_slug: str):
|
| 49 |
-
|
| 50 |
-
|
|
|
|
| 51 |
|
| 52 |
def grade_quiz(student_id: int, lesson_id: int, level_slug: str,
|
| 53 |
answers: list[str], assignment_id: int | None = None):
|
| 54 |
-
d = _req("POST", "/agent/grade",
|
| 55 |
-
|
| 56 |
-
|
| 57 |
return d["score"], d["total"]
|
| 58 |
|
| 59 |
def next_step(student_id: int, lesson_id: int, level_slug: str,
|
| 60 |
answers: list[str], assignment_id: int | None = None):
|
| 61 |
-
return _req("POST", "/agent/coach_or_celebrate",
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
|
| 66 |
# ---- Auth ----
|
| 67 |
def login(email: str, password: str):
|
| 68 |
-
return _req("POST", "/auth/login", json={"email": email, "password": password})
|
| 69 |
|
| 70 |
def signup_student(name: str, email: str, password: str, level_label: str, country_label: str):
|
| 71 |
-
return _req("POST", "/auth/signup/student",
|
| 72 |
-
|
| 73 |
-
|
| 74 |
|
| 75 |
def signup_teacher(title: str, name: str, email: str, password: str):
|
| 76 |
-
return _req("POST", "/auth/signup/teacher",
|
| 77 |
-
|
| 78 |
-
|
| 79 |
|
| 80 |
# ---- New LangGraph-backed endpoints ----
|
| 81 |
def fetch_lesson_content(lesson: str, module: str, topic: str):
|
| 82 |
-
|
| 83 |
-
|
| 84 |
return r["lesson_content"]
|
| 85 |
|
| 86 |
def submit_lesson_quiz(lesson: str, module: str, topic: str, responses: dict):
|
| 87 |
-
|
| 88 |
-
|
| 89 |
|
| 90 |
def submit_practice_quiz(lesson: str, responses: dict):
|
| 91 |
-
|
| 92 |
-
|
| 93 |
|
| 94 |
def send_to_chatbot(messages: list[dict]):
|
| 95 |
-
|
| 96 |
-
return _req("POST", "/chatbot", json=payload).json()
|
|
|
|
| 1 |
# utils/api.py
|
| 2 |
+
import os, json, requests
|
| 3 |
+
from urllib3.util.retry import Retry
|
| 4 |
+
from requests.adapters import HTTPAdapter
|
| 5 |
|
| 6 |
# ---- Setup ----
|
| 7 |
BACKEND = (os.getenv("BACKEND_URL") or "").strip().rstrip("/")
|
| 8 |
if not BACKEND:
|
| 9 |
+
# Fail fast at import; Streamlit will surface this in the sidebar on first run
|
| 10 |
raise RuntimeError("BACKEND_URL is not set in Space secrets.")
|
| 11 |
|
| 12 |
+
# Accept either BACKEND_TOKEN or HF_TOKEN
|
| 13 |
+
TOKEN = (os.getenv("BACKEND_TOKEN") or os.getenv("HF_TOKEN") or "").strip()
|
| 14 |
+
|
| 15 |
DEFAULT_TIMEOUT = int(os.getenv("BACKEND_TIMEOUT", "30"))
|
| 16 |
|
| 17 |
_session = requests.Session()
|
| 18 |
+
|
| 19 |
+
# Light retry for transient network/server blips
|
| 20 |
+
retry = Retry(
|
| 21 |
+
total=3,
|
| 22 |
+
connect=3,
|
| 23 |
+
read=3,
|
| 24 |
+
backoff_factor=0.5,
|
| 25 |
+
status_forcelist=(429, 500, 502, 503, 504),
|
| 26 |
+
allowed_methods=frozenset(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
| 27 |
+
)
|
| 28 |
+
_session.mount("https://", HTTPAdapter(max_retries=retry))
|
| 29 |
+
_session.mount("http://", HTTPAdapter(max_retries=retry))
|
| 30 |
+
|
| 31 |
+
# Default headers
|
| 32 |
+
_session.headers.update({
|
| 33 |
+
"Accept": "application/json, */*;q=0.1",
|
| 34 |
+
"User-Agent": "FinEdu-Frontend/1.0 (+spaces)",
|
| 35 |
+
})
|
| 36 |
if TOKEN:
|
| 37 |
_session.headers["Authorization"] = f"Bearer {TOKEN}"
|
| 38 |
|
| 39 |
+
def _json_or_raise(resp: requests.Response):
|
| 40 |
+
ctype = resp.headers.get("content-type", "")
|
| 41 |
+
if "application/json" in ctype:
|
| 42 |
+
return resp.json()
|
| 43 |
+
# Try to parse anyway; show a helpful error if not JSON
|
| 44 |
+
try:
|
| 45 |
+
return resp.json()
|
| 46 |
+
except Exception:
|
| 47 |
+
snippet = (resp.text or "")[:300]
|
| 48 |
+
raise RuntimeError(f"Expected JSON but got {ctype or 'unknown'}:\n{snippet}")
|
| 49 |
|
| 50 |
def _req(method: str, path: str, **kw):
|
|
|
|
| 51 |
if not path.startswith("/"):
|
| 52 |
path = "/" + path
|
| 53 |
url = f"{BACKEND}{path}"
|
|
|
|
| 61 |
body = r.text[:500]
|
| 62 |
except Exception:
|
| 63 |
pass
|
| 64 |
+
status = getattr(r, "status_code", "?")
|
| 65 |
+
# Give nicer hints for common auth misconfigs
|
| 66 |
+
if status in (401, 403):
|
| 67 |
+
raise RuntimeError(
|
| 68 |
+
f"{method} {path} failed [{status}] – auth rejected. "
|
| 69 |
+
f"Check BACKEND_TOKEN/HF_TOKEN permissions and that the backend Space is private/readable."
|
| 70 |
+
) from e
|
| 71 |
+
raise RuntimeError(f"{method} {path} failed [{status}]: {body}") from e
|
| 72 |
except requests.RequestException as e:
|
| 73 |
raise RuntimeError(f"{method} {path} failed: {e.__class__.__name__}: {e}") from e
|
| 74 |
return r
|
| 75 |
|
|
|
|
| 76 |
# ---- Health ----
|
| 77 |
def health():
|
| 78 |
+
# Prefer /health but allow root fallback if you change the backend later
|
| 79 |
+
try:
|
| 80 |
+
return _json_or_raise(_req("GET", "/health"))
|
| 81 |
+
except Exception:
|
| 82 |
+
# best-effort fallback
|
| 83 |
+
try:
|
| 84 |
+
_req("GET", "/")
|
| 85 |
+
return {"ok": True}
|
| 86 |
+
except Exception:
|
| 87 |
+
return {"ok": False}
|
| 88 |
|
| 89 |
# ---- Legacy agent endpoints (keep) ----
|
| 90 |
def start_agent(student_id: int, lesson_id: int, level_slug: str):
|
| 91 |
+
return _json_or_raise(_req("POST", "/agent/start",
|
| 92 |
+
json={"student_id": student_id, "lesson_id": lesson_id, "level_slug": level_slug}))
|
| 93 |
|
| 94 |
def get_quiz(student_id: int, lesson_id: int, level_slug: str):
|
| 95 |
+
d = _json_or_raise(_req("POST", "/agent/quiz",
|
| 96 |
+
json={"student_id": student_id, "lesson_id": lesson_id, "level_slug": level_slug}))
|
| 97 |
+
return d["items"]
|
| 98 |
|
| 99 |
def grade_quiz(student_id: int, lesson_id: int, level_slug: str,
|
| 100 |
answers: list[str], assignment_id: int | None = None):
|
| 101 |
+
d = _json_or_raise(_req("POST", "/agent/grade",
|
| 102 |
+
json={"student_id": student_id, "lesson_id": lesson_id, "level_slug": level_slug,
|
| 103 |
+
"answers": answers, "assignment_id": assignment_id}))
|
| 104 |
return d["score"], d["total"]
|
| 105 |
|
| 106 |
def next_step(student_id: int, lesson_id: int, level_slug: str,
|
| 107 |
answers: list[str], assignment_id: int | None = None):
|
| 108 |
+
return _json_or_raise(_req("POST", "/agent/coach_or_celebrate",
|
| 109 |
+
json={"student_id": student_id, "lesson_id": lesson_id, "level_slug": level_slug,
|
| 110 |
+
"answers": answers, "assignment_id": assignment_id}))
|
|
|
|
| 111 |
|
| 112 |
# ---- Auth ----
|
| 113 |
def login(email: str, password: str):
|
| 114 |
+
return _json_or_raise(_req("POST", "/auth/login", json={"email": email, "password": password}))
|
| 115 |
|
| 116 |
def signup_student(name: str, email: str, password: str, level_label: str, country_label: str):
|
| 117 |
+
return _json_or_raise(_req("POST", "/auth/signup/student",
|
| 118 |
+
json={"name": name, "email": email, "password": password,
|
| 119 |
+
"level_label": level_label, "country_label": country_label}))
|
| 120 |
|
| 121 |
def signup_teacher(title: str, name: str, email: str, password: str):
|
| 122 |
+
return _json_or_raise(_req("POST", "/auth/signup/teacher",
|
| 123 |
+
json={"title": title, "name": name, "email": email, "password": password}))
|
|
|
|
| 124 |
|
| 125 |
# ---- New LangGraph-backed endpoints ----
|
| 126 |
def fetch_lesson_content(lesson: str, module: str, topic: str):
|
| 127 |
+
r = _json_or_raise(_req("POST", "/lesson",
|
| 128 |
+
json={"lesson": lesson, "module": module, "topic": topic}))
|
| 129 |
return r["lesson_content"]
|
| 130 |
|
| 131 |
def submit_lesson_quiz(lesson: str, module: str, topic: str, responses: dict):
|
| 132 |
+
return _json_or_raise(_req("POST", "/lesson-quiz",
|
| 133 |
+
json={"lesson": lesson, "module": module, "topic": topic, "responses": responses}))
|
| 134 |
|
| 135 |
def submit_practice_quiz(lesson: str, responses: dict):
|
| 136 |
+
return _json_or_raise(_req("POST", "/practice-quiz",
|
| 137 |
+
json={"lesson": lesson, "responses": responses}))
|
| 138 |
|
| 139 |
def send_to_chatbot(messages: list[dict]):
|
| 140 |
+
return _json_or_raise(_req("POST", "/chatbot", json={"messages": messages}))
|
|
|