SarahXia0405 commited on
Commit
363fa19
·
verified ·
1 Parent(s): 1577d6e

Update api/server.py

Browse files
Files changed (1) hide show
  1. api/server.py +127 -4
api/server.py CHANGED
@@ -122,26 +122,33 @@ def _preload_module10_chunks() -> List[Dict[str, Any]]:
122
 
123
  MODULE10_CHUNKS_CACHE = _preload_module10_chunks()
124
 
125
-
126
  def _get_session(user_id: str) -> Dict[str, Any]:
127
  if user_id not in SESSIONS:
128
  SESSIONS[user_id] = {
129
  "user_id": user_id,
130
  "name": "",
131
- "history": [], # List[Tuple[str, str]]
132
  "weaknesses": [],
133
  "cognitive_state": {"confusion": 0, "mastery": 0},
134
  "course_outline": DEFAULT_COURSE_TOPICS,
135
  "rag_chunks": list(MODULE10_CHUNKS_CACHE),
136
  "model_name": DEFAULT_MODEL,
137
- "uploaded_files": [], # ✅ NEW: track uploaded file metadata for prompting/debug
 
 
 
 
138
  }
139
- # ✅ NEW: backfill for existing sessions created before this change
140
  if "uploaded_files" not in SESSIONS[user_id]:
141
  SESSIONS[user_id]["uploaded_files"] = []
 
 
 
 
142
  return SESSIONS[user_id]
143
 
144
 
 
145
  # ✅ NEW: helper to build a deterministic “what files are loaded” hint for the LLM
146
  def _build_upload_hint(sess: Dict[str, Any]) -> str:
147
  files = sess.get("uploaded_files") or []
@@ -461,6 +468,62 @@ class FeedbackReq(BaseModel):
461
  timestamp_ms: Optional[int] = None
462
 
463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  # ----------------------------
465
  # API Routes
466
  # ----------------------------
@@ -871,6 +934,66 @@ def memoryline(user_id: str):
871
  _ = _get_session((user_id or "").strip())
872
  return {"next_review_label": "T+7", "progress_pct": 0.4}
873
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
874
 
875
  # ----------------------------
876
  # SPA Fallback
 
122
 
123
  MODULE10_CHUNKS_CACHE = _preload_module10_chunks()
124
 
 
125
  def _get_session(user_id: str) -> Dict[str, Any]:
126
  if user_id not in SESSIONS:
127
  SESSIONS[user_id] = {
128
  "user_id": user_id,
129
  "name": "",
130
+ "history": [],
131
  "weaknesses": [],
132
  "cognitive_state": {"confusion": 0, "mastery": 0},
133
  "course_outline": DEFAULT_COURSE_TOPICS,
134
  "rag_chunks": list(MODULE10_CHUNKS_CACHE),
135
  "model_name": DEFAULT_MODEL,
136
+ "uploaded_files": [],
137
+ # NEW: profile init (MVP in-memory)
138
+ "profile_bio": "",
139
+ "init_answers": {}, # raw answers
140
+ "init_dismiss_until": 0, # unix ts seconds
141
  }
 
142
  if "uploaded_files" not in SESSIONS[user_id]:
143
  SESSIONS[user_id]["uploaded_files"] = []
144
+ # backfill
145
+ SESSIONS[user_id].setdefault("profile_bio", "")
146
+ SESSIONS[user_id].setdefault("init_answers", {})
147
+ SESSIONS[user_id].setdefault("init_dismiss_until", 0)
148
  return SESSIONS[user_id]
149
 
150
 
151
+
152
  # ✅ NEW: helper to build a deterministic “what files are loaded” hint for the LLM
153
  def _build_upload_hint(sess: Dict[str, Any]) -> str:
154
  files = sess.get("uploaded_files") or []
 
468
  timestamp_ms: Optional[int] = None
469
 
470
 
