Benny-Tang commited on
Commit
69d5318
·
verified ·
1 Parent(s): 0d8204f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +133 -120
app.py CHANGED
@@ -1,146 +1,159 @@
1
- import os
2
  import gradio as gr
3
- from demo_questions import QUESTIONS
4
- from zhipuai import ZhipuAI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- # Initialize ZhipuAI client
7
- ZHIPU_API_KEY = os.getenv("ZHIPU_API_KEY", "")
8
- client = ZhipuAI(api_key=ZHIPU_API_KEY)
9
 
10
- # Store student answers
11
- student_answers = {}
 
12
 
 
 
 
13
 
14
- def load_exam(subject, paper):
15
- """Load selected subject & paper questions."""
16
- if subject not in QUESTIONS or paper not in QUESTIONS[subject]:
17
- return [gr.Markdown("⚠️ No questions available for this selection.")]
18
 
19
- qlist = QUESTIONS[subject][paper]
20
- student_answers.clear() # reset before new exam
21
 
22
- outputs = []
23
- for i, q in enumerate(qlist):
24
- # Store internally but only show text to student
25
- student_answers[i] = {
26
- "id": q.get("id"),
27
- "question": q.get("text"),
28
- "choices": q.get("choices"),
29
- "answer": q.get("answer"),
30
- "student_answer": None,
31
- "paper": paper,
32
- }
33
-
34
- outputs.append(gr.Markdown(f"**Q{i+1}:** {q['text']}"))
35
-
36
- if q.get("choices"):
37
- outputs.append(
38
- gr.Radio(
39
- choices=q["choices"],
40
- label=f"Your Answer for Q{i+1}",
41
- interactive=True,
42
- key=f"q{i+1}",
43
- )
44
- )
45
- else:
46
- outputs.append(
47
- gr.Textbox(
48
- label=f"Your Answer for Q{i+1}",
49
- interactive=True,
50
- key=f"q{i+1}",
51
- )
52
- )
53
-
54
- return outputs
55
-
56
-
57
- def grade_with_zhipu(question, student_answer, expected_answer=None):
58
- """Ask ZhipuAI to grade subjective answers."""
59
- try:
60
- prompt = f"""
61
- You are an exam grader.
62
-
63
- Question: {question}
64
- Student Answer: {student_answer}
65
- Expected Answer (if provided): {expected_answer or "N/A"}
66
-
67
- Grade strictly:
68
- - Give a score from 0 to 1 (0 = wrong, 1 = fully correct, 0.5 if partially correct).
69
- - Provide a short feedback (1-2 sentences).
70
- Return in JSON format with keys: 'score', 'feedback'.
71
- """
72
- response = client.chat.completions.create(
73
- model="glm-4-air", # lightweight for grading
74
- messages=[{"role": "user", "content": prompt}],
75
- temperature=0.2,
76
- )
77
- result = response.choices[0].message["content"].strip()
78
-
79
- # Try to parse JSON-like structure
80
- if "'score'" in result or '"score"' in result:
81
- return result
82
- return '{"score": 0, "feedback": "LLM could not evaluate."}'
83
-
84
- except Exception as e:
85
- return f'{{"score": 0, "feedback": "Error grading with LLM: {str(e)}"}}'
86
-
87
-
88
- def submit_exam(*responses):
89
- """Process submission, calculate score, and show results."""
90
  score = 0
91
- total = len(student_answers)
92
-
93
- for i, response in enumerate(responses):
94
- student_answers[i]["student_answer"] = response
95
 
96
- review = []
97
- for i, q in student_answers.items():
98
- correct = q["answer"]
99
- student = q["student_answer"]
100
-
101
- # Paper 2: objective auto-marking
102
- if q["choices"]:
103
- if student == correct:
104
  score += 1
105
- result = f"✅ Q{i+1}: Correct! (Your Answer: {student})"
106
  else:
