File size: 11,299 Bytes
ba42e04
 
 
c2bc11b
ba42e04
923ec86
ebe3eea
ba42e04
 
 
 
 
 
2288e0e
035c699
 
ba42e04
923ec86
b281201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38f1c03
2288e0e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b281201
ba42e04
 
 
923ec86
 
 
ba42e04
923ec86
b281201
 
923ec86
 
 
 
 
 
 
 
 
 
 
 
 
ba42e04
 
923ec86
ba42e04
923ec86
ba42e04
923ec86
ba42e04
 
 
 
 
 
 
923ec86
ba42e04
 
 
 
 
 
923ec86
ba42e04
923ec86
 
ba42e04
923ec86
ba42e04
 
923ec86
ba42e04
 
923ec86
 
ba42e04
 
5296449
 
 
 
 
 
 
 
ba42e04
5296449
 
ba42e04
5296449
 
 
 
 
 
ba42e04
 
 
c2bc11b
 
923ec86
5296449
 
c2bc11b
923ec86
 
5e17b89
923ec86
c2bc11b
 
 
 
 
 
03533c4
c2bc11b
03533c4
c2bc11b
 
923ec86
03533c4
c2bc11b
923ec86
c2bc11b
923ec86
 
 
5296449
 
 
 
 
 
c2bc11b
5296449
923ec86
 
 
 
c2bc11b
923ec86
b281201
 
 
 
 
 
5296449
c2bc11b
 
 
 
ba42e04
b281201
 
2e36457
 
ba42e04
923ec86
 
c2bc11b
 
923ec86
 
c2bc11b
923ec86
 
 
5e17b89
923ec86
b281201
 
ba42e04
5e17b89
 
c2e1b3d
c2bc11b
923ec86
b281201
2e36457
923ec86
ba42e04
5296449
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
import gradio as gr
import os
from huggingface_hub import InferenceClient, __version__ as hf_version
import random
from typing import Generator, Dict, List, Tuple, Optional
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logging.debug(f"Using huggingface_hub version: {hf_version}")

# Get token from environment variable
hf_token = os.environ.get("QWEN_BOT_TOKEN")
client = InferenceClient("Qwen/Qwen2.5-72B-Instruct", token=hf_token)

