Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -11,57 +11,13 @@ import whisper
|
|
| 11 |
import pandas as pd
|
| 12 |
from gtts import gTTS
|
| 13 |
import re
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
# ---
|
| 16 |
-
|
| 17 |
-
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
| 18 |
-
api_key_found = True
|
| 19 |
-
except TypeError:
|
| 20 |
-
api_key_found = False
|
| 21 |
|
| 22 |
-
|
| 23 |
-
whisper_model = whisper.load_model("base", device="cpu")
|
| 24 |
-
print("Whisper model loaded.")
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
# --- 1. DEFINICI脫N DE PROMPTS PARA LA IA ---
|
| 28 |
-
# (Tus prompts completos van aqu铆...)
|
| 29 |
-
CONVERSATION_SYSTEM_PROMPT = """..."""
|
| 30 |
-
FINAL_EVALUATION_SYSTEM_PROMPT = """..."""
|
| 31 |
-
SENTENCE_EVALUATION_SYSTEM_PROMPT = """
|
| 32 |
-
You are an expert English language examiner...
|
| 33 |
-
JSON Output Structure:
|
| 34 |
-
{
|
| 35 |
-
"overall_score_100": integer,
|
| 36 |
-
"cefr_level": "string",
|
| 37 |
-
"holistic_feedback": { "strengths": "string", "areas_for_improvement": "string" },
|
| 38 |
-
"word_by_word_analysis": [ { "reference_word": "string", "spoken_word": "string", "word_score_100": integer, "correct_ipa": "string", "feedback_en": "string", "feedback_es": "string" } ]
|
| 39 |
-
}
|
| 40 |
-
"""
|
| 41 |
-
|
| 42 |
-
# --- 2. FUNCIONES L脫GICAS ---
|
| 43 |
-
|
| 44 |
-
def extract_word_level_features(audio_path):
|
| 45 |
-
try:
|
| 46 |
-
y, sr = librosa.load(audio_path, sr=16000)
|
| 47 |
-
result = whisper_model.transcribe(audio_path, word_timestamps=True, fp16=False)
|
| 48 |
-
if not result["segments"] or 'words' not in result["segments"][0]: return []
|
| 49 |
-
word_segments = result["segments"][0]["words"]
|
| 50 |
-
features_list = []
|
| 51 |
-
for segment in word_segments:
|
| 52 |
-
start_sample = int(segment['start'] * sr); end_sample = int(segment['end'] * sr)
|
| 53 |
-
word_audio = y[start_sample:end_sample]
|
| 54 |
-
rms_energy = np.mean(librosa.feature.rms(y=word_audio)) if len(word_audio) > 0 else 0
|
| 55 |
-
features_list.append({"word": segment['word'].strip(), "start": round(segment['start'], 2), "end": round(segment['end'], 2), "energy": round(float(rms_energy), 4)})
|
| 56 |
-
return features_list
|
| 57 |
-
except Exception as e:
|
| 58 |
-
print(f"Error during feature extraction: {e}"); return []
|
| 59 |
-
|
| 60 |
-
def chat_interaction(audio_input, history_state):
|
| 61 |
-
# (Tu funci贸n de chat sin cambios va aqu铆...)
|
| 62 |
-
pass
|
| 63 |
-
|
| 64 |
-
# --- CAMBIO: La funci贸n de evaluaci贸n de frase ahora devuelve MARKDOWN ---
|
| 65 |
def run_sentence_evaluation(audio_input, reference_transcript):
|
| 66 |
if not api_key_found: raise gr.Error("OpenAI API key not found.")
|
| 67 |
if audio_input is None or not reference_transcript:
|
|
@@ -81,21 +37,26 @@ def run_sentence_evaluation(audio_input, reference_transcript):
|
|
| 81 |
holistic_feedback_md = f"### Strengths\n{result['holistic_feedback']['strengths']}\n\n### Areas for Improvement\n{result['holistic_feedback']['areas_for_improvement']}"
|
| 82 |
word_analysis_list = result['word_by_word_analysis']
|
| 83 |
|
| 84 |
-
# --- NUEVA L脫GICA: Construir
|
| 85 |
md_table = "| Reference Word | Spoken Word | Score | Feedback (EN) | Feedback (ES) | Reference Audio |\n"
|
| 86 |
md_table += "| :--- | :--- | :---: | :--- | :--- | :---: |\n"
|
| 87 |
|
| 88 |
-
os.makedirs("reference_audio", exist_ok=True)
|
| 89 |
-
|
| 90 |
for index, item in enumerate(word_analysis_list):
|
| 91 |
word_to_speak = item['reference_word']
|
| 92 |
-
safe_filename = re.sub(r'\W+', '', word_to_speak.lower())
|
| 93 |
-
audio_path = f"reference_audio/{index}_{safe_filename}.mp3"
|
| 94 |
|
| 95 |
try:
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
except Exception as e:
|
| 100 |
print(f"Error al generar TTS para '{word_to_speak}': {e}"); audio_player = "Error"
|
| 101 |
|
|
@@ -107,40 +68,20 @@ def run_sentence_evaluation(audio_input, reference_transcript):
|
|
| 107 |
return 0, "Error", error_msg, ""
|
| 108 |
|
| 109 |
|
| 110 |
-
# --- 3. INTERFAZ DE GRADIO CON PESTA脩AS (
|
| 111 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
# PESTA脩A 1: CHAT AI (sin cambios)
|
| 115 |
-
with gr.TabItem("Pr谩ctica Conversacional (Chat AI)"):
|
| 116 |
-
# ... (Aqu铆 va toda la definici贸n de la interfaz de tu chatbot, sin cambios)
|
| 117 |
-
pass
|
| 118 |
-
|
| 119 |
-
# PESTA脩A 2: EVALUACI脫N POR FRASE
|
| 120 |
with gr.TabItem("Evaluaci贸n por Frase"):
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
gr.Markdown("### Assessment Summary")
|
| 131 |
-
with gr.Row():
|
| 132 |
-
score_out_sentence = gr.Number(label="Overall Score (0-100)", interactive=False)
|
| 133 |
-
level_out_sentence = gr.Textbox(label="Estimated CEFR Level", interactive=False)
|
| 134 |
-
holistic_feedback_out_sentence = gr.Markdown(label="Examiner's Feedback")
|
| 135 |
-
|
| 136 |
-
gr.Markdown("--- \n ### Detailed Word-by-Word Analysis")
|
| 137 |
-
|
| 138 |
-
# --- AJUSTE CLAVE: La salida ahora es un 煤nico componente Markdown ---
|
| 139 |
-
word_analysis_out_sentence = gr.Markdown(label="Phonetic Breakdown")
|
| 140 |
-
|
| 141 |
-
def update_text(choice): return gr.Textbox(value=choice)
|
| 142 |
-
tongue_twister_selector.change(fn=update_text, inputs=tongue_twister_selector, outputs=text_in_sentence)
|
| 143 |
-
submit_btn_sentence.click(fn=run_sentence_evaluation, inputs=[audio_in_sentence, text_in_sentence], outputs=[score_out_sentence, level_out_sentence, holistic_feedback_out_sentence, word_analysis_out_sentence])
|
| 144 |
|
| 145 |
if __name__ == "__main__":
|
| 146 |
if not api_key_found: print("\nFATAL: OpenAI API key not found.")
|
|
|
|
| 11 |
import pandas as pd
|
| 12 |
from gtts import gTTS
|
| 13 |
import re
|
| 14 |
+
import io # Necesario para el buffer en memoria
|
| 15 |
+
import base64 # Necesario para la codificaci贸n
|
| 16 |
|
| 17 |
+
# --- (El resto de tu configuraci贸n y prompts se mantienen igual) ---
|
| 18 |
+
# ...
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
# --- CAMBIO: La funci贸n de evaluaci贸n ahora incrusta el audio en Base64 ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
def run_sentence_evaluation(audio_input, reference_transcript):
|
| 22 |
if not api_key_found: raise gr.Error("OpenAI API key not found.")
|
| 23 |
if audio_input is None or not reference_transcript:
|
|
|
|
| 37 |
holistic_feedback_md = f"### Strengths\n{result['holistic_feedback']['strengths']}\n\n### Areas for Improvement\n{result['holistic_feedback']['areas_for_improvement']}"
|
| 38 |
word_analysis_list = result['word_by_word_analysis']
|
| 39 |
|
| 40 |
+
# --- NUEVA L脫GICA: Construir tabla en Markdown con audio Base64 ---
|
| 41 |
md_table = "| Reference Word | Spoken Word | Score | Feedback (EN) | Feedback (ES) | Reference Audio |\n"
|
| 42 |
md_table += "| :--- | :--- | :---: | :--- | :--- | :---: |\n"
|
| 43 |
|
|
|
|
|
|
|
| 44 |
for index, item in enumerate(word_analysis_list):
|
| 45 |
word_to_speak = item['reference_word']
|
|
|
|
|
|
|
| 46 |
|
| 47 |
try:
|
| 48 |
+
# 1. Generar audio en un buffer en memoria (no en un archivo)
|
| 49 |
+
tts = gTTS(text=word_to_speak, lang='en')
|
| 50 |
+
mp3_fp = io.BytesIO()
|
| 51 |
+
tts.write_to_fp(mp3_fp)
|
| 52 |
+
mp3_fp.seek(0)
|
| 53 |
+
|
| 54 |
+
# 2. Codificar el audio en Base64
|
| 55 |
+
audio_base64 = base64.b64encode(mp3_fp.read()).decode('utf-8')
|
| 56 |
+
|
| 57 |
+
# 3. Crear una etiqueta de audio con los datos incrustados (Data URI)
|
| 58 |
+
audio_player = f'<audio src="data:audio/mpeg;base64,{audio_base64}" controls></audio>'
|
| 59 |
+
|
| 60 |
except Exception as e:
|
| 61 |
print(f"Error al generar TTS para '{word_to_speak}': {e}"); audio_player = "Error"
|
| 62 |
|
|
|
|
| 68 |
return 0, "Error", error_msg, ""
|
| 69 |
|
| 70 |
|
| 71 |
+
# --- 3. INTERFAZ DE GRADIO CON PESTA脩AS (La definici贸n no cambia, pero ahora recibe Markdown) ---
|
| 72 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 73 |
+
# ... (Todo el resto de tu interfaz, incluyendo la Pesta帽a 1 y 2, se mantiene exactamente igual)
|
| 74 |
+
# ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
with gr.TabItem("Evaluaci贸n por Frase"):
|
| 76 |
+
# ... (Toda la definici贸n de la interfaz de esta pesta帽a se mantiene igual)
|
| 77 |
+
# ...
|
| 78 |
+
word_analysis_out_sentence = gr.Markdown(label="Phonetic Breakdown") # <-- Esta salida ya est谩 lista para recibir la tabla Markdown
|
| 79 |
+
# ...
|
| 80 |
+
submit_btn_sentence.click(
|
| 81 |
+
fn=run_sentence_evaluation,
|
| 82 |
+
inputs=[audio_in_sentence, text_in_sentence],
|
| 83 |
+
outputs=[score_out_sentence, level_out_sentence, holistic_feedback_out_sentence, word_analysis_out_sentence]
|
| 84 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
if __name__ == "__main__":
|
| 87 |
if not api_key_found: print("\nFATAL: OpenAI API key not found.")
|