107
- result = f"❌ Q{i+1}: Wrong (Your Answer: {student}, Correct: {correct})"
108
- review.append(result)
109
-
110
- # Paper 1: subjective → LLM grading
111
- else:
112
- llm_feedback = grade_with_zhipu(q["question"], student, correct)
113
- review.append(f"✏️ Q{i+1}: Essay Graded → {llm_feedback}")
114
 
115
- summary = f"## Exam Finished!\n\n**Score: {score} / {total}**\n\n" + "\n".join(
116
- review
117
- )
118
- return summary
119
 
120
 
 
 
 
121
  with gr.Blocks() as demo:
122
- gr.Markdown("# 🎓 SPM Exam Simulator (with ZhipuAI Grading)")
123
-
124
- with gr.Row():
125
- subject = gr.Dropdown(choices=list(QUESTIONS.keys()), label="Select Subject")
126
- paper = gr.Dropdown(choices=["Paper1", "Paper2"], label="Select Paper")
127
 
 
128
  load_btn = gr.Button("Load Exam")
129
 
130
- exam_area = gr.Column()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- submit_btn = gr.Button("Submit Answers")
133
- result_box = gr.Markdown("")
134
 
135
- def render_exam(subject, paper):
136
- return load_exam(subject, paper)
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- load_btn.click(render_exam, inputs=[subject, paper], outputs=exam_area)
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- submit_btn.click(submit_exam, inputs=exam_area, outputs=result_box)
141
 
142
- if __name__ == "__main__":
143
- demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=True)
144
 
145
 
146
 
 
 
1
  import gradio as gr
2
+ import random
3
+
4
+ # -------------------------------
5
+ # Demo Exam Data
6
+ # -------------------------------
7
+ QUESTIONS = {
8
+ "Bahasa Melayu Paper 1": [
9
+ {"text": "Tulis sebuah karangan tentang kepentingan membaca.", "answer": None},
10
+ {"text": "Huraikan peranan teknologi dalam kehidupan seharian.", "answer": None},
11
+ ],
12
+ "Bahasa Melayu Paper 2": [
13
+ {"text": "Apakah kata dasar bagi perkataan 'berjalan'?",
14
+ "choices": ["A. Ber", "B. Jalan", "C. An", "D. Jal"], "answer": "B"},
15
+ {"text": "Peribahasa 'harapkan pegar, pegar makan padi' bermaksud?",
16
+ "choices": ["A. Orang rajin", "B. Pengkhianatan oleh penjaga", "C. Hidup susah", "D. Orang baik hati"], "answer": "B"},
17
+ ],
18
+ "English Paper 1": [
19
+ {"text": "Write an essay about the importance of sports.", "answer": None},
20
+ {"text": "Describe a memorable day at school.", "answer": None},
21
+ ],
22
+ "English Paper 2": [
23
+ {"text": "Choose the correct synonym for 'happy'.",
24
+ "choices": ["A. Sad", "B. Joyful", "C. Angry", "D. Tired"], "answer": "B"},
25
+ {"text": "What is the plural of 'child'?",
26
+ "choices": ["A. Childs", "B. Childes", "C. Children", "D. Childer"], "answer": "C"},
27
+ ],
28
+ }
29
+
30
+ # -------------------------------
31
+ # Load Exam Function
32
+ # -------------------------------
33
+ def load_exam(subject):
34
+ questions = QUESTIONS.get(subject, [])
35
+ outputs = []
36
 
37
+ for i, q in enumerate(questions, 1):
38
+ # Display question text
39
+ outputs.append(f"**Q{i}:** {q['text']}")
40
 
41
+ # Subjective Paper → Textbox
42
+ if "choices" not in q:
43
+ outputs.append("") # empty string = textbox placeholder
44
 
45
+ # Objective Paper → Radio buttons
46
+ else:
47
+ outputs.append({"choices": q["choices"], "value": None})
48
 
49
+ return outputs, questions
 
 
 
50
 
 
 
51
 
52
+ # -------------------------------
53
+ # Submit Exam Function
54
+ # -------------------------------
55
+ def submit_exam(answers, questions):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  score = 0
57
+ results = []
 
 
 
