simonraj commited on
Commit
2e5e5ac
·
verified ·
1 Parent(s): 01a50df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -87
app.py CHANGED
@@ -1,146 +1,216 @@
 
1
  import gradio as gr
 
2
  import os
3
  import thinkingframes
4
  import soundfile as sf
5
  import numpy as np
6
  import logging
 
 
 
7
  from dotenv import load_dotenv
8
  from policy import user_acceptance_policy
9
  from styles import theme
10
- from thinkingframes import generate_prompt, strategy_options, questions
11
  from utils import get_image_html, collect_student_info
12
- from database_functions import add_user_privacy, add_submission
13
  from tab_teachers_dashboard import create_teachers_dashboard_tab
14
  from config import CLASS_OPTIONS
15
- import spaces
16
- import edge_tts
17
  import tempfile
18
- import requests # Ensure this import is included for making API requests
19
-
20
-
21
- # Load environment variables
22
- load_dotenv()
23
-
24
- # Whisper API settings
25
- API_URL = "https://api-inference.huggingface.co/models/whisper-large"
26
- headers = {"Authorization": f"Bearer {os.getenv('HF_AUTH_TOKEN')}"}
27
 
28
- def whisper_query(filename):
29
- with open(filename, "rb") as f:
30
- data = f.read()
31
- response = requests.post(API_URL, headers=headers, data=data)
32
- return response.json()
33
 
34
  # For maintaining user session (to keep track of userID)
35
  user_state = gr.State(value="")
36
 
37
- # Load the Meta-Llama-3-8B model from Hugging Face
38
- llm = gr.load("meta-llama/Meta-Llama-3-8B", src="models")
39
 
 
 
 
40
  image_path = "picturePerformance.jpg"
41
  img_html = get_image_html(image_path)
42
 
43
- @spaces.GPU(duration=120)
 
 
 
44
  def transcribe(audio_path):
45
- response = whisper_query(audio_path)
46
- if "text" in response:
47
- return response["text"]
48
- else:
49
- raise ValueError("Transcription failed.")
50
 
 
51
  @spaces.GPU(duration=120)
52
- def generate_feedback(user_id, question_choice, strategy_choice, message, feedback_level):
53
- current_question_index = questions.index(question_choice)
54
- strategy, explanation = strategy_options[strategy_choice]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  conversation = [{
57
  "role": "system",
58
- "content": thinkingframes.generate_system_message(current_question_index, feedback_level)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }, {
60
  "role": "user",
61
  "content": message
62
  }]
63
 
64
- feedback = llm(conversation)[0]["generated_text"]
65
-
66
- questionNo = current_question_index + 1
67
- add_submission(user_id, message, feedback, int(0), "", questionNo)
 
 
 
 
 
68
 
69
- return feedback
 
70
 
71
- @spaces.GPU(duration=60)
72
- def generate_audio_feedback(feedback_buffer):
73
- communicate = edge_tts.Communicate(feedback_buffer)
74
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
75
- tmp_path = tmp_file.name
76
- asyncio.run(communicate.save(tmp_path))
77
- return tmp_path
78
 
79
- def predict(question_choice, strategy_choice, feedback_level, audio):
80
- current_audio_output = None
 
 
81
 
82
  if audio is None:
83
- return [("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "No audio data received. Please try again.")], current_audio_output
84
-
 
85
  sample_rate, audio_data = audio
86
 
87
  if audio_data is None or len(audio_data) == 0:
88
- return [("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "No audio data received. Please try again.")], current_audio_output
 
89
 
90
  audio_path = "audio.wav"
91
  if not isinstance(audio_data, np.ndarray):
92
  raise ValueError("audio_data must be a numpy array")
93
  sf.write(audio_path, audio_data, sample_rate)
94
 
95
- chat_history = [("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "Transcribing your audio, please listen to your oral response while waiting ...")]
 
96
 
97
  try:
98
- student_response = transcribe(audio_path)
 
99
 
100
  if not student_response.strip():
101
- return [("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "Transcription failed. Please try again or seek assistance.")], current_audio_output
102
-
103
- chat_history.append(("Student", student_response))
104
-
105
- chat_history.append(("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "Transcription complete. Generating feedback. Please continue listening to your oral response while waiting ..."))
106
-
107
- feedback = generate_feedback(int(user_state.value), question_choice, strategy_choice, student_response, feedback_level)
108
-
109
- chat_history.append(("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", feedback))
110
-
111
- audio_output_path = generate_audio_feedback(feedback)
112
-
113
- current_audio_output = (24000, audio_output_path)
114
- return chat_history, current_audio_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  except Exception as e:
117
  logging.error(f"An error occurred: {str(e)}", exc_info=True)
118
- return [("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "An error occurred. Please try again or seek assistance.")], current_audio_output
119
-
120
- def toggle_oral_coach_visibility(class_name, index_no, policy_checked):
121
- if not policy_checked:
122
- return "Please agree to the Things to Note When using the Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡ before submitting.", gr.update(visible=False)
123
- user_id, message = add_user_privacy(class_name, index_no)
124
- if "Error" in message:
125
- return message, gr.update(visible=False)
126
- user_state.value = user_id
127
- return message, gr.update(visible=True)
128
-
129
- with gr.Blocks(title="Oral Coach powered by ZeroGPU⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡ and Meta AI 🦙 (LLama3)", theme=theme, css="footer {visibility: hidden}textbox{resize:none}") as demo:
130
- with gr.Tab("Oral Coach ⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡"):
131
  gr.Markdown("## Student Information")
132
  class_name = gr.Dropdown(label="Class", choices=CLASS_OPTIONS)
133
  index_no = gr.Dropdown(label="Index No", choices=[f"{i:02}" for i in range(1, 46)])
134
 
135
  policy_text = gr.Markdown(user_acceptance_policy)
136
- policy_checkbox = gr.Checkbox(label="I have read and agree to the Things to Note When using the Oral Coach ⚡ϞϞ(๑�� ․̫ ⚈๑)∩ ⚡", value=False)
137
 
138
  submit_info_btn = gr.Button("Submit Info")
139
  info_output = gr.Text()
140
-
141
  with gr.Column(visible=False) as oral_coach_content:
142
- gr.Markdown("## Powered by Hugging Face")
143
- gr.Markdown(img_html)
144
  with gr.Row():
145
  with gr.Column(scale=1):
146
  gr.Markdown("### Step 1: Choose a Question")
@@ -157,21 +227,38 @@ with gr.Blocks(title="Oral Coach powered by ZeroGPU⚡ϞϞ(๑⚈ ․̫ ⚈๑)
157
  submit_answer_btn = gr.Button("Submit Oral Response")
158
 
159
  gr.Markdown("### Step 5: Review your personalised feedback")
160
- feedback_output = gr.Chatbot(label="Feedback", scale=4, height=700, show_label=True)
161
- audio_output = gr.Audio(type="numpy", label="Audio Playback", format="wav", autoplay="True")
162
-
163
- submit_answer_btn.click(
164
- predict,
165
- inputs=[question_choice, strategy_choice, feedback_level, audio_input],
166
- outputs=[feedback_output, audio_output]
167
  )
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  submit_info_btn.click(
170
  toggle_oral_coach_visibility,
171
  inputs=[class_name, index_no, policy_checkbox],
172
  outputs=[info_output, oral_coach_content]
173
  )
174
 
 
175
  create_teachers_dashboard_tab()
176
 
177
  demo.queue(max_size=20)
 
1
+ # app.py
2
  import gradio as gr
3
+ import asyncio
4
  import os
5
  import thinkingframes
6
  import soundfile as sf
7
  import numpy as np
8
  import logging
9
+ from huggingface_hub import InferenceClient
10
+ from streaming_stt_nemo import Model
11
+ import edge_tts
12
  from dotenv import load_dotenv
13
  from policy import user_acceptance_policy
14
  from styles import theme
15
+ from thinkingframes import generate_prompt, strategy_options
16
  from utils import get_image_html, collect_student_info
17
+ from database_functions import add_submission
18
  from tab_teachers_dashboard import create_teachers_dashboard_tab
19
  from config import CLASS_OPTIONS
20
+ from concurrent.futures import ThreadPoolExecutor
 
21
  import tempfile
22
+ import spaces
 
 
 
 
 
 
 
 
23
 
24
+ # Load CSS from external file
25
+ with open('styles.css', 'r') as file:
26
+ css = file.read()
 
 
27
 
28
  # For maintaining user session (to keep track of userID)
29
  user_state = gr.State(value="")
30
 
31
+ load_dotenv()
 
32
 
33
+ client = InferenceClient("mistralai/Mixtral-8x7B-Instruct-v0.1")
34
+ default_lang = "en"
35
+ engines = {default_lang: Model(default_lang)}
36
  image_path = "picturePerformance.jpg"
37
  img_html = get_image_html(image_path)
38
 
39
+ # Create a thread pool executor
40
+ executor = ThreadPoolExecutor()
41
+
42
+ # Transcription function using streaming_stt_nemo
43
  def transcribe(audio_path):
44
+ lang = "en"
45
+ model = engines[lang]
46
+ with open(audio_path, "rb") as audio_file:
47
+ text = model.stt_file(audio_file)[0]
48
+ return text
49
 
50
+ # Inference function using Hugging Face InferenceClient
51
  @spaces.GPU(duration=120)
52
+ def model(text):
53
+ system_instructions = "[SYSTEM] You are CrucialCoach, an AI-powered conversational coach. Guide the user through challenging workplace situations using the principles from 'Crucial Conversations'. Ask one question at a time and provide step-by-step guidance.\n\n[USER]"
54
+ generate_kwargs = dict(
55
+ temperature=0.7,
56
+ max_new_tokens=512,
57
+ top_p=0.95,
58
+ repetition_penalty=1,
59
+ do_sample=True,
60
+ seed=42,
61
+ )
62
+ formatted_prompt = system_instructions + text + "[CrucialCoach]"
63
+ stream = client.text_generation(
64
+ formatted_prompt, **generate_kwargs, stream=True, details=True, return_full_text=False)
65
+ output = ""
66
+ for response in stream:
67
+ if not response.token.text == "</s>":
68
+ output += response.token.text
69
+ return output
70
+
71
+ # Text-to-Speech function using edge_tts
72
+ async def generate_audio_feedback(feedback_text):
73
+ communicate = edge_tts.Communicate(feedback_text)
74
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
75
+ tmp_path = tmp_file.name
76
+ await communicate.save(tmp_path)
77
+ return tmp_path
78
+
79
+ # Generating feedback for the Oral Coach
80
+ async def generate_feedback(user_id, question_choice, strategy_choice, message, feedback_level):
81
+ current_question_index = thinkingframes.questions.index(question_choice)
82
+ strategy, explanation = thinkingframes.strategy_options[strategy_choice]
83
 
84
  conversation = [{
85
  "role": "system",
86
+ "content": f"You are an expert Primary 6 English Language Teacher in a Singapore Primary school, "
87
+ f"directly guiding a Primary 6 student in Singapore in their oral responses. "
88
+ f"Format the feedback in Markdown so that it can be easily read. "
89
+ f"Address the student directly in the second person in your feedback. "
90
+ f"The student is answering the question: '{thinkingframes.questions[current_question_index]}'. "
91
+ f"For Question 1, consider the picture description: '{thinkingframes.description}'. "
92
+ f"For Questions 2 and 3, the picture is not relevant, so the student should not refer to it in their response. "
93
+ f"Analyze the student's response using the following step-by-step approach: "
94
+ f"1. Evaluate the response against the {strategy} thinking frame. "
95
+ f"2. Assess how well the student's response addresses each criteria of the {strategy} thinking frame: "
96
+ f" - Assign emoticon scores based on how well the student comprehensively covered each criteria: "
97
+ f" - 😊😊😊 (three smiling faces) for a good coverage "
98
+ f" - 😊😊 (two smiling faces) for an average coverage "
99
+ f" - 😊 (one smiling face) for a poor coverage "
100
+ f" - Provide a clear, direct, and concise explanation of how well the answer addresses each criteria. "
101
+ f" - Identify specific areas for improvement in students responses, and provide targeted suggestions for improvement. "
102
+ f"3. Identify overall strengths and areas for improvement in the student's response using the {strategy} to format and provide targeted areas for improvement. "
103
+ f"4. Provide specific feedback on grammar, vocabulary, and sentence structure. "
104
+ f" Suggest age-appropriate enhancements that are one level higher than the student's current response. "
105
+ f"5. Conclude with follow-up questions for reflection. "
106
+ f"If the student's response deviates from the question, provide clear and concise feedback to help them refocus and try again. "
107
+ f"Ensure that the vocabulary and sentence structure recommendations are achievable for Primary 6 students in Singapore. "
108
+ f"Example Feedback Structure for Each Criteria: "
109
+ f"Criteria: [Criteria Name] "
110
+ f"Score: [Smiling emoticons] "
111
+ f"Explanation: [Clear, direct, and concise explanation of how well the answer addresses the criteria. Identify specific areas for improvement, and provide targeted suggestions for improvement.] "
112
+ f"{thinkingframes.generate_prompt(feedback_level)}"
113
  }, {
114
  "role": "user",
115
  "content": message
116
  }]
117
 
118
+ response = model(conversation)
119
+ chat_history = [] # Initialize chat history outside the loop
120
+ full_feedback = "" # Accumulate the entire feedback message
121
+ try:
122
+ for chunk in response:
123
+ if chunk.choices[0].delta and chunk.choices[0].delta.content:
124
+ feedback_chunk = chunk.choices[0].delta.content
125
+ yield feedback_chunk # Yield each feedback chunk as it is generated
126
+ await asyncio.sleep(0)
127
 
128
+ except Exception as e:
129
+ logging.error(f"An error occurred during feedback generation: {str(e)}")
130
 
131
+ questionNo = current_question_index + 1
132
+ add_submission(user_id, message, full_feedback, int(0), "", questionNo)
 
 
 
 
 
133
 
134
+ # Function to predict and handle the entire workflow
135
+ async def predict(question_choice, strategy_choice, feedback_level, audio):
136
+ current_audio_output = None # Initialize current_audio_output to None
137
+ final_feedback = "" # Store only the assistant's feedback
138
 
139
  if audio is None:
140
+ yield [("Oral Coach ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "No audio data received. Please try again.")], current_audio_output
141
+ return
142
+
143
  sample_rate, audio_data = audio
144
 
145
  if audio_data is None or len(audio_data) == 0:
146
+ yield [("Oral Coach ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "No audio data received. Please try again.")], current_audio_output
147
+ return
148
 
149
  audio_path = "audio.wav"
150
  if not isinstance(audio_data, np.ndarray):
151
  raise ValueError("audio_data must be a numpy array")
152
  sf.write(audio_path, audio_data, sample_rate)
153
 
154
+ chat_history = [("Oral Coach ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "Transcribing your audio, please listen to your oral response while waiting ...")]
155
+ yield chat_history, current_audio_output
156
 
157
  try:
158
+ transcription_future = executor.submit(transcribe, audio_path)
159
+ student_response = await asyncio.wrap_future(transcription_future)
160
 
161
  if not student_response.strip():
162
+ yield [("Oral Coach ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "Transcription failed. Please try again or seek assistance.")], current_audio_output
163
+ return
164
+
165
+ chat_history.append(("Student", student_response)) # Add student's transcript
166
+ yield chat_history, current_audio_output
167
+
168
+ chat_history.append(("Oral Coach ⚡ ϞϞ(๑⚈ ․̫ ⚈๑) ⚡", "Transcription complete. Generating feedback. Please continue listening to your oral response while waiting ..."))
169
+ yield chat_history, current_audio_output
170
+
171
+ moderation_response = client.moderations.create(input=student_response)
172
+ flagged = any(result.flagged for result in moderation_response.results)
173
+ if flagged:
174
+ moderated_message = "The message has been flagged. Please see your teacher to clarify."
175
+ questionNo = thinkingframes.questions.index(question_choice) + 1
176
+ add_submission(int(user_state.value), moderated_message, "", int(0), "", questionNo)
177
+ yield chat_history, current_audio_output
178
+ return
179
+
180
+ accumulated_feedback = "" # Variable to store the accumulated feedback
181
+
182
+ async for feedback_chunk in generate_feedback(int(user_state.value), question_choice, strategy_choice, student_response, feedback_level):
183
+ accumulated_feedback += feedback_chunk # Accumulate the feedback chunks
184
+ if chat_history and chat_history[-1][0] == "Oral Coach ⚡ ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡":
185
+ chat_history[-1] = ("Oral Coach ⚡ ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", accumulated_feedback) # Update the last message in chat_history
186
+ else:
187
+ chat_history.append(("Oral Coach ⚡ ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", accumulated_feedback)) # Append a new message to chat_history
188
+ yield chat_history, current_audio_output # Yield the updated chat_history and current_audio_output
189
+
190
+ feedback_buffer = accumulated_feedback # Use the accumulated feedback for TTS
191
+ audio_task = asyncio.create_task(generate_audio_feedback(feedback_buffer))
192
+ current_audio_output = await audio_task # Store audio output
193
+ yield chat_history, current_audio_output # Yield the final chat_history and current_audio_output
194
 
195
  except Exception as e:
196
  logging.error(f"An error occurred: {str(e)}", exc_info=True)
197
+ yield [("Oral Coach ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", "An error occurred. Please try again or seek assistance.")], current_audio_output
198
+
199
+ with gr.Blocks(title="Oral Coach powered by ZeroGPU⚡ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡ and Meta AI 🦙 (LLama3)", theme=theme, css="footer {visibility: hidden}textbox{resize:none}") as demo:
200
+ with gr.Tab("Oral Coach ⚡ ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡"):
 
 
 
 
 
 
 
 
 
201
  gr.Markdown("## Student Information")
202
  class_name = gr.Dropdown(label="Class", choices=CLASS_OPTIONS)
203
  index_no = gr.Dropdown(label="Index No", choices=[f"{i:02}" for i in range(1, 46)])
204
 
205
  policy_text = gr.Markdown(user_acceptance_policy)
206
+ policy_checkbox = gr.Checkbox(label="I have read and agree to the Things to Note When using the Oral Coach ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡", value=False)
207
 
208
  submit_info_btn = gr.Button("Submit Info")
209
  info_output = gr.Text()
210
+
211
  with gr.Column(visible=False) as oral_coach_content:
212
+ gr.Markdown("## English Language Oral Coach ⚡ ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡")
213
+ gr.Markdown(img_html) # Display the image
214
  with gr.Row():
215
  with gr.Column(scale=1):
216
  gr.Markdown("### Step 1: Choose a Question")
 
227
  submit_answer_btn = gr.Button("Submit Oral Response")
228
 
229
  gr.Markdown("### Step 5: Review your personalised feedback")
230
+ feedback_output = gr.Chatbot(
231
+ label="Feedback",
232
+ scale=4,
233
+ height=700,
234
+ show_label=True
 
 
235
  )
236
 
237
+ audio_output = gr.Audio(type="numpy", label="Audio Playback", format="wav", autoplay=True)
238
+
239
+ submit_answer_btn.click(
240
+ predict,
241
+ inputs=[question_choice, strategy_choice, feedback_level, audio_input],
242
+ outputs=[feedback_output, audio_output],
243
+ api_name="predict"
244
+ )
245
+
246
+ def toggle_oral_coach_visibility(class_name, index_no, policy_checked):
247
+ if not policy_checked:
248
+ return "Please agree to the Things to Note When using the Oral Coach ⚡ ϞϞ(๑⚈ ․̫ ⚈๑)∩ ⚡ before submitting.", gr.update(visible=False)
249
+ validation_passed, message, userid = collect_student_info(class_name, index_no)
250
+ if not validation_passed:
251
+ return message, gr.update(visible=False)
252
+ user_state.value = userid
253
+ return message, gr.update(visible=True)
254
+
255
  submit_info_btn.click(
256
  toggle_oral_coach_visibility,
257
  inputs=[class_name, index_no, policy_checkbox],
258
  outputs=[info_output, oral_coach_content]
259
  )
260
 
261
+ # Define other tabs like Teacher's Dashboard
262
  create_teachers_dashboard_tab()
263
 
264
  demo.queue(max_size=20)