Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -7,6 +7,7 @@ from dotenv import load_dotenv
|
|
| 7 |
import os
|
| 8 |
import speech_recognition as sr
|
| 9 |
from pydub import AudioSegment
|
|
|
|
| 10 |
|
| 11 |
load_dotenv()
|
| 12 |
|
|
@@ -22,39 +23,50 @@ recognizer = sr.Recognizer()
|
|
| 22 |
|
| 23 |
MAIN_SYSTEM_PROMPT = {
|
| 24 |
"role": "system",
|
| 25 |
-
"content": """You are Sam, an intelligent and
|
| 26 |
-
'response': Your main response,
|
| 27 |
-
'corrections':
|
| 28 |
-
'vocabulary':
|
| 29 |
-
'level_assessment': Current assessment
|
| 30 |
'encouragement': A motivating comment,
|
| 31 |
-
'context_memory': Important details about the user
|
| 32 |
-
|
| 33 |
-
1. Maintain natural conversation while gathering user information
|
| 34 |
-
2. Adapt language complexity to user's level
|
| 35 |
-
3. Provide gentle corrections without interrupting flow
|
| 36 |
-
4. Remember and use context from previous messages
|
| 37 |
-
5. Focus on user's interests and profession
|
| 38 |
-
6. Give specific vocabulary related to user's field
|
| 39 |
-
7. Keep responses encouraging and supportive
|
| 40 |
|
| 41 |
-
|
| 42 |
-
-
|
| 43 |
-
-
|
| 44 |
-
-
|
| 45 |
-
-
|
| 46 |
-
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
WELCOME_PROMPT = {
|
| 51 |
"role": "system",
|
| 52 |
-
"content": """Create a
|
| 53 |
-
1. Introduces you as Sam,
|
| 54 |
-
2. Asks for
|
|
|
|
| 55 |
|
| 56 |
Return in JSON format with key 'greeting'.
|
| 57 |
-
|
| 58 |
}
|
| 59 |
|
| 60 |
class EnglishTutor:
|
|
@@ -62,7 +74,7 @@ class EnglishTutor:
|
|
| 62 |
self.chat_history = [MAIN_SYSTEM_PROMPT]
|
| 63 |
self.user_info = {
|
| 64 |
"name": None,
|
| 65 |
-
"level":
|
| 66 |
"interests": [],
|
| 67 |
"country": None,
|
| 68 |
"profession": None,
|
|
@@ -77,7 +89,7 @@ class EnglishTutor:
|
|
| 77 |
json={
|
| 78 |
"model": "deepseek-chat",
|
| 79 |
"messages": [WELCOME_PROMPT],
|
| 80 |
-
"temperature": random.uniform(0.
|
| 81 |
"response_format": {"type": "json_object"}
|
| 82 |
}
|
| 83 |
)
|
|
@@ -85,11 +97,15 @@ class EnglishTutor:
|
|
| 85 |
return welcome_json["greeting"]
|
| 86 |
except Exception as e:
|
| 87 |
print(f"Error in welcome message: {str(e)}")
|
| 88 |
-
return "Hi! I'm Sam, your English tutor. What's your name?"
|
| 89 |
|
| 90 |
def get_bot_response(self, user_message):
|
| 91 |
try:
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
response = requests.post(
|
| 95 |
"https://api.deepseek.com/v1/chat/completions",
|
|
@@ -97,49 +113,66 @@ class EnglishTutor:
|
|
| 97 |
json={
|
| 98 |
"model": "deepseek-chat",
|
| 99 |
"messages": self.chat_history,
|
| 100 |
-
"temperature": random.uniform(0.
|
| 101 |
"response_format": {"type": "json_object"}
|
| 102 |
}
|
| 103 |
)
|
| 104 |
|
| 105 |
bot_response = json.loads(response.json()["choices"][0]["message"]["content"])
|
| 106 |
|
|
|
|
| 107 |
if "level_assessment" in bot_response:
|
| 108 |
self.user_info["level"] = bot_response["level_assessment"]
|
| 109 |
if "context_memory" in bot_response:
|
| 110 |
self._update_user_info(bot_response["context_memory"])
|
| 111 |
|
| 112 |
-
formatted_response = self._format_response(bot_response)
|
| 113 |
-
|
| 114 |
self.chat_history.append({"role": "assistant", "content": json.dumps(bot_response)})
|
| 115 |
|
| 116 |
-
return
|
| 117 |
except Exception as e:
|
| 118 |
print(f"Error getting bot response: {str(e)}")
|
| 119 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
def _update_user_info(self, context_memory):
|
| 122 |
-
if isinstance(context_memory,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
for key in self.user_info:
|
| 124 |
if key in context_memory:
|
| 125 |
self.user_info[key] = context_memory[key]
|
| 126 |
|
| 127 |
-
def
|
| 128 |
-
|
|
|
|
| 129 |
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
# Make encouragement more impressive
|
| 133 |
-
if "doing" in encouragement.lower() and "great" in encouragement.lower():
|
| 134 |
-
formatted += f"\n\nπ {encouragement}"
|
| 135 |
-
elif "wonderful" in encouragement.lower() or "excellent" in encouragement.lower():
|
| 136 |
-
formatted += f"\n\n⨠{encouragement}"
|
| 137 |
-
elif "good" in encouragement.lower() or "well" in encouragement.lower():
|
| 138 |
-
formatted += f"\n\nπ― {encouragement}"
|
| 139 |
-
else:
|
| 140 |
-
formatted += f"\n\nπ« {encouragement}"
|
| 141 |
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
def convert_audio_to_text(audio_path):
|
| 145 |
try:
|
|
@@ -178,14 +211,15 @@ tutor = EnglishTutor()
|
|
| 178 |
def initialize_chat():
|
| 179 |
try:
|
| 180 |
welcome = tutor.get_welcome_message()
|
| 181 |
-
|
|
|
|
| 182 |
history = [{"role": "assistant", "content": welcome}]
|
| 183 |
-
return history, welcome_audio, f"
|
| 184 |
except Exception as e:
|
| 185 |
print(f"Error initializing chat: {str(e)}")
|
| 186 |
-
welcome_msg = "Hi! I'm Sam, your English tutor. What's your name?"
|
| 187 |
history = [{"role": "assistant", "content": welcome_msg}]
|
| 188 |
-
return history, None, f"
|
| 189 |
|
| 190 |
def process_audio(audio, history, transcript, corrections):
|
| 191 |
try:
|
|
@@ -196,40 +230,50 @@ def process_audio(audio, history, transcript, corrections):
|
|
| 196 |
if not user_message:
|
| 197 |
return history, None, transcript, corrections
|
| 198 |
|
| 199 |
-
|
| 200 |
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
|
| 206 |
-
|
| 207 |
-
if
|
| 208 |
-
|
| 209 |
|
| 210 |
-
|
|
|
|
|
|
|
| 211 |
|
| 212 |
-
|
| 213 |
-
|
| 214 |
history = history or []
|
| 215 |
history.append({"role": "user", "content": user_message})
|
| 216 |
-
history.append({"role": "assistant", "content":
|
| 217 |
|
| 218 |
-
|
|
|
|
| 219 |
|
|
|
|
| 220 |
new_corrections = corrections
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
return history, audio_response, new_transcript, new_corrections
|
| 235 |
except Exception as e:
|
|
@@ -245,8 +289,8 @@ def clear_chat():
|
|
| 245 |
return initialize_chat()
|
| 246 |
|
| 247 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 248 |
-
gr.Markdown("# π English Learning Assistant")
|
| 249 |
-
gr.Markdown("π€ **Record your voice and click submit** -
|
| 250 |
|
| 251 |
with gr.Row():
|
| 252 |
with gr.Column(scale=3):
|
|
@@ -291,13 +335,13 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 291 |
max_lines=8,
|
| 292 |
show_label=False,
|
| 293 |
interactive=False,
|
| 294 |
-
placeholder="Grammar corrections
|
| 295 |
container=True
|
| 296 |
)
|
| 297 |
|
| 298 |
with gr.Row():
|
| 299 |
clear_btn = gr.Button("π Start New Conversation", variant="secondary", size="lg")
|
| 300 |
-
gr.Markdown("π‘ **Tip**:
|
| 301 |
|
| 302 |
submit_btn.click(
|
| 303 |
submit_recording,
|
|
|
|
| 7 |
import os
|
| 8 |
import speech_recognition as sr
|
| 9 |
from pydub import AudioSegment
|
| 10 |
+
import re
|
| 11 |
|
| 12 |
load_dotenv()
|
| 13 |
|
|
|
|
| 23 |
|
| 24 |
MAIN_SYSTEM_PROMPT = {
|
| 25 |
"role": "system",
|
| 26 |
+
"content": """You are Sam, an intelligent and proactive English tutor. You drive the conversation and actively engage students. Your responses must be in JSON format with these keys:
|
| 27 |
+
'response': Your main response (keep it conversational and engaging),
|
| 28 |
+
'corrections': Specific grammar or pronunciation corrections with examples,
|
| 29 |
+
'vocabulary': Alternative words/phrases with explanations,
|
| 30 |
+
'level_assessment': Current assessment (beginner/intermediate/advanced),
|
| 31 |
'encouragement': A motivating comment,
|
| 32 |
+
'context_memory': Important details about the user,
|
| 33 |
+
'next_question': A follow-up question to keep conversation flowing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
Your personality:
|
| 36 |
+
- Be the conversation driver - ask follow-up questions
|
| 37 |
+
- Show genuine interest in the student's life
|
| 38 |
+
- Provide corrections naturally without stopping the flow
|
| 39 |
+
- Use the student's name frequently
|
| 40 |
+
- Build on previous topics
|
| 41 |
+
- Be encouraging but provide constructive feedback
|
| 42 |
+
- Ask about their day, work, hobbies, culture, goals
|
| 43 |
+
|
| 44 |
+
Correction guidelines:
|
| 45 |
+
- Always provide corrections if there are grammar mistakes
|
| 46 |
+
- Suggest better vocabulary choices
|
| 47 |
+
- Give pronunciation tips when needed
|
| 48 |
+
- Use format: "Instead of 'X', try saying 'Y'"
|
| 49 |
+
|
| 50 |
+
Conversation flow:
|
| 51 |
+
- Start with personal questions (name, country, job, hobbies)
|
| 52 |
+
- Build conversations around their interests
|
| 53 |
+
- Use profession-specific vocabulary
|
| 54 |
+
- Ask about their culture and experiences
|
| 55 |
+
- Keep the conversation natural and flowing
|
| 56 |
+
- Always end with a question to continue the dialogue
|
| 57 |
+
|
| 58 |
+
Response length: Keep responses conversational (2-3 sentences max for response field)."""
|
| 59 |
}
|
| 60 |
|
| 61 |
WELCOME_PROMPT = {
|
| 62 |
"role": "system",
|
| 63 |
+
"content": """Create a warm welcome message that:
|
| 64 |
+
1. Introduces you as Sam, an enthusiastic English tutor
|
| 65 |
+
2. Asks for their name and where they're from
|
| 66 |
+
3. Shows excitement about helping them
|
| 67 |
|
| 68 |
Return in JSON format with key 'greeting'.
|
| 69 |
+
Make it warm, friendly and enthusiastic - maximum 2 sentences."""
|
| 70 |
}
|
| 71 |
|
| 72 |
class EnglishTutor:
|
|
|
|
| 74 |
self.chat_history = [MAIN_SYSTEM_PROMPT]
|
| 75 |
self.user_info = {
|
| 76 |
"name": None,
|
| 77 |
+
"level": "beginner",
|
| 78 |
"interests": [],
|
| 79 |
"country": None,
|
| 80 |
"profession": None,
|
|
|
|
| 89 |
json={
|
| 90 |
"model": "deepseek-chat",
|
| 91 |
"messages": [WELCOME_PROMPT],
|
| 92 |
+
"temperature": random.uniform(0.8, 1.0),
|
| 93 |
"response_format": {"type": "json_object"}
|
| 94 |
}
|
| 95 |
)
|
|
|
|
| 97 |
return welcome_json["greeting"]
|
| 98 |
except Exception as e:
|
| 99 |
print(f"Error in welcome message: {str(e)}")
|
| 100 |
+
return "Hi! I'm Sam, your English tutor. What's your name and where are you from?"
|
| 101 |
|
| 102 |
def get_bot_response(self, user_message):
|
| 103 |
try:
|
| 104 |
+
# Add user context to the message
|
| 105 |
+
context_info = f"User info: {self.user_info}"
|
| 106 |
+
enhanced_message = f"{user_message}\n\n[Context: {context_info}]"
|
| 107 |
+
|
| 108 |
+
self.chat_history.append({"role": "user", "content": enhanced_message})
|
| 109 |
|
| 110 |
response = requests.post(
|
| 111 |
"https://api.deepseek.com/v1/chat/completions",
|
|
|
|
| 113 |
json={
|
| 114 |
"model": "deepseek-chat",
|
| 115 |
"messages": self.chat_history,
|
| 116 |
+
"temperature": random.uniform(0.8, 1.0),
|
| 117 |
"response_format": {"type": "json_object"}
|
| 118 |
}
|
| 119 |
)
|
| 120 |
|
| 121 |
bot_response = json.loads(response.json()["choices"][0]["message"]["content"])
|
| 122 |
|
| 123 |
+
# Update user info
|
| 124 |
if "level_assessment" in bot_response:
|
| 125 |
self.user_info["level"] = bot_response["level_assessment"]
|
| 126 |
if "context_memory" in bot_response:
|
| 127 |
self._update_user_info(bot_response["context_memory"])
|
| 128 |
|
|
|
|
|
|
|
| 129 |
self.chat_history.append({"role": "assistant", "content": json.dumps(bot_response)})
|
| 130 |
|
| 131 |
+
return bot_response
|
| 132 |
except Exception as e:
|
| 133 |
print(f"Error getting bot response: {str(e)}")
|
| 134 |
+
return {
|
| 135 |
+
"response": "I apologize, but I couldn't process that properly. Could you try again?",
|
| 136 |
+
"corrections": "",
|
| 137 |
+
"vocabulary": "",
|
| 138 |
+
"level_assessment": "beginner",
|
| 139 |
+
"encouragement": "Don't worry, let's keep practicing!",
|
| 140 |
+
"context_memory": "",
|
| 141 |
+
"next_question": "What would you like to talk about?"
|
| 142 |
+
}
|
| 143 |
|
| 144 |
def _update_user_info(self, context_memory):
|
| 145 |
+
if isinstance(context_memory, str):
|
| 146 |
+
# Try to extract name if mentioned
|
| 147 |
+
if "name" in context_memory.lower():
|
| 148 |
+
name_match = re.search(r"name[:\s]+([A-Za-z]+)", context_memory)
|
| 149 |
+
if name_match:
|
| 150 |
+
self.user_info["name"] = name_match.group(1)
|
| 151 |
+
|
| 152 |
+
# Try to extract country if mentioned
|
| 153 |
+
if "country" in context_memory.lower() or "from" in context_memory.lower():
|
| 154 |
+
country_match = re.search(r"(?:from|country)[:\s]+([A-Za-z\s]+)", context_memory)
|
| 155 |
+
if country_match:
|
| 156 |
+
self.user_info["country"] = country_match.group(1).strip()
|
| 157 |
+
|
| 158 |
+
elif isinstance(context_memory, dict):
|
| 159 |
for key in self.user_info:
|
| 160 |
if key in context_memory:
|
| 161 |
self.user_info[key] = context_memory[key]
|
| 162 |
|
| 163 |
+
def clean_text_for_tts(self, text):
|
| 164 |
+
# Remove emojis and special characters that might cause TTS issues
|
| 165 |
+
text = re.sub(r'[π―πβ¨π«π€π€]', '', text)
|
| 166 |
|
| 167 |
+
# Remove extra spaces and newlines
|
| 168 |
+
text = re.sub(r'\s+', ' ', text).strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
+
# Remove duplicate words at the beginning
|
| 171 |
+
words = text.split()
|
| 172 |
+
if len(words) > 1 and words[0].lower() == words[1].lower():
|
| 173 |
+
text = ' '.join(words[1:])
|
| 174 |
+
|
| 175 |
+
return text
|
| 176 |
|
| 177 |
def convert_audio_to_text(audio_path):
|
| 178 |
try:
|
|
|
|
| 211 |
def initialize_chat():
|
| 212 |
try:
|
| 213 |
welcome = tutor.get_welcome_message()
|
| 214 |
+
clean_welcome = tutor.clean_text_for_tts(welcome)
|
| 215 |
+
welcome_audio = text_to_speech(clean_welcome)
|
| 216 |
history = [{"role": "assistant", "content": welcome}]
|
| 217 |
+
return history, welcome_audio, f"π€ Sam: {welcome}", ""
|
| 218 |
except Exception as e:
|
| 219 |
print(f"Error initializing chat: {str(e)}")
|
| 220 |
+
welcome_msg = "Hi! I'm Sam, your English tutor. What's your name and where are you from?"
|
| 221 |
history = [{"role": "assistant", "content": welcome_msg}]
|
| 222 |
+
return history, None, f"π€ Sam: {welcome_msg}", ""
|
| 223 |
|
| 224 |
def process_audio(audio, history, transcript, corrections):
|
| 225 |
try:
|
|
|
|
| 230 |
if not user_message:
|
| 231 |
return history, None, transcript, corrections
|
| 232 |
|
| 233 |
+
bot_response = tutor.get_bot_response(user_message)
|
| 234 |
|
| 235 |
+
# Create the main response with follow-up question
|
| 236 |
+
main_response = bot_response.get("response", "")
|
| 237 |
+
if bot_response.get("next_question"):
|
| 238 |
+
main_response += f" {bot_response['next_question']}"
|
| 239 |
|
| 240 |
+
# Add encouragement
|
| 241 |
+
if bot_response.get("encouragement"):
|
| 242 |
+
main_response += f" {bot_response['encouragement']}"
|
| 243 |
|
| 244 |
+
# Clean text for TTS
|
| 245 |
+
clean_response = tutor.clean_text_for_tts(main_response)
|
| 246 |
+
audio_response = text_to_speech(clean_response)
|
| 247 |
|
| 248 |
+
# Update chat history
|
|
|
|
| 249 |
history = history or []
|
| 250 |
history.append({"role": "user", "content": user_message})
|
| 251 |
+
history.append({"role": "assistant", "content": main_response})
|
| 252 |
|
| 253 |
+
# Update transcript
|
| 254 |
+
new_transcript = transcript + f"\n\nπ€ You: {user_message}\nπ€ Sam: {main_response}"
|
| 255 |
|
| 256 |
+
# Update corrections and vocabulary (THIS IS THE FIX!)
|
| 257 |
new_corrections = corrections
|
| 258 |
+
correction_parts = []
|
| 259 |
+
|
| 260 |
+
if bot_response.get("corrections") and bot_response["corrections"].strip():
|
| 261 |
+
correction_parts.append(f"βοΈ **Grammar Corrections:**\n{bot_response['corrections']}")
|
| 262 |
+
|
| 263 |
+
if bot_response.get("vocabulary") and bot_response["vocabulary"].strip():
|
| 264 |
+
vocab = bot_response['vocabulary']
|
| 265 |
+
if isinstance(vocab, dict):
|
| 266 |
+
vocab_text = "\n".join([f"β’ '{k}' β '{v}'" for k, v in vocab.items()])
|
| 267 |
+
else:
|
| 268 |
+
vocab_text = str(vocab)
|
| 269 |
+
correction_parts.append(f"π **Vocabulary Suggestions:**\n{vocab_text}")
|
| 270 |
+
|
| 271 |
+
if bot_response.get("level_assessment"):
|
| 272 |
+
correction_parts.append(f"π **Current Level:** {bot_response['level_assessment'].title()}")
|
| 273 |
+
|
| 274 |
+
if correction_parts:
|
| 275 |
+
new_correction_text = "\n\n".join(correction_parts)
|
| 276 |
+
new_corrections = new_corrections + f"\n\n--- Latest Feedback ---\n{new_correction_text}" if new_corrections else new_correction_text
|
| 277 |
|
| 278 |
return history, audio_response, new_transcript, new_corrections
|
| 279 |
except Exception as e:
|
|
|
|
| 289 |
return initialize_chat()
|
| 290 |
|
| 291 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 292 |
+
gr.Markdown("# π English Learning Assistant with Sam")
|
| 293 |
+
gr.Markdown("π€ **Record your voice and click submit** - Sam will actively guide your conversation and help improve your English!")
|
| 294 |
|
| 295 |
with gr.Row():
|
| 296 |
with gr.Column(scale=3):
|
|
|
|
| 335 |
max_lines=8,
|
| 336 |
show_label=False,
|
| 337 |
interactive=False,
|
| 338 |
+
placeholder="Grammar corrections, vocabulary suggestions, and level assessment will appear here...",
|
| 339 |
container=True
|
| 340 |
)
|
| 341 |
|
| 342 |
with gr.Row():
|
| 343 |
clear_btn = gr.Button("π Start New Conversation", variant="secondary", size="lg")
|
| 344 |
+
gr.Markdown("π‘ **Tip**: Sam will actively guide the conversation and provide personalized feedback!")
|
| 345 |
|
| 346 |
submit_btn.click(
|
| 347 |
submit_recording,
|