|
import gradio as gr |
|
import openai |
|
import json |
|
import os |
|
from datetime import datetime |
|
|
|
|
|
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"] |
|
|
|
|
|
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": |
|
|
|
words = content.lower().replace('.', ' ').replace(',', ' ').replace('!', ' ').replace('?', ' ').split() |
|
self.vocabulary.update(words) |
|
|
|
def get_messages(self): |
|
|
|
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): |
|
|
|
system_prompt = self._generate_system_prompt() |
|
messages = [{"role": "system", "content": system_prompt}] |
|
|
|
|
|
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. |
|
""" |
|
|
|
|
|
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" |
|
|
|
|
|
session.api_key = api_key |
|
|
|
|
|
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: |
|
|
|
session.add_message("user", user_message) |
|
|
|
|
|
client = openai.OpenAI( |
|
base_url="https://openrouter.ai/api/v1", |
|
api_key=session.api_key |
|
) |
|
|
|
|
|
messages = session.get_openai_messages() |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
ai_response = completion.choices[0].message.content |
|
|
|
|
|
session.add_message("assistant", ai_response) |
|
|
|
|
|
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) |
|
|
|
|
|
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") |
|
|
|
|
|
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) |
|
|
|
|
|
user_input.submit(get_ai_response, inputs=user_input, outputs=chat_output) |
|
|
|
|
|
if __name__ == "__main__": |
|
app.launch() |