import gradio as gr import openai import json import os from datetime import datetime # Default configurations SUPPORTED_LANGUAGES = { "English": "en", "Spanish": "es", "French": "fr", "German": "de", "Chinese": "zh", "Japanese": "ja", "Italian": "it", "Portuguese": "pt", "Russian": "ru", "Arabic": "ar" } PROFICIENCY_LEVELS = ["Beginner", "Intermediate", "Advanced"] # Tracks conversation history and user stats class LearningSession: def __init__(self): self.conversation_history = [] self.vocabulary = set() self.session_start = datetime.now() self.api_key = None self.language = "Spanish" self.language_code = "es" self.proficiency = "Beginner" self.thinking_mode = False def add_message(self, role, content): self.conversation_history.append({"role": role, "content": content}) if role == "assistant": # Extract vocabulary from assistant responses words = content.lower().replace('.', ' ').replace(',', ' ').replace('!', ' ').replace('?', ' ').split() self.vocabulary.update(words) def get_messages(self): # Format for display formatted = [] for msg in self.conversation_history: if msg["role"] == "system": continue speaker = "👤 You:" if msg["role"] == "user" else f"🤖 {self.language} Tutor:" formatted.append(f"{speaker} {msg['content']}") return "\n\n".join(formatted) def get_openai_messages(self): # Add system message for context system_prompt = self._generate_system_prompt() messages = [{"role": "system", "content": system_prompt}] # Add conversation history (limit to last 10 messages to save tokens) messages.extend(self.conversation_history[-10:]) return messages def _generate_system_prompt(self): if self.thinking_mode: mode_instruction = ( "Use a thinking/reasoning mode where you carefully analyze the user's language, " "provide corrections, and explain grammar concepts in detail." ) else: mode_instruction = ( "Maintain a natural conversational flow. Only correct critical errors that " "would impede understanding, and do so gently within the conversation." ) language_level_map = { "Beginner": "Use simple vocabulary and short sentences. Frequently introduce basic vocabulary and simple grammar constructions.", "Intermediate": "Use moderate vocabulary and varied sentence structures. Introduce idioms occasionally and more complex grammar patterns.", "Advanced": "Use rich vocabulary, complex sentences, idioms, and cultural references. Challenge the learner with sophisticated language constructs." } level_instruction = language_level_map[self.proficiency] return f"""You are a friendly and patient {self.language} language tutor. IMPORTANT: You must respond ONLY in {self.language} except when explaining grammar concepts in thinking mode. {mode_instruction} {level_instruction} Keep conversations engaging, diverse, and natural. Ask questions about the learner's interests, daily life, or opinions. Occasionally introduce culturally relevant topics about countries where {self.language} is spoken. Remember that you are helping someone learn {self.language}, so maintain an encouraging tone. """ # Initialize session session = LearningSession() def set_api_key(api_key): """Validate and set the OpenRouter API key.""" if not api_key.strip(): return "❌ Please enter your OpenRouter API key" # Store API key in session session.api_key = api_key # Simple validation (just checking if it looks like a valid key format) if len(api_key) < 20: return "❌ API key looks too short. Please check it and try again." return "✅ API key set! You can now start your language learning session." def update_settings(language, proficiency, thinking_mode): """Update session settings.""" session.language = language session.language_code = SUPPORTED_LANGUAGES[language] session.proficiency = proficiency session.thinking_mode = thinking_mode return f"Settings updated: Learning {language} at {proficiency} level. Thinking mode: {'On' if thinking_mode else 'Off'}" def get_ai_response(user_message): """Get response from Qwen3 0.6B via OpenRouter API.""" if not session.api_key: return "Please set your OpenRouter API key first." if not user_message.strip(): return "Please enter a message." try: # Add user message to history session.add_message("user", user_message) # Create OpenAI client with OpenRouter base URL client = openai.OpenAI( base_url="https://openrouter.ai/api/v1", api_key=session.api_key ) # Get conversation history formatted for API messages = session.get_openai_messages() # Make API request completion = client.chat.completions.create( extra_headers={ "HTTP-Referer": "Language Learning Companion App", "X-Title": "Language Learning App with Qwen3" }, model="qwen/qwen3-0.6b-04-28:free", messages=messages ) # Extract response ai_response = completion.choices[0].message.content # Add to history session.add_message("assistant", ai_response) # Return full conversation history return session.get_messages() except Exception as e: return f"Error: {str(e)}" def reset_conversation(): """Reset the conversation history.""" session.conversation_history = [] return "Conversation has been reset." def get_vocabulary_list(): """Get the current vocabulary list.""" if not session.vocabulary: return "No vocabulary collected yet." vocab_list = sorted(list(session.vocabulary)) return ", ".join(vocab_list) def get_session_stats(): """Get statistics about the current session.""" duration = datetime.now() - session.session_start hours, remainder = divmod(duration.seconds, 3600) minutes, seconds = divmod(remainder, 60) stats = { "Language": session.language, "Proficiency Level": session.proficiency, "Session Duration": f"{hours}h {minutes}m {seconds}s", "Messages Exchanged": len(session.conversation_history), "Vocabulary Words": len(session.vocabulary) } return json.dumps(stats, indent=2) # Create Gradio interface with gr.Blocks(title="Language Learning Companion") as app: gr.Markdown("# 🌍 Language Learning Companion") gr.Markdown("Learn languages through natural conversation with AI powered by Qwen3 0.6B") with gr.Accordion("Setup", open=True): api_key_input = gr.Textbox( label="OpenRouter API Key", placeholder="Enter your OpenRouter API key...", type="password" ) api_submit = gr.Button("Set API Key") api_status = gr.Textbox(label="API Status", interactive=False) api_submit.click(set_api_key, inputs=api_key_input, outputs=api_status) with gr.Accordion("Learning Settings", open=True): with gr.Row(): language_dropdown = gr.Dropdown( choices=list(SUPPORTED_LANGUAGES.keys()), value="Spanish", label="Language to Learn" ) proficiency_dropdown = gr.Dropdown( choices=PROFICIENCY_LEVELS, value="Beginner", label="Proficiency Level" ) thinking_checkbox = gr.Checkbox( label="Enable Thinking Mode (Grammar Explanations)", value=False ) settings_submit = gr.Button("Update Settings") settings_status = gr.Textbox(label="Settings Status", interactive=False) settings_submit.click( update_settings, inputs=[language_dropdown, proficiency_dropdown, thinking_checkbox], outputs=settings_status ) with gr.Row(): with gr.Column(scale=2): chat_output = gr.Textbox( label="Conversation", placeholder="Your conversation will appear here...", lines=15, interactive=False ) user_input = gr.Textbox( label="Your message", placeholder=f"Type your message in any language...", lines=2 ) with gr.Row(): submit_btn = gr.Button("Send", variant="primary") reset_btn = gr.Button("Reset Conversation") with gr.Column(scale=1): with gr.Accordion("Vocabulary", open=True): vocab_output = gr.Textbox( label="Words Encountered", lines=10, interactive=False ) vocab_btn = gr.Button("Show Vocabulary") with gr.Accordion("Session Stats", open=True): stats_output = gr.JSON(label="Learning Statistics") stats_btn = gr.Button("Update Stats") # Set up button actions submit_btn.click(get_ai_response, inputs=user_input, outputs=chat_output) reset_btn.click(reset_conversation, outputs=chat_output) vocab_btn.click(get_vocabulary_list, outputs=vocab_output) stats_btn.click(get_session_stats, outputs=stats_output) # Allow pressing Enter to submit user_input.submit(get_ai_response, inputs=user_input, outputs=chat_output) # Launch the app if __name__ == "__main__": app.launch()