mramirez2001 commited on
Commit
05b2dea
verified
1 Parent(s): fd30b83

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +124 -85
app.py CHANGED
@@ -18,136 +18,175 @@ except TypeError:
18
  api_key_found = False
19
 
20
  print("Loading Whisper for transcription...")
 
21
  whisper_model = whisper.load_model("base", device="cpu")
22
  print("Whisper model loaded.")
23
 
24
 
25
  # --- 1. DEFINICI脫N DE PROMPTS PARA LA IA ---
26
 
27
- # Prompt para la conversaci贸n
28
- CONVERSATION_SYSTEM_PROMPT = "..." # (Mantener el prompt de conversaci贸n que ya tienes)
 
 
 
 
 
 
 
 
 
 
29
 
30
- # Prompt para la evaluaci贸n final de la conversaci贸n
31
- FINAL_EVALUATION_SYSTEM_PROMPT = "..." # (Mantener el prompt de evaluaci贸n final que ya tienes)
32
 
33
- # Prompt para la evaluaci贸n de una sola frase
34
- SENTENCE_EVALUATION_SYSTEM_PROMPT = """
35
- You are an expert English language examiner specializing in phonetics. Your task is to provide a detailed, diagnostic assessment of a student's spoken English based on a reference sentence and detailed word-level audio analysis. Your entire response MUST be in English. You must return a single, valid JSON object with the following structure. Do not include any text outside of this JSON object.
36
  JSON Output Structure:
37
  {
38
- "overall_score_100": integer,
39
- "cefr_level": "string (A1, A2, B1, B2, C1, or C2)",
40
- "holistic_feedback": {
41
- "strengths": "string (A paragraph in English summarizing strong points.)",
42
- "areas_for_improvement": "string (A paragraph in English detailing main error patterns.)"
 
 
43
  },
44
- "word_by_word_analysis": [
45
- {
46
- "reference_word": "string",
47
- "spoken_word": "string",
48
- "word_score_100": integer,
49
- "correct_ipa": "string",
50
- "feedback": "string"
51
- }
52
- ]
53
  }
54
  """
55
 
56
 
57
  # --- 2. FUNCIONES L脫GICAS ---
58
 
59
- # Funci贸n para la Pesta帽a "Pr谩ctica Conversacional"
60
- def chat_interaction(audio_input, history_state):
61
- # ... (Mantener la funci贸n 'chat_interaction' completa que ya tienes)
62
- pass # Placeholder for your existing chat_interaction function
63
-
64
- # Funci贸n para la Pesta帽a "Evaluaci贸n por Frase"
65
- def run_sentence_evaluation(audio_input, reference_transcript):
66
- # This is the 'run_evaluation' function from your previous, single-tab app
67
- if not api_key_found: raise gr.Error("OpenAI API key not found.")
68
- if audio_input is None or not reference_transcript:
69
- return 0, "N/A", "Please provide both an audio file and the reference text.", None
70
-
71
  sr, y = audio_input
72
  temp_audio_path = "temp_audio.wav"
73
  sf.write(temp_audio_path, y, sr)
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- # (La l贸gica de 'extract_word_level_features' y la llamada a la API va aqu铆)
76
- # For brevity, this part is condensed. Use the full function from your previous script.
77
 
78
- # Placeholder for the actual API call logic
79
- word_analysis_df = pd.DataFrame({
80
- "Reference Word": reference_transcript.split(),
81
- "Spoken Word": reference_transcript.split(),
82
- "Score": [np.random.randint(80, 100) for _ in reference_transcript.split()],
83
- "Correct IPA": ["..."], "Feedback": ["..."]
84
- })
85
- holistic_feedback_md = "### Strengths\nExcellent clarity.\n\n### Areas for Improvement\nWork on sentence intonation."
86
 
