Spaces:
Sleeping
Sleeping
Commit
·
d9bd342
1
Parent(s):
b911d8c
Update timetable creator
Browse files
app.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
# Access site: https://binkhoale1812-tutorbot.hf.space
|
2 |
import os
|
3 |
import time
|
4 |
import uvicorn
|
@@ -13,13 +13,13 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
13 |
from google import genai
|
14 |
from gradio_client import Client, handle_file
|
15 |
|
16 |
-
#
|
17 |
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s — %(name)s — %(levelname)s — %(message)s", force=True)
|
18 |
logger = logging.getLogger("tutor-chatbot")
|
19 |
logger.setLevel(logging.DEBUG)
|
20 |
logger.info("🚀 Starting Tutor Chatbot API...")
|
21 |
|
22 |
-
# —————— Environment
|
23 |
gemini_flash_api_key = os.getenv("FlashAPI")
|
24 |
if not gemini_flash_api_key:
|
25 |
raise ValueError("❌ Missing Gemini Flash API key!")
|
@@ -35,7 +35,7 @@ check_system_resources()
|
|
35 |
os.environ["OMP_NUM_THREADS"] = "1"
|
36 |
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
37 |
|
38 |
-
# —————— FastAPI Setup
|
39 |
app = FastAPI(title="Tutor Chatbot API")
|
40 |
app.add_middleware(
|
41 |
CORSMiddleware,
|
@@ -59,7 +59,7 @@ def gemini_flash_completion(prompt, model="gemini-2.5-flash-preview-04-17", temp
|
|
59 |
logger.error(f"❌ Gemini error: {e}")
|
60 |
return "Error generating response from Gemini."
|
61 |
|
62 |
-
#
|
63 |
qwen_client = Client("prithivMLmods/Qwen2.5-VL-7B-Instruct")
|
64 |
logger.info("[Qwen] Using remote API via Gradio Client")
|
65 |
|
@@ -120,7 +120,7 @@ def qwen_image_summary(image_file: UploadFile, subject: str, level: str) -> str:
|
|
120 |
raise HTTPException(500, "❌ Qwen image analysis failed")
|
121 |
|
122 |
|
123 |
-
#
|
124 |
@app.post("/chat")
|
125 |
async def chat_endpoint(
|
126 |
query: str = Form(""),
|
@@ -205,7 +205,88 @@ async def chat_endpoint(
|
|
205 |
response_text += f"\n\n*(Response time: {end_time - start_time:.2f} seconds)*"
|
206 |
return JSONResponse(content={"response": response_text})
|
207 |
|
208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
if __name__ == "__main__":
|
210 |
logger.info("✅ Launching FastAPI server...")
|
211 |
try:
|
|
|
1 |
+
# Access site: https://binkhoale1812-tutorbot.hf.space
|
2 |
import os
|
3 |
import time
|
4 |
import uvicorn
|
|
|
13 |
from google import genai
|
14 |
from gradio_client import Client, handle_file
|
15 |
|
16 |
+
# ———————— Logging —————————
|
17 |
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s — %(name)s — %(levelname)s — %(message)s", force=True)
|
18 |
logger = logging.getLogger("tutor-chatbot")
|
19 |
logger.setLevel(logging.DEBUG)
|
20 |
logger.info("🚀 Starting Tutor Chatbot API...")
|
21 |
|
22 |
+
# —————— Environment ———————
|
23 |
gemini_flash_api_key = os.getenv("FlashAPI")
|
24 |
if not gemini_flash_api_key:
|
25 |
raise ValueError("❌ Missing Gemini Flash API key!")
|
|
|
35 |
os.environ["OMP_NUM_THREADS"] = "1"
|
36 |
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
37 |
|
38 |
+
# —————— FastAPI Setup —————
|
39 |
app = FastAPI(title="Tutor Chatbot API")
|
40 |
app.add_middleware(
|
41 |
CORSMiddleware,
|
|
|
59 |
logger.error(f"❌ Gemini error: {e}")
|
60 |
return "Error generating response from Gemini."
|
61 |
|
62 |
+
# —— Qwen 2.5 VL Client Setup —————
|
63 |
qwen_client = Client("prithivMLmods/Qwen2.5-VL-7B-Instruct")
|
64 |
logger.info("[Qwen] Using remote API via Gradio Client")
|
65 |
|
|
|
120 |
raise HTTPException(500, "❌ Qwen image analysis failed")
|
121 |
|
122 |
|
123 |
+
# ————— Unified Chat Endpoint —————
|
124 |
@app.post("/chat")
|
125 |
async def chat_endpoint(
|
126 |
query: str = Form(""),
|
|
|
205 |
response_text += f"\n\n*(Response time: {end_time - start_time:.2f} seconds)*"
|
206 |
return JSONResponse(content={"response": response_text})
|
207 |
|
208 |
+
|
209 |
+
# ————— Clsr Pydantic Schema —————
|
210 |
+
from pydantic import BaseModel, Field, validator
|
211 |
+
from typing import Optional, List, Literal
|
212 |
+
# Dynamic cls
|
213 |
+
class StudyPreferences(BaseModel):
|
214 |
+
daysPerWeek: int = Field(..., ge=1, le=7)
|
215 |
+
hoursPerSession: float = Field(..., ge=0.5, le=4)
|
216 |
+
learningStyle: Literal["step-by-step", "conceptual", "visual"]
|
217 |
+
# Dynamic cls
|
218 |
+
class ClassroomRequest(BaseModel):
|
219 |
+
id: str
|
220 |
+
name: str = Field(..., min_length=2)
|
221 |
+
role: Literal["tutor", "student"]
|
222 |
+
subject: str
|
223 |
+
gradeLevel: str
|
224 |
+
textbookUrl: Optional[str] = None
|
225 |
+
syllabusUrl: Optional[str] = None
|
226 |
+
studyPreferences: StudyPreferences
|
227 |
+
|
228 |
+
# —————— Time table creator ——————
|
229 |
+
import json
|
230 |
+
from fastapi import Body
|
231 |
+
@app.post("/api/classrooms")
|
232 |
+
async def create_classroom(payload: ClassroomRequest = Body(...)):
|
233 |
+
"""
|
234 |
+
Generate a detailed study timetable based on classroom parameters.
|
235 |
+
"""
|
236 |
+
# ---------- Build prompt for Gemini 2.5 ----------
|
237 |
+
prefs = payload.studyPreferences
|
238 |
+
prompt = f"""
|
239 |
+
You are an expert academic coordinator.
|
240 |
+
|
241 |
+
Create a **4-week study timetable** for a classroom with the following settings:
|
242 |
+
|
243 |
+
- Subject: {payload.subject}
|
244 |
+
- Grade level: {payload.gradeLevel}
|
245 |
+
- Study days per week: {prefs.daysPerWeek}
|
246 |
+
- Hours per session: {prefs.hoursPerSession}
|
247 |
+
- Preferred learning style: {prefs.learningStyle}
|
248 |
+
- Role perspective: {payload.role}
|
249 |
+
{"- Textbook URL: " + payload.textbookUrl if payload.textbookUrl else ""}
|
250 |
+
{"- Syllabus URL: " + payload.syllabusUrl if payload.syllabusUrl else ""}
|
251 |
+
|
252 |
+
Requirements:
|
253 |
+
|
254 |
+
1. Divide each week into exactly {prefs.daysPerWeek} sessions (label Day 1 … Day {prefs.daysPerWeek}).
|
255 |
+
|
256 |
+
2. For **each session**, return:
|
257 |
+
- `week` (1-4)
|
258 |
+
- `day` (1-{prefs.daysPerWeek})
|
259 |
+
- `durationHours` (fixed: {prefs.hoursPerSession})
|
260 |
+
- `topic` (max 15 words)
|
261 |
+
- `activities` (array of 2-3 bullet strings)
|
262 |
+
- `materials` (array of links/titles; include textbook chapters if URL given)
|
263 |
+
- `homework` (concise task ≤ 30 words)
|
264 |
+
|
265 |
+
3. **Output pure JSON only** using the schema:
|
266 |
+
|
267 |
+
```json
|
268 |
+
{{
|
269 |
+
"classroom_id": "<same id as request>",
|
270 |
+
"timetable": [{{session objects as listed}}]
|
271 |
+
}}
|
272 |
+
|
273 |
+
Do not wrap JSON in markdown fences or commentary.
|
274 |
+
"""
|
275 |
+
raw = gemini_flash_completion(prompt).strip()
|
276 |
+
|
277 |
+
# ---------- Attempt to parse JSON ----------
|
278 |
+
try:
|
279 |
+
timetable_json = json.loads(raw)
|
280 |
+
except json.JSONDecodeError:
|
281 |
+
logger.warning("Gemini returned invalid JSON; sending raw text.")
|
282 |
+
return JSONResponse(content={"classroom_id": payload.id, "timetable_raw": raw})
|
283 |
+
# Ensure id is echoed (fallback if model forgot)
|
284 |
+
timetable_json["classroom_id"] = payload.id
|
285 |
+
return JSONResponse(content=timetable_json)
|
286 |
+
|
287 |
+
|
288 |
+
|
289 |
+
# —————— Launch Server ———————
|
290 |
if __name__ == "__main__":
|
291 |
logger.info("✅ Launching FastAPI server...")
|
292 |
try:
|