Deevyankar commited on
Commit
9f80686
·
verified ·
1 Parent(s): 670089f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -91
app.py CHANGED
@@ -89,14 +89,17 @@ def clean_source_name(book_name: str) -> str:
89
  return name or "Professor Handouts"
90
 
91
 
 
 
 
 
92
  def prioritize_professor_handouts(records):
93
- """
94
- Professor Handouts are always shown and used first.
95
- Other textbooks are used only as supporting material.
96
- """
97
  return sorted(
98
  records,
99
- key=lambda r: 0 if clean_source_name(r.get("book", "")) == "Professor Handouts" else 1
 
 
 
100
  )
101
 
102
 
@@ -107,21 +110,10 @@ def is_general_chat(text: str) -> bool:
107
  t = text.lower().strip()
108
 
109
  general_phrases = [
110
- "hi",
111
- "hello",
112
- "hola",
113
- "hey",
114
- "good morning",
115
- "good afternoon",
116
- "good evening",
117
- "thanks",
118
- "thank you",
119
- "gracias",
120
- "ok",
121
- "okay",
122
- "who are you",
123
- "what can you do",
124
- "help"
125
  ]
126
 
127
  return t in general_phrases
@@ -158,9 +150,9 @@ def general_chat_reply(text: str, language_mode: str) -> str:
158
 
159
 
160
  # =====================================================
161
- # RETRIEVAL WITH SIMILARITY SCORES
162
  # =====================================================
163
- def search_hybrid(query: str, shortlist_k: int = 20, final_k: int = 4):
164
  ensure_loaded()
165
 
166
  q_tokens = tokenize(query)
@@ -172,18 +164,28 @@ def search_hybrid(query: str, shortlist_k: int = 20, final_k: int = 4):
172
  qvec = EMBED_MODEL.encode([query], normalize_embeddings=True).astype("float32")[0]
173
  dense_scores = shortlist_emb @ qvec
174
 
175
- rerank = np.argsort(dense_scores)[::-1][:final_k]
176
- final_idx = shortlist_idx[rerank]
177
- final_scores = dense_scores[rerank]
178
-
179
  results = []
180
 
181
- for idx, score in zip(final_idx, final_scores):
182
  record = CHUNKS[int(idx)].copy()
 
 
 
 
 
183
  record["similarity_score"] = float(score)
 
 
 
 
 
 
 
184
  results.append(record)
185
 
186
- return prioritize_professor_handouts(results)
 
 
187
 
188
 
189
  def build_context(records):
@@ -197,10 +199,11 @@ def build_context(records):
197
  blocks.append(
198
  f"""[Source {i}]
199
  Book: {clean_book}
200
- Source priority: {"Primary Professor Handouts" if clean_book == "Professor Handouts" else "Supporting textbook"}
201
  Section: {r.get('section_title','')}
202
  Pages: {r.get('page_start','')}-{r.get('page_end','')}
203
  Similarity Score: {r.get('similarity_score', 0):.3f}
 
204
  Text:
205
  {r.get('text','')}"""
206
  )
@@ -232,7 +235,7 @@ def make_sources(records):
232
  section = r.get("section_title", "Course Material")
233
  page_start = r.get("page_start", "")
234
  page_end = r.get("page_end", "")
235
- score = r.get("similarity_score", 0)
236
 
237
  if page_start and page_end and page_start != page_end:
238
  page_text = f"pages {page_start}-{page_end}"
@@ -241,13 +244,10 @@ def make_sources(records):
241
  else:
242
  page_text = "page not specified"
243
 
244
- if clean_book == "Professor Handouts":
245
- source_type = "Primary source"
246
- else:
247
- source_type = "Supporting textbook"
248
 
249
  lines.append(
250
- f"• {clean_book} ({source_type}) | {section} | {page_text} | similarity: {score:.2f}"
251
  )
252
 
253
  return "\n".join(lines)
@@ -275,36 +275,49 @@ def compute_confidence(records, answer: str):
275
  "score": 0.0,
276
  }
277
 
278
- scores = [float(r.get("similarity_score", 0)) for r in records]
279
-
280
- if not scores:
281
  return {
282
  "level": "red",
283
  "label": "Not found",
284
  "score": 0.0,
285
  }
286
 
 
 
 
287
  top_score = max(scores)
288
- avg_score = sum(scores) / len(scores)
 
 
 
 
 
 
 
 
 
 
 
 
289
 
290
- if top_score >= 0.55 and avg_score >= 0.38:
291
  return {
292
  "level": "green",
293
  "label": "High confidence",
294
- "score": top_score,
295
  }