58
 
59
+ for ans, q in zip(answers, questions):
60
+ if "choices" in q: # objective paper
61
+ correct = q["answer"]
62
+ if ans == correct:
 
 
 
 
63
  score += 1
64
+ results.append(f"✅ {q['text']} Your answer: {ans} (Correct)")
65
  else:
66
+ results.append(f"❌ {q['text']} Your answer: {ans}, Correct answer: {correct}")
67
+ else: # subjective paper
68
+ results.append(f"📝 {q['text']} → Your response recorded.")
 
 
 
 
69
 
70
+ return f"Your Score: {score} / {len(questions)}", "\n\n".join(results)
 
 
 
71
 
72
 
73
+ # -------------------------------
74
+ # Build Gradio UI
75
+ # -------------------------------
76
  with gr.Blocks() as demo:
77
+ gr.Markdown("## 📘 SPM Exam Simulator")
 
 
 
 
78
 
79
+ subject = gr.Dropdown(list(QUESTIONS.keys()), label="Select Subject")
80
  load_btn = gr.Button("Load Exam")
81
 
82
+ question_boxes = []
83
+ answer_inputs = []
84
+
85
+ # Preallocate 10 slots (max 10 questions per subject for demo)
86
+ for i in range(10):
87
+ qtext = gr.Markdown(visible=False)
88
+ atext = gr.Textbox(visible=False, label="Your Answer")
89
+ aradio = gr.Radio(visible=False, label="Select Answer")
90
+ question_boxes.append((qtext, atext, aradio))
91
+ answer_inputs.append((atext, aradio))
92
+
93
+ submit_btn = gr.Button("Submit", visible=False)
94
+ score_output = gr.Markdown()
95
+ detail_output = gr.Markdown()
96
+ state_questions = gr.State()
97
+
98
+ def on_load(subject):
99
+ outputs, questions = load_exam(subject)
100
+
101
+ # Reset UI
102
+ updates = []
103
+ answers_state = []
104
+
105
+ for i, (qtext, atext, aradio) in enumerate(question_boxes):
106
+ if i < len(questions):
107
+ q = questions[i]
108
+ qtext.update(value=f"**Q{i+1}:** {q['text']}", visible=True)
109
+
110
+ if "choices" in q: # objective
111
+ atext.update(visible=False)
112
+ aradio.update(choices=q["choices"], value=None, visible=True)
113
+ answers_state.append(None)
114
+ else: # subjective
115
+ aradio.update(visible=False)
116
+ atext.update(value="", visible=True)
117
+ answers_state.append("")
118
+ else:
119
+ qtext.update(visible=False)
120
+ atext.update(visible=False)
121
+ aradio.update(visible=False)
122
+
123
+ updates.extend([qtext, atext, aradio])
124
 
125
+ return updates + [gr.Button.update(visible=True), questions]
 
126
 
127
+ def on_submit(*all_inputs):
128
+ # all_inputs includes answers + state_questions
129
+ *answers, questions = all_inputs
130
+
131
+ # Extract answers: pick from textbox or radio
132
+ processed = []
133
+ for (txt, rad), ans in zip(answer_inputs, answers):
134
+ if isinstance(ans, str):
135
+ processed.append(ans)
136
+ elif isinstance(ans, dict) and "value" in ans:
137
+ processed.append(ans["value"])
138
+ else:
139
+ processed.append(None)
140
 
141
+ return submit_exam(processed, questions)
142
+
143
+ load_btn.click(
144
+ fn=on_load,
145
+ inputs=[subject],
146
+ outputs=[comp for triple in question_boxes for comp in triple] + [submit_btn, state_questions],
147
+ )
148
+
149
+ submit_btn.click(
150
+ fn=on_submit,
151
+ inputs=[comp for pair in answer_inputs for comp in pair] + [state_questions],
152
+ outputs=[score_output, detail_output],
153
+ )
154
 
155
+ demo.launch(server_name="0.0.0.0", server_port=7860)
156
 
 
 
157
 
158
 
159