TOPIC_EXAMPLES = {
    "Daily Life": {
        "beginner": [
            "What time do you wake up?",
            "Do you go to school or work?",
            "What do you eat for breakfast?"
        ],
        "intermediate": [
            "What do you usually do after work?",
            "Do you like cooking? What’s your favorite dish?",
            "Tell me about your morning routine."
        ],
        "advanced": [
            "How do you balance personal and professional responsibilities?",
            "What does a productive day look like for you?",
            "How has your daily routine changed over the years?"
        ]
    },
    "Travel": {
        "beginner": [
            "Do you like to travel?",
            "Have you been to another city?",
            "Do you like airplanes?"
        ],
        "intermediate": [
            "Have you ever been to another country?",
            "What's your dream vacation?",
            "What do you like to pack in your suitcase?"
        ],
        "advanced": [
            "How has travel influenced your worldview?",
            "What do you think makes a destination culturally significant?",
            "Describe your most challenging travel experience."
        ]
    },
    "Food": {
        "beginner": [
            "Do you like apples?",
            "What is your favorite snack?",
            "Do you eat rice or noodles?"
        ],
        "intermediate": [
            "Can you describe how to cook your favorite dish?",
            "What is your go-to comfort food and why?",
            "Have you ever tried food from another country?"
        ],
        "advanced": [
            "How does food reflect a culture’s values and traditions?",
            "Compare the cuisines of two different countries.",
            "What’s the most unique food you’ve ever tasted?"
        ]
    },
    "Work & School": {
        "beginner": [
            "Do you go to school or work?",
            "What is your teacher’s name?",
            "Do you have homework?"
        ],
        "intermediate": [
            "What do you do at your job or school?",
            "What’s your favorite subject or task?",
            "Do you like working with other people?"
        ],
        "advanced": [
            "How do you stay motivated in your work or studies?",
            "What are the challenges of remote learning or working?",
            "How do education systems differ around the world?"
        ]
    },
    "Hobbies": {
        "beginner": [
            "Do you like music?",
            "What games do you play?",
            "Can you draw or paint?"
        ],
        "intermediate": [
            "What hobbies do you enjoy in your free time?",
            "When did you start your favorite hobby?",
            "Do you prefer indoor or outdoor hobbies?"
        ],
        "advanced": [
            "How can hobbies contribute to personal growth?",
            "What’s a hobby you would like to master and why?",
            "How has technology changed the way we pursue hobbies?"
        ]
    },
    "Shopping": {
        "beginner": [
            "Do you like shopping?",
            "What do you buy at the store?",
            "Do you go shopping alone?"
        ],
        "intermediate": [
            "Do you prefer shopping online or in stores?",
            "Tell me about your last shopping trip.",
            "What kinds of things do you usually buy?"
        ],
        "advanced": [
            "How has consumer behavior changed over time?",
            "What are the pros and cons of online shopping?",
            "Do advertisements affect your shopping decisions?"
        ]
    },
    "Weather": {
        "beginner": [
            "Is it sunny today?",
            "Do you like rain?",
            "What is your favorite season?"
        ],
        "intermediate": [
            "What’s the weather like where you are?",
            "Do you like hot or cold weather?",
            "How do you prepare for a rainy day?"
        ],
        "advanced": [
            "How does climate affect daily life in your region?",
            "What are the consequences of global climate change?",
            "How does weather influence culture and traditions?"
        ]
    }
}

MAX_HISTORY_LENGTH = 20
MEMORY_WINDOW = 5
MAX_TOKENS = 1024
TEMPERATURE = 0.7
TOP_P = 0.95

def get_examples_for_topic(topic, difficulty):
    return TOPIC_EXAMPLES.get(topic, {}).get(difficulty, [])

def get_conversation_prompt():
    return """You are JoJo, a friendly and supportive AI who helps people practice speaking English through fun, casual conversation.

Always keep your replies short (2–4 sentences) and speak naturally, like a friendly tutor or partner. Ask engaging, open-ended questions to keep the conversation going.

If the user makes a grammar or vocabulary mistake, gently correct them by repeating the corrected sentence, followed by a kind note like: \"You can also say it like this!\"

Do not teach grammar rules. Just offer corrections through example. Never lecture or explain unless asked.

Keep the tone warm, encouraging, and non-judgmental."""

def respond(message: str, chat_history: List[Tuple[str, str]], topic: Optional[str] = None, use_full_memory: bool = True) -> Tuple[str, List[Tuple[str, str]]]:
    if not message.strip():
        return "", chat_history

    try:
        api_messages = [{"role": "system", "content": get_conversation_prompt()}]
        logging.debug(f"System Message: {api_messages[0]}")

        if chat_history and use_full_memory:
            for user_msg, bot_msg in chat_history[-MEMORY_WINDOW:]:
                api_messages.extend([
                    {"role": "user", "content": str(user_msg)},
                    {"role": "assistant", "content": str(bot_msg)}
                ])

        api_messages.append({"role": "user", "content": str(message)})
        response = client.chat_completion(
            messages=api_messages,
            max_tokens=MAX_TOKENS,
            temperature=TEMPERATURE,
            top_p=TOP_P
        )

        bot_message = response.choices[0].message.content
        updated_history = list(chat_history)
        updated_history.append((message, bot_message))
        return "", updated_history

    except Exception as e:
        logging.error("Error in respond function", exc_info=True)
        error_msg = f"There was a temporary issue. Please try again. (Error: {str(e)})"
        return "", list(chat_history) + [(message, error_msg)]

def get_avatar_url():
    return "https://api.dicebear.com/7.x/bottts/svg?seed=rabbit&backgroundColor=b6e3f4"