296
 
297
- if top_score >= 0.38:
298
  return {
299
  "level": "orange",
300
  "label": "Medium confidence",
301
- "score": top_score,
302
  }
303
 
304
  return {
305
  "level": "red",
306
  "label": "Low confidence",
307
- "score": top_score,
308
  }
309
 
310
 
@@ -739,9 +752,7 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
739
  try:
740
  history = history + [{"role": "user", "content": text}]
741
 
742
- # ---------------------------------------------
743
  # General greeting / normal conversation
744
- # ---------------------------------------------
745
  if is_general_chat(text):
746
  reply = general_chat_reply(text, language_mode)
747
 
@@ -770,9 +781,7 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
770
 
771
  return "", history, render_chat(history), quiz_state, render_dashboard()
772
 
773
- # ---------------------------------------------
774
  # Quiz evaluation mode
775
- # ---------------------------------------------
776
  if quiz_state.get("active", False):
777
  evaluation = oai_json(
778
  build_quiz_eval_prompt(
@@ -837,16 +846,11 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
837
 
838
  return "", history, render_chat(history), quiz_state, render_dashboard()
839
 
840
- # ---------------------------------------------
841
  # Retrieval
842
- # ---------------------------------------------
843
- records = search_hybrid(text, shortlist_k=20, final_k=4)
844
- records = prioritize_professor_handouts(records)
845
  context = build_context(records)
846
 
847
- # ---------------------------------------------
848
  # Quiz generation
849
- # ---------------------------------------------
850
  if mode == "Quiz Me":
851
  n_questions = choose_quiz_count(text, quiz_count_mode)
852
 
@@ -899,9 +903,7 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
899
 
900
  return "", history, render_chat(history), quiz_state, render_dashboard()
901
 
902
- # ---------------------------------------------
903
  # Normal answer
904
- # ---------------------------------------------
905
  answer = oai_text(
906
  build_tutor_prompt(
907
  mode,
@@ -972,18 +974,12 @@ def clear_all():
972
  CSS = """
973
  :root{
974
  --page-bg: #d9d9dd;
975
- --panel-bg: #555765;
976
- --chat-bg: #4a4c59;
977
- --grad-top: #e8c7d4;
978
- --grad-mid: #a55ca2;
979
- --grad-bot: #5a2d77;
980
- --accent: #f4eb4b;
981
- --accent-soft: #f5ef9a;
982
- --user-bubble: #ffffff;
983
- --bot-bubble: #f5efad;
984
  --text-dark: #241336;
985
- --text-dark-strong: #170c25;
986
- --text-light: #ffffff;
987
  --shadow: rgba(30,20,50,0.18);
988
  }
989
 
@@ -1037,13 +1033,15 @@ footer{
1037
  border-left: 5px solid #c7a008;
1038
  }
1039
 
 
1040
  .bc-phone{
1041
  position: relative;
1042
- background: linear-gradient(180deg, var(--grad-top) 0%, var(--grad-mid) 48%, var(--grad-bot) 100%);
1043
  border-radius: 30px;
1044
  padding: 92px 14px 14px 14px;
1045
- box-shadow: 0 16px 34px var(--shadow);
1046
  min-height: 620px;
 
1047
  }
1048
 
1049
  .bc-logo-holder{
@@ -1054,7 +1052,7 @@ footer{
1054
  width: 104px;
1055
  height: 104px;
1056
  border-radius: 999px;
1057
- background: var(--accent);
1058
  display: flex;
1059
  align-items: center;
1060
  justify-content: center;
@@ -1078,17 +1076,17 @@ footer{
1078
  text-align:center;
1079
  font-size: 13px;
1080
  font-weight: 900;
1081
- color: var(--text-dark-strong);
1082
- background: rgba(255,255,255,0.40);
1083
  line-height: 1.05;
1084
  }
1085
 
1086
  .bc-chat-shell{
1087
- background: rgba(74,76,89,0.92);
1088
  border-radius: 20px;
1089
  padding: 16px;
1090
  min-height: 460px;
1091
- box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
1092
  }
1093
 
1094
  .bc-chat-wrap{
@@ -1105,7 +1103,7 @@ footer{
1105
  }
1106
 
1107
  .bc-chat-wrap::-webkit-scrollbar-thumb{
1108
- background: rgba(255,255,255,0.28);
1109
  border-radius: 999px;
1110
  }
1111
 
@@ -1134,19 +1132,21 @@ footer{
1134
  }
1135
 
1136
  .bc-user-bubble{
1137
- background: var(--user-bubble);
1138
- color: var(--text-dark-strong) !important;
 
1139
  border-bottom-left-radius: 8px;
1140
  }
1141
 
1142
  .bc-bot-bubble{
1143
- background: var(--bot-bubble);
1144
- color: var(--text-dark-strong) !important;
 
1145
  border-bottom-right-radius: 8px;
1146
  }
1147
 
1148
  .bc-bubble strong{
1149
- color: var(--text-dark-strong) !important;
1150
  }
1151
 
1152
  .bc-confidence{
@@ -1155,10 +1155,11 @@ footer{
1155
  gap:8px;
1156
  margin-bottom:10px;
1157
  padding:7px 10px;
1158
- background:rgba(255,255,255,0.65);
1159
  border-radius:999px;
1160
  font-size:13px;
1161
  color:#111827;
 
1162
  }
1163
 
1164
  .bc-dot{
@@ -1177,16 +1178,17 @@ footer{
1177
  }
1178
 
1179
  .bc-empty-text{
1180
- color: white;
1181
  text-align:center;
1182
  opacity: 0.96;
1183
  font-size: 16px;
1184
  line-height: 1.6;
 
1185
  }
1186
 
1187
  .bc-input-bar{
1188
  margin-top: 12px;
1189
- background: var(--accent);
1190
  border-radius: 999px;
1191
  padding: 8px 10px;
1192
  display:flex;
@@ -1199,22 +1201,22 @@ footer{
1199
  width: 38px;
1200
  height: 38px;
1201
  border-radius: 999px;
1202
- background: rgba(255,255,255,0.34);
1203
  display:flex;
1204
  align-items:center;
1205
  justify-content:center;
1206
  font-size: 30px;
1207
  font-weight: 900;
1208
- color: var(--text-dark-strong);
1209
  user-select:none;
1210
  }
1211
 
1212
  #bc_msg textarea{
1213
- background: rgba(255,255,255,0.42) !important;
1214
- border: none !important;
1215
  box-shadow: none !important;
1216
  border-radius: 999px !important;
1217
- color: var(--text-dark-strong) !important;
1218
  padding: 11px 14px !important;
1219
  min-height: 42px !important;
1220
  }
@@ -1228,15 +1230,15 @@ footer{
1228
  height: 42px !important;
1229
  border-radius: 999px !important;
1230
  border: none !important;
1231
- background: rgba(255,255,255,0.34) !important;
1232
- color: var(--text-dark-strong) !important;
1233
  font-size: 20px !important;
1234
  font-weight: 900 !important;
1235
  box-shadow: none !important;
1236
  }
1237
 
1238
  #bc_send button:hover{
1239
- background: rgba(255,255,255,0.52) !important;
1240
  }
1241
 
1242
  #bc_clear button, #bc_refresh button, #bc_clear_analytics button{
 
89
  return name or "Professor Handouts"
90
 
91
 
92
+ def source_priority_label(book_name: str) -> str:
93
+ return "Primary source" if clean_source_name(book_name) == "Professor Handouts" else "Supporting textbook"
94
+
95
+
96
  def prioritize_professor_handouts(records):
 
 
 
 
97
  return sorted(
98
  records,
99
+ key=lambda r: (
100
+ 0 if clean_source_name(r.get("book", "")) == "Professor Handouts" else 1,
101
+ -float(r.get("final_score", r.get("similarity_score", 0)))
102
+ )
103
  )
104
 
105
 
 
110
  t = text.lower().strip()
111
 
112
  general_phrases = [
113
+ "hi", "hello", "hola", "hey",
114
+ "good morning", "good afternoon", "good evening",
115
+ "thanks", "thank you", "gracias",
116
+ "ok", "okay", "who are you", "what can you do", "help"
 
 
 
 
 
 
 
 
 
 
 
117
  ]
118
 
119
  return t in general_phrases
 
150
 
151
 
152
  # =====================================================
153
+ # RETRIEVAL WITH PROFESSOR HANDOUT BOOST
154
  # =====================================================
155
+ def search_hybrid(query: str, shortlist_k: int = 30, final_k: int = 5):
156
  ensure_loaded()
157
 
158
  q_tokens = tokenize(query)
 
164
  qvec = EMBED_MODEL.encode([query], normalize_embeddings=True).astype("float32")[0]
165
  dense_scores = shortlist_emb @ qvec
166
 
 
 
 
 
167
  results = []
168
 
169
+ for idx, score in zip(shortlist_idx, dense_scores):
170
  record = CHUNKS[int(idx)].copy()
171
+ clean_book = clean_source_name(record.get("book", ""))
172
+
173
+ priority_boost = 0.15 if clean_book == "Professor Handouts" else 0.0
174
+ final_score = float(score) + priority_boost
175
+
176
  record["similarity_score"] = float(score)
177
+ record["final_score"] = final_score
178
+ record["source_priority"] = (
179
+ "Professor Handouts"
180
+ if clean_book == "Professor Handouts"
181
+ else "Supporting textbook"
182
+ )
183
+
184
  results.append(record)
185
 
186
+ results = sorted(results, key=lambda r: r["final_score"], reverse=True)
187
+
188
+ return prioritize_professor_handouts(results[:final_k])
189
 
190
 
191
  def build_context(records):
 
199
  blocks.append(
200
  f"""[Source {i}]
201
  Book: {clean_book}
202
+ Source priority: {source_priority_label(clean_book)}
203
  Section: {r.get('section_title','')}
204
  Pages: {r.get('page_start','')}-{r.get('page_end','')}
205
  Similarity Score: {r.get('similarity_score', 0):.3f}
206
+ Final Score: {r.get('final_score', r.get('similarity_score', 0)):.3f}
207
  Text:
208
  {r.get('text','')}"""
209
  )
 
235
  section = r.get("section_title", "Course Material")
236
  page_start = r.get("page_start", "")
237
  page_end = r.get("page_end", "")
238
+ score = r.get("final_score", r.get("similarity_score", 0))
239
 
240
  if page_start and page_end and page_start != page_end:
241
  page_text = f"pages {page_start}-{page_end}"
 
244
  else:
245
  page_text = "page not specified"
246
 
247
+ source_type = source_priority_label(clean_book)
 
 
 
248
 
249
  lines.append(
250
+ f"• {clean_book} ({source_type}) | {section} | {page_text} | relevance: {score:.2f}"
251
  )
252
 
253
  return "\n".join(lines)
 
275
  "score": 0.0,
276
  }
277
 
278
+ if not records:
 
 
279
  return {
280
  "level": "red",
281
  "label": "Not found",
282
  "score": 0.0,
283
  }
284
 
285
+ scores = [float(r.get("final_score", r.get("similarity_score", 0))) for r in records]
286
+ raw_scores = [float(r.get("similarity_score", 0)) for r in records]
287
+
288
  top_score = max(scores)
289
+ top_raw = max(raw_scores)
290
+
291
+ professor_found = any(
292
+ clean_source_name(r.get("book", "")) == "Professor Handouts"
293
+ for r in records[:3]
294
+ )
295
+
296
+ if professor_found and top_score >= 0.48:
297
+ return {
298
+ "level": "green",
299
+ "label": "High confidence",
300
+ "score": top_raw,
301
+ }
302
 
303
+ if top_score >= 0.52:
304
  return {
305
  "level": "green",
306
  "label": "High confidence",
307
+ "score": top_raw,
308
  }
309
 
310
+ if top_score >= 0.35:
311
  return {
312
  "level": "orange",
313
  "label": "Medium confidence",
314
+ "score": top_raw,
315
  }
316
 
317
  return {
318
  "level": "red",
319
  "label": "Low confidence",
320
+ "score": top_raw,
321
  }
322
 
323
 
 
752
  try:
753
  history = history + [{"role": "user", "content": text}]
754
 
 
755
  # General greeting / normal conversation
 
756
  if is_general_chat(text):
757
  reply = general_chat_reply(text, language_mode)
758
 
 
781
 
782
  return "", history, render_chat(history), quiz_state, render_dashboard()
783
 
 
784
  # Quiz evaluation mode
 
785
  if quiz_state.get("active", False):
786
  evaluation = oai_json(
787
  build_quiz_eval_prompt(
 
846
 
847
  return "", history, render_chat(history), quiz_state, render_dashboard()
848
 
 
849
  # Retrieval
850
+ records = search_hybrid(text, shortlist_k=30, final_k=5)
 
 
851
  context = build_context(records)
852
 
 
853
  # Quiz generation
 
854
  if mode == "Quiz Me":
855
  n_questions = choose_quiz_count(text, quiz_count_mode)
856
 
 
903
 
904
  return "", history, render_chat(history), quiz_state, render_dashboard()
905
 
 
906
  # Normal answer
 
907
  answer = oai_text(
908
  build_tutor_prompt(
909
  mode,
 
974
  CSS = """
975
  :root{
976
  --page-bg: #d9d9dd;
977
+ --uva-purple: #5a2d77;
978
+ --uva-purple-light: #7b3f98;
979
+ --uva-gold: #c7a008;
980
+ --uva-gold-light: #fff8cc;
981
+ --uva-soft-purple: #efe7f6;
 
 
 
 
982
  --text-dark: #241336;
 
 
983
  --shadow: rgba(30,20,50,0.18);
984
  }
985
 
 
1033
  border-left: 5px solid #c7a008;
1034
  }
1035
 
1036
+ /* CHAT WINDOW - same family as analytics dashboard */
1037
  .bc-phone{
1038
  position: relative;
1039
+ background: #ffffff;
1040
  border-radius: 30px;
1041
  padding: 92px 14px 14px 14px;
1042
+ box-shadow: 0 16px 34px rgba(0,0,0,0.22);
1043
  min-height: 620px;
1044
+ border-top: 8px solid #5a2d77;
1045
  }
1046
 
1047
  .bc-logo-holder{
 
1052
  width: 104px;
1053
  height: 104px;
1054
  border-radius: 999px;
1055
+ background: #c7a008;
1056
  display: flex;
1057
  align-items: center;
1058
  justify-content: center;
 
1076
  text-align:center;
1077
  font-size: 13px;
1078
  font-weight: 900;
1079
+ color: #241336;
1080
+ background: rgba(255,255,255,0.55);
1081
  line-height: 1.05;
1082
  }
1083
 
1084
  .bc-chat-shell{
1085
+ background: #ffffff;
1086
  border-radius: 20px;
1087
  padding: 16px;
1088
  min-height: 460px;
1089
+ box-shadow: inset 0 0 0 2px #e5d8ef;
1090
  }
1091
 
1092
  .bc-chat-wrap{
 
1103
  }
1104
 
1105
  .bc-chat-wrap::-webkit-scrollbar-thumb{
1106
+ background: #c7a008;
1107
  border-radius: 999px;
1108
  }
1109
 
 
1132
  }
1133
 
1134
  .bc-user-bubble{
1135
+ background: #efe7f6;
1136
+ color: #241336 !important;
1137
+ border: 2px solid #d8c6e8;
1138
  border-bottom-left-radius: 8px;
1139
  }
1140
 
1141
  .bc-bot-bubble{
1142
+ background: #fff8cc;
1143
+ color: #241336 !important;
1144
+ border: 2px solid #c7a008;
1145
  border-bottom-right-radius: 8px;
1146
  }
1147
 
1148
  .bc-bubble strong{
1149
+ color: #241336 !important;
1150
  }
1151
 
1152
  .bc-confidence{
 
1155
  gap:8px;
1156
  margin-bottom:10px;
1157
  padding:7px 10px;
1158
+ background:rgba(255,255,255,0.75);
1159
  border-radius:999px;
1160
  font-size:13px;
1161
  color:#111827;
1162
+ border:1px solid #e5d8ef;
1163
  }
1164
 
1165
  .bc-dot{
 
1178
  }
1179
 
1180
  .bc-empty-text{
1181
+ color: #5a2d77;
1182
  text-align:center;
1183
  opacity: 0.96;
1184
  font-size: 16px;
1185
  line-height: 1.6;
1186
+ font-weight:600;
1187
  }
1188
 
1189
  .bc-input-bar{
1190
  margin-top: 12px;
1191
+ background: #5a2d77;
1192
  border-radius: 999px;
1193
  padding: 8px 10px;
1194
  display:flex;
 
1201
  width: 38px;
1202
  height: 38px;
1203
  border-radius: 999px;
1204
+ background: #c7a008;
1205
  display:flex;
1206
  align-items:center;
1207
  justify-content:center;
1208
  font-size: 30px;
1209
  font-weight: 900;
1210
+ color: #ffffff;
1211
  user-select:none;
1212
  }
1213
 
1214
  #bc_msg textarea{
1215
+ background: #ffffff !important;
1216
+ border: 2px solid #c7a008 !important;
1217
  box-shadow: none !important;
1218
  border-radius: 999px !important;
1219
+ color: #241336 !important;
1220
  padding: 11px 14px !important;
1221
  min-height: 42px !important;
1222
  }
 
1230
  height: 42px !important;
1231
  border-radius: 999px !important;
1232
  border: none !important;
1233
+ background: #c7a008 !important;
1234
+ color: #ffffff !important;
1235
  font-size: 20px !important;
1236
  font-weight: 900 !important;
1237
  box-shadow: none !important;
1238
  }
1239
 
1240
  #bc_send button:hover{
1241
+ background: #9f8006 !important;
1242
  }
1243
 
1244
  #bc_clear button, #bc_refresh button, #bc_clear_analytics button{