Babellama / app.py
jeremierostan's picture
Update app.py
173f083 verified
import gradio as gr
import requests
import numpy as np
import soundfile as sf
import io
import tempfile
import os
from groq import Groq
from crdt import warn
# Language configuration
LANGUAGE_CONFIG = {
"English": {
"voice_id": "vits-eng-1",
"whisper_lang": "en",
"system_prompt": "You are a language tutor. Your goal is to help students practice language skills in English by engaging in conversations with them. As part of your responses, give them feedback on their English proficiency if needed. If they make a clear mistake (vocabulary or grammar, not spelling), indicate it gently and explain how to correct them. Please respond in English. Keep your answers very concise. Note that you are used in a school setting and should refuse to answer or produce any content that is violent, sexual, discriminatory, or inappropriate for children under 13."
},
"French": {
"voice_id": "vits-fra-1",
"whisper_lang": "fr",
"system_prompt": "You are a language tutor. Your goal is to help students practice language skills in French by engaging in conversations with them. As part of your responses, give them feedback on their French proficiency if needed. If they make a clear mistake (vocabulary or grammar, not spelling), indicate it gently and explain how to correct them. Please respond in French. Keep your answers very concise. Note that you are used in a school setting and should refuse to answer or produce any content that is violent, sexual, discriminatory, or inappropriate for children under 13."
},
"Spanish": {
"voice_id": "vits-spa-1",
"whisper_lang": "es",
"system_prompt": "You are a language tutor. Your goal is to help students practice language skills in Spanish by engaging in conversations with them. As part of your responses, give them feedback on their Spanish proficiency if needed. If they make a clear mistake (vocabulary or grammar, not spelling), indicate it gently and explain how to correct them. Please respond in Spanish. Keep your answers very concise. Note that you are used in a school setting and should refuse to answer or produce any content that is violent, sexual, discriminatory, or inappropriate for children under 13."
}
}
def generate_audio(text: str, neets_api_key: str, language: str) -> tuple[int, np.ndarray]:
"""Generate audio from text using Neets API"""
print(f"Generating audio for text in {language}:", text)
try:
# Make request with simplified params
response = requests.post(
url="https://api.neets.ai/v1/tts",
headers={
"Content-Type": "application/json",
"X-API-Key": neets_api_key
},
json={
"text": text,
"voice_id": LANGUAGE_CONFIG[language]["voice_id"],
"params": {
"model": "vits"
}
}
)
print(f"TTS Response status: {response.status_code}")
if response.status_code != 200:
print(f"TTS Response content: {response.content.decode()}")
raise ValueError(f"TTS API returned status code {response.status_code}")
# Save the audio to a temporary file
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as temp_file:
temp_file.write(response.content)
temp_path = temp_file.name
# Read the audio file
audio_data, sample_rate = sf.read(temp_path)
# Convert to mono if stereo
if len(audio_data.shape) > 1:
audio_data = np.mean(audio_data, axis=1)
# Convert to float32 if needed
if audio_data.dtype != np.float32:
audio_data = audio_data.astype(np.float32)
# Clean up the temporary file
os.unlink(temp_path)
return sample_rate, audio_data
except Exception as e:
print(f"Audio generation error: {str(e)}")
if hasattr(response, 'content'):
print(f"Response content: {response.content[:100]}") # Print first 100 bytes
raise
def transcribe_audio(audio_path: str, groq_api_key: str, language: str) -> str:
"""Transcribe audio using Groq's Whisper API"""
client = Groq(api_key=groq_api_key)
try:
with open(audio_path, "rb") as audio_file:
transcription = client.audio.transcriptions.create(
file=(audio_path, audio_file.read()),
model="whisper-large-v3-turbo",
response_format="json",
language=LANGUAGE_CONFIG[language]["whisper_lang"],
temperature=0.0
)
return transcription.text
except Exception as e:
print(f"Transcription error: {str(e)}")
raise
def chat_with_groq(messages: list, groq_api_key: str, language: str) -> str:
"""Send chat request to Groq API"""
client = Groq(api_key=groq_api_key)
try:
response = client.chat.completions.create(
model="llama-3.1-70b-versatile",
messages=[
{"role": "system", "content": LANGUAGE_CONFIG[language]["system_prompt"]},
*messages
],
temperature=0.7
)
return response.choices[0].message.content
except Exception as e:
print(f"Groq chat error: {str(e)}")
raise
def process_voice_message(audio, history, neets_api_key: str, groq_api_key: str,
english: bool, french: bool, spanish: bool):
"""Process recorded voice message"""
if not all([neets_api_key, groq_api_key]):
return "", [{"role": "error", "content": "Please provide all API keys."}], None
# Check language selection
selected_languages = []
if english: selected_languages.append("English")
if french: selected_languages.append("French")
if spanish: selected_languages.append("Spanish")
if not selected_languages:
return "", [{"role": "error", "content": "Please select at least one language."}], None
if len(selected_languages) > 1:
return "", [{"role": "error", "content": "Please select only one language."}], None
selected_language = selected_languages[0]
print(f"Selected language: {selected_language}")
try:
# Save the recorded audio to a temporary file
if isinstance(audio, tuple):
sr, data = audio
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
sf.write(temp_file.name, data, sr)
temp_path = temp_file.name
else:
temp_path = audio
# Transcribe the audio using Groq
transcribed_text = transcribe_audio(temp_path, groq_api_key, selected_language)
print(f"Transcribed text: {transcribed_text}")
# Clean up temporary file if we created one
if isinstance(audio, tuple):
os.unlink(temp_path)
# Prepare messages
messages = []
for msg in (history or []):
if isinstance(msg, dict):
messages.append({
"role": msg["role"],
"content": msg["content"]
})
# Add transcribed message
messages.append({
"role": "user",
"content": transcribed_text
})
# Get chat completion from Groq
bot_response = chat_with_groq(messages, groq_api_key, selected_language)
print(f"Bot response: {bot_response}")
# Generate audio for the response
try:
sample_rate, audio_data = generate_audio(bot_response, neets_api_key, selected_language)
audio_output = (sample_rate, audio_data)
except Exception as audio_error:
print(f"Audio generation error: {audio_error}")
audio_output = None
# Update history with new messages
new_history = messages + [
{"role": "assistant", "content": bot_response}
]
return transcribed_text, new_history, audio_output
except Exception as e:
print(f"Processing error: {str(e)}")
return "", [{"role": "error", "content": f"Error: {str(e)}"}], None
# Create Gradio interface
with gr.Blocks() as demo:
with gr.Row():
# Logo
logo_url = "https://i.postimg.cc/cHmNBmg1/ED-COACH-1.jpg"
gr.Image(logo_url, label="", show_label=False, height=200)
gr.Markdown("# BABELlama Multilingual Tutor")
with gr.Row():
neets_api_key = gr.Textbox(
label="Neets API Key",
type="password",
placeholder="Enter your Neets API key here"
)
groq_api_key = gr.Textbox(
label="Groq API Key",
type="password",
placeholder="Enter your Groq API key here"
)
with gr.Row():
gr.Markdown("### Select Language")
english_checkbox = gr.Checkbox(label="English", value=True)
french_checkbox = gr.Checkbox(label="French")
spanish_checkbox = gr.Checkbox(label="Spanish")
chatbot = gr.Chatbot(
type="messages",
show_copy_button=True,
layout="bubble"
)
with gr.Row():
# Audio recorder for voice input
audio_input = gr.Audio(
label="Record Message",
sources=["microphone"],
type="numpy"
)
# Display transcribed text
transcribed_msg = gr.Textbox(
label="Transcribed Message",
placeholder="Your message will appear here after recording...",
interactive=False
)
# Audio output for bot response
audio_output = gr.Audio(
label="Bot Voice",
autoplay=True,
type="numpy"
)
# Add clear button
clear = gr.Button("Clear Conversation")
# Handle audio submission
audio_input.stop_recording(
process_voice_message,
inputs=[
audio_input, chatbot, neets_api_key, groq_api_key,
english_checkbox, french_checkbox, spanish_checkbox
],
outputs=[transcribed_msg, chatbot, audio_output]
)
# Make checkboxes mutually exclusive
english_checkbox.change(
lambda x: (False, False) if x else (False, False),
inputs=english_checkbox,
outputs=[french_checkbox, spanish_checkbox]
)
french_checkbox.change(
lambda x: (False, False) if x else (False, False),
inputs=french_checkbox,
outputs=[english_checkbox, spanish_checkbox]
)
spanish_checkbox.change(
lambda x: (False, False) if x else (False, False),
inputs=spanish_checkbox,
outputs=[english_checkbox, french_checkbox]
)
# Handle clear button
clear.click(
lambda: ("", [], None),
outputs=[transcribed_msg, chatbot, audio_output]
)
gr.Markdown(warn)
# "Powered by Groq" badge
with gr.Row():
groq_url = "https://i.ibb.co/FxPsxKF/PBG-mark1-color.png"
gr.Image(groq_url, label="", show_label=False, height=50)
if __name__ == "__main__":
demo.launch()