87
- return 92, "B2", holistic_feedback_md, gr.DataFrame(value=word_analysis_df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- # --- 3. INTERFAZ DE GRADIO CON PESTA脩AS ---
 
 
 
 
91
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
92
  gr.Markdown("# 馃嚞馃嚙 AI English Speaking Practice & Assessment")
93
 
94
  with gr.Tabs():
95
- # --- Pesta帽a 1: Chat AI ---
96
  with gr.TabItem("Pr谩ctica Conversacional (Chat AI)"):
97
  with gr.Row():
98
  with gr.Column(scale=2):
99
- chatbot = gr.Chatbot(value=[(None, "Hi there! I'm Alex. How are you doing today?")], label="Conversation with your AI Tutor")
 
 
 
100
  audio_in_chat = gr.Audio(sources=["microphone"], type="numpy", label="Record your response")
101
  with gr.Column(scale=1):
102
  gr.Markdown("### Final Report")
103
  feedback_en_out = gr.Markdown(label="English Feedback", visible=False)
104
  feedback_es_out = gr.Markdown(label="Retroalimentaci贸n en Espa帽ol", visible=False)
 
 
105
  history = gr.State([])
 
106
  audio_in_chat.stop_recording(
107
  fn=chat_interaction,
108
  inputs=[audio_in_chat, history],
109
  outputs=[chatbot, history, feedback_en_out, feedback_es_out]
110
  )
111
 
112
- # --- Pesta帽a 2: Evaluaci贸n por Frase ---
113
  with gr.TabItem("Evaluaci贸n por Frase"):
114
- TONGUE_TWISTERS = [
115
- "Peter Piper picked a peck of pickled peppers.",
116
- "She sells seashells by the seashore.",
117
- "How much wood would a woodchuck chuck if a woodchuck could chuck wood?",
118
- "Betty Botter bought some butter but she said the butter鈥檚 bitter.",
119
- "A proper copper coffee pot."
120
- ]
121
-
122
- gr.Markdown("Choose a tongue twister or write your own sentence. Record yourself, and our AI examiner will provide a detailed diagnostic report.")
123
-
124
- tongue_twister_selector = gr.Dropdown(choices=TONGUE_TWISTERS, label="Or Choose a Tongue Twister to Practice")
125
-
126
- with gr.Row():
127
- with gr.Column(scale=1):
128
- audio_in_sentence = gr.Audio(sources=["microphone"], type="numpy", label="1. Record Your Voice")
129
- text_in_sentence = gr.Textbox(lines=3, label="2. Reference Sentence", value=TONGUE_TWISTERS[0])
130
- submit_btn_sentence = gr.Button("Get Assessment", variant="primary")
131
-
132
- with gr.Column(scale=2):
133
- gr.Markdown("### Assessment Summary")
134
- with gr.Row():
135
- score_out_sentence = gr.Number(label="Overall Score (0-100)", interactive=False)
136
- level_out_sentence = gr.Textbox(label="Estimated CEFR Level", interactive=False)
137
- holistic_feedback_out_sentence = gr.Markdown(label="Examiner's Feedback")
138
-
139
- gr.Markdown("--- \n ### Detailed Word-by-Word Analysis")
140
- word_analysis_out_sentence = gr.DataFrame(headers=["Reference Word", "Spoken Word", "Score", "Correct IPA", "Feedback"], label="Phonetic Breakdown", wrap=True)
141
-
142
- def update_text(choice):
143
- return gr.Textbox(value=choice)
144
- tongue_twister_selector.change(fn=update_text, inputs=tongue_twister_selector, outputs=text_in_sentence)
145
-
146
- submit_btn_sentence.click(
147
- fn=run_sentence_evaluation,
148
- inputs=[audio_in_sentence, text_in_sentence],
149
- outputs=[score_out_sentence, level_out_sentence, holistic_feedback_out_sentence, word_analysis_out_sentence]
150
- )
151
 
152
  if __name__ == "__main__":
153
  if not api_key_found:
 
18
  api_key_found = False
19
 
20
  print("Loading Whisper for transcription...")
21
+ # Usamos el modelo 'base' que es un buen compromiso entre velocidad y precisi贸n
22
  whisper_model = whisper.load_model("base", device="cpu")
23
  print("Whisper model loaded.")
24
 
25
 
26
  # --- 1. DEFINICI脫N DE PROMPTS PARA LA IA ---
27
 
28
+ # Prompt para mantener la conversaci贸n
29
+ CONVERSATION_SYSTEM_PROMPT = """
30
+ You are a friendly and encouraging English language tutor named Alex.
31
+ A student will speak to you. Your task is to keep a natural, simple conversation going.
32
+ 1. Briefly analyze the user's previous response to estimate their CEFR level (A1, A2, B1, etc.).
33
+ 2. Formulate a simple, open-ended follow-up question that is appropriate for THAT estimated level.
34
+ 3. Your entire response must be a single, short paragraph in natural, conversational English. DO NOT use JSON.
35
+ """
36
+
37
+ # Prompt para la evaluaci贸n final
38
+ FINAL_EVALUATION_SYSTEM_PROMPT = """
39
+ You are an expert English language examiner providing a final report. Analyze the entire conversation history provided.
40
 
41
+ Your task is to return a single, valid JSON object with the following structure. Do not include any text outside this JSON object.
 
42
 
 
 
 
43
  JSON Output Structure:
44
  {
45
+ "cefr_level": "string (e.g., A2, B1)",
46
+ "feedback_en": {
47
+ "strengths": "string (A paragraph summarizing the student's strong points in pronunciation, vocabulary, and fluency.)",
48
+ "areas_for_improvement": "string (A paragraph detailing the main patterns of error and what to focus on.)",
49
+ "word_by_word_feedback": [
50
+ {"word": "string", "feedback": "string (Specific phonetic or usage feedback.)"}
51
+ ]
52
  },
53
+ "feedback_es": {
54
+ "fortalezas": "string (Un p谩rrafo resumiendo los puntos fuertes del estudiante en pronunciaci贸n, vocabulario y fluidez.)",
55
+ "areas_a_mejorar": "string (Un p谩rrafo detallando los patrones de error principales y en qu茅 enfocarse.)",
56
+ "feedback_por_palabra": [
57
+ {"palabra": "string", "feedback": "string (Retroalimentaci贸n fon茅tica o de uso espec铆fica.)"}
58
+ ]
59
+ }
 
 
60
  }
61
  """
62
 
63
 
64
  # --- 2. FUNCIONES L脫GICAS ---
65
 
66
+ def transcribe_audio(audio_input):
67
+ """Transcribe el audio usando la API de Whisper de OpenAI."""
68
+ if audio_input is None:
69
+ return ""
 
 
 
 
 
 
 
 
70
  sr, y = audio_input
71
  temp_audio_path = "temp_audio.wav"
72
  sf.write(temp_audio_path, y, sr)
73
+ with open(temp_audio_path, "rb") as audio_file:
74
+ transcript = client.audio.transcriptions.create(
75
+ model="whisper-1",
76
+ file=audio_file
77
+ ).text
78
+ return transcript
79
+
80
+ def chat_interaction(audio_input, history_state):
81
+ """
82
+ Gestiona una vuelta de la conversaci贸n.
83
+ """
84
+ if not api_key_found: raise gr.Error("OpenAI API key not found.")
85
+ if audio_input is None: return history_state, history_state, "", ""
86
 
87
+ # 1. Transcribir el audio del usuario
88
+ user_text = transcribe_audio(audio_input)
89
 
90
+ # 2. Actualizar el historial con el mensaje del usuario
91
+ history_state.append({"role": "user", "content": user_text})
 
 
 
 
 
 
92
 
93
+ # Formatear para el chatbot de Gradio
94
+ chat_display = []
95
+ for i, msg in enumerate(history_state):
96
+ if msg['role'] == 'user':
97
+ chat_display.append((msg['content'], None))
98
+ elif msg['role'] == 'assistant':
99
+ if chat_display and chat_display[-1][1] is None:
100
+ chat_display[-1] = (chat_display[-1][0], msg['content'])
101
+
102
+ # 3. Decidir si continuar la conversaci贸n o dar el reporte final
103
+ if len(history_state) < 9: # 1 system + 4 pares de user/assistant
104
+ # --- Continuar conversaci贸n ---
105
+ messages_to_send = [{"role": "system", "content": CONVERSATION_SYSTEM_PROMPT}] + history_state
106
+
107
+ response = client.chat.completions.create(
108
+ model="gpt-4o",
109
+ messages=messages_to_send,
110
+ temperature=0.7
111
+ )
112
+ ai_response = response.choices[0].message.content
113
+ history_state.append({"role": "assistant", "content": ai_response})
114
+ chat_display[-1] = (chat_display[-1][0], ai_response)
115
+
116
+ return chat_display, history_state, gr.Markdown(visible=False), gr.Markdown(visible=False)
117
 
118
+ else:
119
+ # --- Generar evaluaci贸n final ---
120
+ print("Generating final evaluation...")
121
+ messages_to_send = [{"role": "system", "content": FINAL_EVALUATION_SYSTEM_PROMPT}] + history_state
122
+
123
+ response = client.chat.completions.create(
124
+ model="gpt-4o",
125
+ response_format={"type": "json_object"},
126
+ messages=messages_to_send
127
+ )
128
+
129
+ try:
130
+ result = json.loads(response.choices[0].message.content)
131
+
132
+ # Formatear el feedback en Ingl茅s
133
+ fb_en = result.get('feedback_en', {})
134
+ md_en = f"## Final Report (CEFR Level: {result.get('cefr_level', 'N/A')})\n"
135
+ md_en += f"### Strengths\n{fb_en.get('strengths', '')}\n"
136
+ md_en += f"### Areas for Improvement\n{fb_en.get('areas_for_improvement', '')}\n"
137
+ md_en += "### Word-by-Word Feedback\n"
138
+ for item in fb_en.get('word_by_word_feedback', []):
139
+ md_en += f"- **{item['word']}**: {item['feedback']}\n"
140
+
141
+ # Formatear el feedback en Espa帽ol
142
+ fb_es = result.get('feedback_es', {})
143
+ md_es = f"## Reporte Final (Nivel MCERL: {result.get('cefr_level', 'N/A')})\n"
144
+ md_es += f"### Fortalezas\n{fb_es.get('fortalezas', '')}\n"
145
+ md_es += f"### 脕reas a Mejorar\n{fb_es.get('areas_a_mejorar', '')}\n"
146
+ md_es += "### Retroalimentaci贸n por Palabra\n"
147
+ for item in fb_es.get('feedback_por_palabra', []):
148
+ md_es += f"- **{item['palabra']}**: {item['feedback']}\n"
149
+
150
+ # Mensaje final para el chat
151
+ final_message = "Thank you for the conversation! Here is your final report."
152
+ chat_display[-1] = (chat_display[-1][0], final_message)
153
+
154
+ return chat_display, history_state, gr.Markdown(value=md_en, visible=True), gr.Markdown(value=md_es, visible=True)
155
 
156
+ except (json.JSONDecodeError, KeyError) as e:
157
+ print(f"Error parsing final report: {e}")
158
+ return chat_display, history_state, gr.Markdown(value="Error generating report.", visible=True), gr.Markdown(visible=False)
159
+
160
+ # --- 3. INTERFAZ DE GRADIO ---
161
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
162
  gr.Markdown("# 馃嚞馃嚙 AI English Speaking Practice & Assessment")
163
 
164
  with gr.Tabs():
 
165
  with gr.TabItem("Pr谩ctica Conversacional (Chat AI)"):
166
  with gr.Row():
167
  with gr.Column(scale=2):
168
+ chatbot = gr.Chatbot(
169
+ value=[(None, "Hi there! I'm Alex. How are you doing today?")],
170
+ label="Conversation with your AI Tutor"
171
+ )
172
  audio_in_chat = gr.Audio(sources=["microphone"], type="numpy", label="Record your response")
173
  with gr.Column(scale=1):
174
  gr.Markdown("### Final Report")
175
  feedback_en_out = gr.Markdown(label="English Feedback", visible=False)
176
  feedback_es_out = gr.Markdown(label="Retroalimentaci贸n en Espa帽ol", visible=False)
177
+
178
+ # Estado para guardar el historial de la conversaci贸n
179
  history = gr.State([])
180
+
181
  audio_in_chat.stop_recording(
182
  fn=chat_interaction,
183
  inputs=[audio_in_chat, history],
184
  outputs=[chatbot, history, feedback_en_out, feedback_es_out]
185
  )
186
 
 
187
  with gr.TabItem("Evaluaci贸n por Frase"):
188
+ gr.Markdown("This is a placeholder for the original sentence evaluation tool.")
189
+ # Aqu铆 pegar铆as la interfaz de la herramienta anterior si quisieras combinar ambas.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  if __name__ == "__main__":
192
  if not api_key_found: