mikaelJ46 commited on
Commit
4aaafc4
·
verified ·
1 Parent(s): e7dd03b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -20
app.py CHANGED
@@ -366,24 +366,71 @@ Start immediately with Q1. Do not include any introduction or explanation.
366
  def grade_student_answers(questions, student_answers, grade_level, topic, subject=None):
367
  """Grade all student answers and provide AI feedback - NO STRICT FORMAT REQUIRED"""
368
 
369
- if not questions or not student_answers:
370
- return " No questions or answers to grade."
371
-
372
  feedback_list = []
373
-
374
  for i, question_text in enumerate(questions):
375
- student_answer = student_answers.get(i, "") or ""
376
-
377
- if not student_answer.strip():
378
- feedback_list.append(f"Q{i+1}: [NOT ANSWERED]")
 
 
 
 
 
 
 
 
 
 
379
  continue
380
-
381
- # Create flexible grading prompt - AI evaluates based on question number & content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  grading_prompt = f"""You are an experienced UNEB examiner for {grade_level} students. Subject: {subject or 'General'}
383
 
384
  Question {i+1}: {question_text}
385
 
386
- Student's Answer: {student_answer}
387
 
388
  Evaluate this answer:
389
  - Is it correct, partially correct, or incorrect?
@@ -392,10 +439,10 @@ Evaluate this answer:
392
  - If incorrect, briefly explain the correct approach
393
 