custom_css = """
.compact-btn {
    padding: 0.75rem !important;
    font-size: 1rem !important;
    font-weight: 500;
    border-radius: 8px;
    background-color: #2f2f2f;
    color: white;
    transition: background-color 0.3s;
}
.compact-btn:hover {
    background-color: #444;
}
#voice-controls {
    margin-top: 1em;
    text-align: center;
    opacity: 0.5;
    font-size: 0.9rem;
    font-style: italic;
}
"""

with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
    gr.Markdown("""
    # 🐰 JoJo - Your Speaking Buddy
    **Chat in English with JoJo — your kind and cheerful language partner.**  
    Pick a topic, choose your level, and practice naturally. JoJo will guide you, ask questions, and gently correct you along the way!
    """)

    avatar = get_avatar_url()
    memory_flag = gr.State(value=True)

    with gr.Row():
        with gr.Column(scale=3):
            chatbot = gr.Chatbot(
                height=400,
                bubble_full_width=True,
                show_copy_button=True,
                avatar_images=[None, avatar],
                scale=1,
                min_width=800
            )
            msg = gr.Textbox(
                placeholder="Say something to JoJo...",
                scale=4
            )

            with gr.Row():
                submit = gr.Button("Send", variant="primary")
                clear = gr.Button("New Chat")

            gr.Markdown("""
            <div id="voice-controls">
            🎤 Voice input and 🔈 playback coming soon!
            </div>
            """)

        with gr.Column(scale=1):
            gr.Markdown("""### 🎯 Conversation Settings""")
            topic = gr.Dropdown(
                choices=list(TOPIC_EXAMPLES.keys()),
                label="Select Topic",
                value="Daily Life"
            )

            difficulty = gr.Dropdown(
                choices=["beginner", "intermediate", "advanced"],
                label="Select Difficulty",
                value="intermediate"
            )

            gr.Markdown("""### 💬 Quick Starters""")
            starter_btn1 = gr.Button("Starter 1", scale=1, min_width=250, elem_classes="compact-btn")
            starter_btn2 = gr.Button("Starter 2", scale=1, min_width=250, elem_classes="compact-btn")
            starter_btn3 = gr.Button("Starter 3", scale=1, min_width=250, elem_classes="compact-btn")
            starter_buttons = [starter_btn1, starter_btn2, starter_btn3]

            def update_starters(selected_topic, selected_difficulty):
                examples = get_examples_for_topic(selected_topic, selected_difficulty)
                results = [examples[i] if i < len(examples) else "" for i in range(3)]
                return tuple(results)

            def use_starter(text: str, history: List[Tuple[str, str]], selected_topic: str, memory_flag: bool) -> Tuple[str, List[Tuple[str, str]]]:
                if not text:
                    return "", history
                try:
                    _, updated = respond(text, history, selected_topic, memory_flag)
                    return "", updated
                except Exception as e:
                    return "", history + [(text, f"Error: {str(e)}")]

            for btn in starter_buttons:
                btn.click(fn=use_starter, inputs=[btn, chatbot, topic, memory_flag], outputs=[msg, chatbot], queue=True)

            topic.change(fn=update_starters, inputs=[topic, difficulty], outputs=starter_buttons)
            difficulty.change(fn=update_starters, inputs=[topic, difficulty], outputs=starter_buttons)

    msg.submit(fn=respond, inputs=[msg, chatbot, topic, memory_flag], outputs=[msg, chatbot])
    submit.click(fn=respond, inputs=[msg, chatbot, topic, memory_flag], outputs=[msg, chatbot])
    clear.click(lambda: [], None, chatbot, queue=False)
    clear.click(lambda: "", None, msg, queue=False)

    default_starters = get_examples_for_topic("Daily Life", "intermediate")
    demo.load(fn=lambda: tuple(default_starters[:3]), outputs=starter_buttons, queue=False)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)