471
+ class ProfileStatusResp(BaseModel):
472
+ need_init: bool
473
+ bio_len: int
474
+ dismissed_until: int
475
+
476
+ class ProfileDismissReq(BaseModel):
477
+ user_id: str
478
+ days: int = 7
479
+
480
+ class ProfileInitSubmitReq(BaseModel):
481
+ user_id: str
482
+ answers: Dict[str, Any]
483
+ language_preference: str = "Auto"
484
+
485
+ def _generate_profile_bio_with_clare(
486
+ sess: Dict[str, Any],
487
+ answers: Dict[str, Any],
488
+ language_preference: str = "Auto",
489
+ ) -> str:
490
+ # 生成时不要污染用户正常 history,用空 history
491
+ prompt = f"""
492
+ You are Clare, an AI teaching assistant.
493
+ Task: Generate a short Profile Bio for the student based ONLY on the provided initialization answers.
494
+
495
+ Rules:
496
+ - Tone: neutral, supportive, non-judgmental.
497
+ - No medical/psychological diagnosis language.
498
+ - Do not infer sensitive attributes (race/religion/politics/health/sexuality).
499
+ - Length: 80–160 Chinese characters if user seems Chinese; otherwise 60–120 English words.
500
+ - Structure: (1) background & goal, (2) current skill level, (3) learning preferences, (4) how Clare will support.
501
+
502
+ Student name (if any): {sess.get("name","")}
503
+ Initialization answers (JSON):
504
+ {answers}
505
+ """.strip()
506
+
507
+ resolved_lang = detect_language(prompt, language_preference)
508
+
509
+ try:
510
+ bio, _unused_history, _run_id = chat_with_clare(
511
+ message=prompt,
512
+ history=[],
513
+ model_name=sess["model_name"],
514
+ language_preference=resolved_lang,
515
+ learning_mode="summary",
516
+ doc_type="Other Course Document",
517
+ course_outline=sess["course_outline"],
518
+ weaknesses=sess["weaknesses"],
519
+ cognitive_state=sess["cognitive_state"],
520
+ rag_context="", # profile init 不需要 RAG
521
+ )
522
+ return (bio or "").strip()
523
+ except Exception as e:
524
+ print("[profile_bio] generate failed:", repr(e))
525
+ return ""
526
+
527
  # ----------------------------
528
  # API Routes
529
  # ----------------------------
 
934
  _ = _get_session((user_id or "").strip())
935
  return {"next_review_label": "T+7", "progress_pct": 0.4}
936
 
937
+ @app.get("/api/profile/status")
938
+ def profile_status(user_id: str):
939
+ user_id = (user_id or "").strip()
940
+ if not user_id:
941
+ return JSONResponse({"error": "Missing user_id"}, status_code=400)
942
+
943
+ sess = _get_session(user_id)
944
+ bio = (sess.get("profile_bio") or "").strip()
945
+ bio_len = len(bio)
946
+
947
+ now = int(time.time())
948
+ dismissed_until = int(sess.get("init_dismiss_until") or 0)
949
+
950
+ # 触发条件:bio <= 50 且不在 dismiss 窗口内
951
+ need_init = (bio_len <= 50) and (now >= dismissed_until)
952
+
953
+ return {
954
+ "need_init": need_init,
955
+ "bio_len": bio_len,
956
+ "dismissed_until": dismissed_until,
957
+ }
958
+
959
+
960
+ @app.post("/api/profile/dismiss")
961
+ def profile_dismiss(req: ProfileDismissReq):
962
+ user_id = (req.user_id or "").strip()
963
+ if not user_id:
964
+ return JSONResponse({"error": "Missing user_id"}, status_code=400)
965
+
966
+ sess = _get_session(user_id)
967
+ days = max(1, min(int(req.days or 7), 30)) # 1–30天
968
+ sess["init_dismiss_until"] = int(time.time()) + days * 24 * 3600
969
+ return {"ok": True, "dismissed_until": sess["init_dismiss_until"]}
970
+
971
+
972
+ @app.post("/api/profile/init_submit")
973
+ def profile_init_submit(req: ProfileInitSubmitReq):
974
+ user_id = (req.user_id or "").strip()
975
+ if not user_id:
976
+ return JSONResponse({"error": "Missing user_id"}, status_code=400)
977
+
978
+ sess = _get_session(user_id)
979
+ answers = req.answers or {}
980
+
981
+ # save raw answers
982
+ sess["init_answers"] = answers
983
+
984
+ # generate bio
985
+ bio = _generate_profile_bio_with_clare(sess, answers, req.language_preference)
986
+
987
+ if not bio:
988
+ return JSONResponse({"error": "Failed to generate bio"}, status_code=500)
989
+
990
+ sess["profile_bio"] = bio
991
+
992
+ return {
993
+ "ok": True,
994
+ "bio": bio,
995
+ }
996
+
997
 
998
  # ----------------------------
999
  # SPA Fallback