394
  Accept answers in ANY format/style as long as they address the question. Be constructive and encouraging."""
395
-
396
  grade_response, _ = ask_ai(grading_prompt, temperature=0.3)
397
  feedback_list.append(f"Q{i+1}:\n{grade_response}\n")
398
-
399
  full_feedback = "\n" + "="*60 + "\n".join(feedback_list)
400
  return full_feedback
401
 
@@ -408,6 +455,7 @@ class StudentSession:
408
  self.current_topic = None
409
  self.current_grade = None
410
  self.last_feedback = None
 
411
 
412
  def clear_session(self):
413
  self.current_questions = []
@@ -640,6 +688,11 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
640
 
641
  # Global state for questions
642
  questions_state = gr.State(value=[])
 
 
 
 
 
643
 
644
  # Student name input
645
  with gr.Group():
@@ -691,10 +744,7 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
691
  num_questions_input = gr.Slider(minimum=1, maximum=100, step=1, value=20, label="Number of Questions")
692
  generate_btn = gr.Button(" Generate Questions", variant="primary", size="lg")
693
 
694
- questions_output = gr.HTML(
695
- value="",
696
- elem_classes="question-display"
697
- )
698
 
699
  status_output = gr.Textbox(
700
  label="Status",
@@ -727,7 +777,7 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
727
  generate_btn.click(
728
  fn=generate_and_display,
729
  inputs=[grade_input, subject_input, topic_input, num_questions_input],
730
- outputs=[questions_output, questions_state, status_output]
731
  )
732
  # Download handler - returns file path for gr.DownloadButton
733
  def download_qns():
@@ -850,11 +900,13 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
850
 
851
  # Check which method was used
852
  if canvas_input is not None:
853
- session.current_answers = {0: "[Canvas drawing - sent to AI for analysis]"}
 
854
  return f" Canvas submission received with {len(session.current_questions)} questions. Processing..."
855
 
856
  elif upload_input is not None:
857
- session.current_answers = {0: "[Uploaded image - sent to AI for analysis]"}
 
858
  return f" Photo submission received with {len(session.current_questions)} questions. Processing..."
859
 
860
  elif typed_input and typed_input.strip():
@@ -942,6 +994,20 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
942
  lines=20,
943
  elem_classes="feedback-box"
944
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
945
 
946
  with gr.Row():
947
  download_feedback_btn = gr.DownloadButton(" Download Feedback (TXT)")
@@ -967,6 +1033,8 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
967
  )
968
 
969
  session.last_feedback = feedback
 
 
970
  return feedback
971
 
972
  correction_btn.click(
@@ -991,6 +1059,40 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
991
  inputs=[],
992
  outputs=download_feedback_btn
993
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
994
 
995
  # ===== TAB 4: START NEW SESSION =====
996
  with gr.Tab(" New Session"):
 
366
  def grade_student_answers(questions, student_answers, grade_level, topic, subject=None):
367
  """Grade all student answers and provide AI feedback - NO STRICT FORMAT REQUIRED"""
368
 
369
+ if not questions:
370
+ return " No questions to grade."
371
+
372
  feedback_list = []
373
+
374
  for i, question_text in enumerate(questions):
375
+ student_answer = student_answers.get(i, None)
376
+
377
+ # No answer submitted for this question
378
+ if student_answer is None:
379
+ # Ask AI for a short guidance/hint for this question (best-effort)
380
+ hint_prompt = f"""You are an experienced UNEB examiner for {grade_level} students.
381
+
382
+ Question {i+1}: {question_text}
383
+
384
+ The student did not submit an answer. Provide a very brief (2-3 line) approach or hint that would help the student answer this question."""
385
+ hint_resp, _ = ask_ai(hint_prompt, temperature=0.3)
386
+ if not hint_resp or "All AI services failed" in hint_resp:
387
+ hint_resp = "No answer submitted. Expected: show step-by-step working and final answer; include units where applicable."
388
+ feedback_list.append(f"Q{i+1}: [NOT ANSWERED]\nGuidance: {hint_resp}\n")
389
  continue
390
+
391
+ # Handle image answers (canvas/uploaded photos)
392
+ try:
393
+ from PIL import Image as PILImage
394
+ is_image = isinstance(student_answer, PILImage.Image)
395
+ except Exception:
396
+ is_image = False
397
+
398
+ if is_image:
399
+ grading_prompt = f"""You are an experienced UNEB examiner for {grade_level} students. Subject: {subject or 'General'}
400
+
401
+ Question {i+1}: {question_text}
402
+
403
+ The student submitted handwritten work as an image. Analyse the image, extract the student's answer for this question, and then:
404
+ - State whether the answer is correct, partially correct, or incorrect
405
+ - Provide a mark/score
406
+ - Give brief constructive feedback and the correct approach if needed
407
+
408
+ If you cannot analyse images, state that image analysis requires a vision-capable model."""
409
+
410
+ grade_response, _ = ask_ai(grading_prompt, temperature=0.3, image=student_answer)
411
+ feedback_list.append(f"Q{i+1}:\n{grade_response}\n")
412
+ continue
413
+
414
+ # Treat as text answer
415
+ text_ans = str(student_answer).strip()
416
+ if not text_ans:
417
+ # empty string case
418
+ hint_prompt = f"""You are an experienced UNEB examiner for {grade_level} students.
419
+
420
+ Question {i+1}: {question_text}
421
+
422
+ The student submitted an empty/text-missing answer. Provide a very brief (2-3 line) approach or hint that would help the student answer this question."""
423
+ hint_resp, _ = ask_ai(hint_prompt, temperature=0.3)
424
+ if not hint_resp or "All AI services failed" in hint_resp:
425
+ hint_resp = "No answer submitted. Expected: show step-by-step working and final answer; include units where applicable."
426
+ feedback_list.append(f"Q{i+1}: [NOT ANSWERED]\nGuidance: {hint_resp}\n")
427
+ continue
428
+
429
  grading_prompt = f"""You are an experienced UNEB examiner for {grade_level} students. Subject: {subject or 'General'}
430
 
431
  Question {i+1}: {question_text}
432
 
433
+ Student's Answer: {text_ans}
434
 
435
  Evaluate this answer:
436
  - Is it correct, partially correct, or incorrect?
 
439
  - If incorrect, briefly explain the correct approach
440
 
441
  Accept answers in ANY format/style as long as they address the question. Be constructive and encouraging."""
442
+
443
  grade_response, _ = ask_ai(grading_prompt, temperature=0.3)
444
  feedback_list.append(f"Q{i+1}:\n{grade_response}\n")
445
+
446
  full_feedback = "\n" + "="*60 + "\n".join(feedback_list)
447
  return full_feedback
448
 
 
455
  self.current_topic = None
456
  self.current_grade = None
457
  self.last_feedback = None
458
+ self.correction_conversation = [] # list of {'role':'user'|'assistant','content':str}
459
 
460
  def clear_session(self):
461
  self.current_questions = []
 
688
 
689
  # Global state for questions
690
  questions_state = gr.State(value=[])
691
+ # Shared question display (visible across tabs so students don't switch panels)
692
+ questions_display = gr.HTML(
693
+ value="",
694
+ elem_classes="question-display"
695
+ )
696
 
697
  # Student name input
698
  with gr.Group():
 
744
  num_questions_input = gr.Slider(minimum=1, maximum=100, step=1, value=20, label="Number of Questions")
745
  generate_btn = gr.Button(" Generate Questions", variant="primary", size="lg")
746
 
747
+ # (questions_display is shared above - no local questions_output here)
 
 
 
748
 
749
  status_output = gr.Textbox(
750
  label="Status",
 
777
  generate_btn.click(
778
  fn=generate_and_display,
779
  inputs=[grade_input, subject_input, topic_input, num_questions_input],
780
+ outputs=[questions_display, questions_state, status_output]
781
  )
782
  # Download handler - returns file path for gr.DownloadButton
783
  def download_qns():
 
900
 
901
  # Check which method was used
902
  if canvas_input is not None:
903
+ # Map the same canvas image to all question indices so each question gets graded
904
+ session.current_answers = {i: canvas_input for i in range(len(session.current_questions))}
905
  return f" Canvas submission received with {len(session.current_questions)} questions. Processing..."
906
 
907
  elif upload_input is not None:
908
+ # Map the uploaded image to all question indices so AI can analyse each part
909
+ session.current_answers = {i: upload_input for i in range(len(session.current_questions))}
910
  return f" Photo submission received with {len(session.current_questions)} questions. Processing..."
911
 
912
  elif typed_input and typed_input.strip():
 
994
  lines=20,
995
  elem_classes="feedback-box"
996
  )
997
+
998
+ # Conversation area for follow-up questions about feedback
999
+ conversation_display = gr.Textbox(
1000
+ label="Correction Conversation",
1001
+ lines=12,
1002
+ interactive=False
1003
+ )
1004
+
1005
+ followup_input = gr.Textbox(
1006
+ label="Ask a follow-up question",
1007
+ placeholder="Ask about a specific question or the feedback...",
1008
+ lines=2
1009
+ )
1010
+ followup_btn = gr.Button(" Ask Follow-up")
1011
 
1012
  with gr.Row():
1013
  download_feedback_btn = gr.DownloadButton(" Download Feedback (TXT)")
 
1033
  )
1034
 
1035
  session.last_feedback = feedback
1036
+ # initialize conversation with assistant feedback
1037
+ session.correction_conversation = [{'role':'assistant', 'content': feedback}]
1038
  return feedback
1039
 
1040
  correction_btn.click(
 
1059
  inputs=[],
1060
  outputs=download_feedback_btn
1061
  )
1062
+
1063
+ # Follow-up handler: student asks a question about the feedback
1064
+ def send_followup(question_text):
1065
+ if not session.last_feedback:
1066
+ return "Please generate corrections first.", None
1067
+
1068
+ if not question_text or not question_text.strip():
1069
+ return "Please type a follow-up question.", None
1070
+
1071
+ # Build a conversational prompt referencing the previous feedback
1072
+ convo_context = ''
1073
+ # include last assistant feedback
1074
+ convo_context += session.last_feedback + "\n\n"
1075
+ followup_prompt = f"You are an experienced UNEB examiner. The student received the following feedback:\n\n{convo_context}\nNow the student asks: {question_text}\n\nAnswer concisely, refer to the specific question number if applicable, and be encouraging."
1076
+
1077
+ resp, _ = ask_ai(followup_prompt, temperature=0.4)
1078
+
1079
+ # Append to session conversation
1080
+ session.correction_conversation.append({'role':'user', 'content': question_text})
1081
+ session.correction_conversation.append({'role':'assistant', 'content': resp})
1082
+
1083
+ # Build display text combining conversation
1084
+ display = ''
1085
+ for msg in session.correction_conversation:
1086
+ prefix = 'Student:' if msg['role']=='user' else 'AI:'
1087
+ display += f"{prefix} {msg['content']}\n\n"
1088
+
1089
+ return '', display
1090
+
1091
+ followup_btn.click(
1092
+ fn=send_followup,
1093
+ inputs=[followup_input],
1094
+ outputs=[followup_input, conversation_display]
1095
+ )
1096
 
1097
  # ===== TAB 4: START NEW SESSION =====
1098
  with gr.Tab(" New Session"):