File size: 15,994 Bytes
3ead6ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
import os
import sys

# Add src directory to Python path for Hugging Face Spaces compatibility
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
SRC_DIR = os.path.join(PROJECT_ROOT, "src")
sys.path.insert(0, SRC_DIR)

import json

import gradio as gr
from numpy import add

from learnbee.llm_call import LLMCall
from learnbee.mcp_server import get_lesson_content, get_lesson_list


LESSON_CONTENT_MAX_LENGTH = 50000

# Available tutor names for early childhood education
TUTOR_NAMES = ["Professor Owl", "Star Explorer", "Logic Bot", "Nature Guide", "Story Friend"]


def load_lesson_content(lesson_name, selected_tutor, progress=gr.Progress()):
    """Load lesson content and extract key concepts. Returns introduction message for chatbot."""
    if not lesson_name:
        return "", "", "Please select a lesson first.", []
    
    # Get current chatbot state (empty list if none)
    chatbot_messages = []

    progress(0.1, desc="Loading lesson content...")

    lesson_content = get_lesson_content(lesson_name, LESSON_CONTENT_MAX_LENGTH)

    progress(0.5, desc="Extracting key concepts from the lesson...")

    # Extract key concepts using LLM
    try:
        call_llm = LLMCall()
        concepts = call_llm.extract_key_concepts(lesson_content)

        progress(0.7, desc="Generating lesson introduction...")

        # Generate lesson introduction with summary and example questions
        introduction = ""
        if concepts:
            try:
                introduction = call_llm.generate_lesson_introduction(
                    lesson_content, lesson_name, concepts
                )
            except Exception as e:
                print(f"Error generating introduction: {str(e)}")
                # Continue without introduction if it fails

        progress(1.0, desc="Complete!")

        if concepts:
            concepts_display = ', '.join(concepts[:5])
            if len(concepts) > 5:
                concepts_display += f" and {len(concepts) - 5} more"
            
            # Build simple status message (just confirmation)
            status_message = (
                f"βœ… Successfully loaded '{lesson_name}'!\n\n"
                f"πŸ“š Found {len(concepts)} key concepts: {concepts_display}\n\n"
                f"πŸŽ“ Your tutor is ready! Check the chat for a welcome message."
            )
            
            # Prepare chatbot message with introduction
            if introduction:
                # Format the introduction as a friendly greeting from the tutor
                tutor_greeting = (
                    f"Hello! πŸ‘‹ I'm {selected_tutor}, and I'm so excited to learn with you today!\n\n"
                    f"{introduction}\n\n"
                    f"Let's start our learning adventure! What would you like to explore first? 🌟"
                )
                chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
            else:
                # Fallback greeting if introduction generation fails
                tutor_greeting = (
                    f"Hello! πŸ‘‹ I'm {selected_tutor}, and I'm excited to learn with you today!\n\n"
                    f"We're going to explore: {concepts_display}\n\n"
                    f"What would you like to learn about first? 🌟"
                )
                chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
            
            return (
                lesson_name,
                lesson_content,
                status_message,
                chatbot_messages,
            )
        else:
            status_message = (
                f"⚠️ Loaded '{lesson_name}' but no key concepts were automatically detected.\n"
                f"You can still chat with your tutor about the lesson content!"
            )
            tutor_greeting = (
                f"Hello! πŸ‘‹ I'm {selected_tutor}, and I'm ready to learn with you!\n\n"
                f"Let's explore the lesson '{lesson_name}' together. What would you like to know? 🌟"
            )
            chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
            return (
                lesson_name,
                lesson_content,
                status_message,
                chatbot_messages,
            )
    except Exception as e:
        status_message = (
            f"❌ Error extracting concepts: {str(e)}\n\n"
            f"You can still try chatting about the lesson content."
        )
        tutor_greeting = (
            f"Hello! πŸ‘‹ I'm {selected_tutor}, and I'm here to help you learn!\n\n"
            f"Let's explore together. What would you like to know? 🌟"
        )
        chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
        return (
            lesson_name,
            lesson_content,
            status_message,
            chatbot_messages,
        )


def custom_respond(
    message, history, lesson_name, lesson_content, selected_tutor, difficulty_level
):
    """Custom respond function with educational system prompt."""
    if not lesson_name or not selected_tutor:
        yield "Please select a lesson and tutor first."
        return

    if not lesson_content:
        lesson_content = get_lesson_content(lesson_name, LESSON_CONTENT_MAX_LENGTH)

    # Generate educational system prompt with enhanced pedagogy
    # fmt: off
    system_prompt = (
        f"You are {selected_tutor}, a friendly and patient Educational Tutor specializing in early childhood education (ages 3-6).\n\n"
        
        "CORE PEDAGOGICAL PRINCIPLES:\n"
        "1. Socratic Method: Guide through questions, not answers. Help children discover knowledge themselves.\n"
        "2. Scaffolding: Break complex ideas into smaller, manageable steps. Build understanding gradually.\n"
        "3. Positive Reinforcement: Celebrate attempts, not just correct answers. Use encouraging phrases like 'Great thinking!' or 'You're on the right track!'\n"
        "4. Active Learning: Encourage hands-on thinking, examples from their world, and personal connections.\n"
        "5. Repetition with Variation: Reinforce concepts through different examples and contexts.\n\n"
        
        "COMMUNICATION GUIDELINES:\n"
        "- Use very simple, age-appropriate language (3-6 year olds).\n"
        "- Keep sentences short (5-10 words maximum).\n"
        "- Use concrete examples from children's daily lives (toys, family, pets, food, nature).\n"
        "- Incorporate playful elements: emojis, simple analogies, and fun comparisons.\n"
        "- Be warm, enthusiastic, and patient. Show excitement about learning!\n"
        "- Use the child's name when possible (refer to them as 'you' or 'little learner').\n\n"
        
        "TEACHING STRATEGIES BY DIFFICULTY LEVEL:\n"
        f"- {difficulty_level.upper()} level:\n"
        + ("  * Beginner: Use very simple words, lots of examples, visual descriptions, and yes/no questions.\n"
           if difficulty_level == "beginner" else
           "  * Intermediate: Introduce slightly more complex concepts, encourage longer explanations, use 'why' and 'how' questions.\n"
           if difficulty_level == "intermediate" else
           "  * Advanced: Challenge with problem-solving, encourage predictions, explore connections between concepts.\n") +
        "\n"
        
        "INTERACTION PATTERNS:\n"
        "- When a child asks a question: Respond with a guiding question first, then offer a hint if needed.\n"
        "- When a child gives an answer: Validate their thinking, then ask a follow-up to deepen understanding.\n"
        "- When a child seems confused: Break it down into smaller pieces, use a different example, or try a simpler approach.\n"
        "- When a child shows excitement: Match their energy and build on their interest.\n"
        "- When off-topic: Acknowledge their interest, then gently connect it back: 'That's interesting! Now, let's think about how that relates to our lesson...'\n\n"
        
        "SAFETY AND BOUNDARIES:\n"
        "- Only discuss topics appropriate for ages 3-12.\n"
        "- If asked about inappropriate topics, gently redirect: 'Let's focus on our fun lesson instead!'\n"
        "- Keep all content educational and positive.\n"
        "- Never provide medical, legal, or safety advice beyond basic age-appropriate concepts.\n\n"
        
        "LESSON CONTEXT:\n"
        "====================\n"
        f"{lesson_content}\n"
        "====================\n\n"
        
        "YOUR ROLE:\n"
        "You are teaching this lesson content to a young child. Make it engaging, interactive, and fun. "
        "Remember: the goal is not just to convey information, but to spark curiosity and build confidence in learning.\n\n"
        
        "LANGUAGE INSTRUCTION:\n"
        "IMPORTANT: Always respond in the EXACT same language that the child uses in their messages. "
        "If the child writes in Spanish, respond in Spanish. If they write in English, respond in English. "
        "If they write in French, respond in French. Match the child's language automatically. "
        "This is critical for effective communication with young learners.\n"
    )
    # fmt: on

    # Call the respond method with educational system prompt
    call_llm = LLMCall()
    for response in call_llm.respond(
        message, 
        history, 
        system_prompt=system_prompt,
        tutor_name=selected_tutor,
        difficulty_level=difficulty_level
    ):
        yield response


def gradio_ui():
    lesson_name = gr.BrowserState("")
    selected_tutor = gr.BrowserState(TUTOR_NAMES[0] if TUTOR_NAMES else "")

    lesson_choices = json.loads(get_lesson_list())

    with gr.Blocks() as demo:

        with gr.Tab("Chat"):
            # Title
            with gr.Row():
                gr.Markdown("# Learnbee-mcp - Educational Tutor")

            # Status
            with gr.Row():
                status_markdown = gr.Markdown(label="Status")
                status_markdown.value = (
                    # fmt: off
                    "πŸ‘‹ Welcome to Learnbee-mcp! πŸŽ“<br><br>"
                    "πŸ“– <strong>Getting Started:</strong><br>"
                    "1. Select a lesson from the dropdown<br>"
                    "2. Choose your favorite tutor<br>"
                    "3. Pick a difficulty level<br>"
                    "4. Click 'Load Lesson & Prepare Tutor'<br>"
                    "5. Start learning and chatting! πŸ’¬<br><br>"
                    "✨ This educational system is designed for early childhood education (ages 3-6)."
                    # fmt: on
                )

            # Hidden textbox for lesson content
            lesson_content = gr.Textbox(visible=False)

            with gr.Row():

                with gr.Column(scale=1):
                    # Lesson selection
                    with gr.Row():
                        lesson_dropdown = gr.Dropdown(
                            label="πŸ“š Select a Lesson",
                            choices=lesson_choices,
                            interactive=True,
                        )

                    # Tutor selection
                    with gr.Row():
                        tutor_dropdown = gr.Dropdown(
                            label="🦸 Select a Tutor",
                            choices=TUTOR_NAMES,
                            value=TUTOR_NAMES[0] if TUTOR_NAMES else None,
                            interactive=True,
                        )

                    # Difficulty level selection
                    with gr.Row():
                        difficulty_dropdown = gr.Dropdown(
                            label="πŸ“Š Difficulty Level",
                            choices=["beginner", "intermediate", "advanced"],
                            value="beginner",
                            interactive=True,
                        )

                    with gr.Row():
                        load_button = gr.Button(
                            "Load Lesson & Prepare Tutor", variant="primary"
                        )

                        def update_tutor_selection(tutor):
                            """Update selected tutor."""
                            return tutor

                        tutor_dropdown.change(
                            fn=update_tutor_selection,
                            inputs=[tutor_dropdown],
                            outputs=[selected_tutor],
                        )

                    with gr.Row():
                        gr.Markdown(
                            "🌍 **Multilingual Support:** The tutor will automatically respond in the same language you use!<br>"
                            "Just start chatting in your preferred language (English, Spanish, French, etc.) and the tutor will match it.<br>"
                            "<br>"
                            "πŸ”„ **Note:** Once you start chatting, you can't change the lesson or tutor. <br>"
                            "If you want to pick a different one, just hit the reset button and start fresh! 😊<br>"
                        )

                with gr.Column(scale=2):
                    # Chat interface - defined before use
                    chat_interface = gr.ChatInterface(
                        fn=custom_respond,
                        additional_inputs=[
                            lesson_dropdown,
                            lesson_content,
                            tutor_dropdown,
                            difficulty_dropdown,
                        ],
                        type="messages",
                        autofocus=False
                    )

                    # Connect load button after chat_interface is defined
                    load_button.click(
                        fn=load_lesson_content,
                        inputs=[lesson_dropdown, tutor_dropdown],
                        outputs=[
                            lesson_name,
                            lesson_content,
                            status_markdown,
                            chat_interface.chatbot_value,
                        ],
                    )

                    reset_button = gr.Button("Reset", variant="secondary")
                    reset_button.click(
                        lambda: (
                            gr.update(value=""),
                            gr.update(value=""),
                            gr.update(value=TUTOR_NAMES[0] if TUTOR_NAMES else None),
                            gr.update(value="beginner"),
                            "Status reset.",
                            [],
                        ),
                        outputs=[
                            lesson_dropdown,
                            lesson_content,
                            tutor_dropdown,
                            difficulty_dropdown,
                            status_markdown,
                            chat_interface.chatbot_value,
                        ],
                    )

        with gr.Tab("List Lessons"):
            gr.Markdown("πŸ“š Get the list of available lessons.")
            btn = gr.Button("Get")
            output_text = gr.Textbox(label="Lessons")
            btn.click(get_lesson_list, None, output_text)

        with gr.Tab("Lesson Content"):
            gr.Markdown("πŸ“– Get the content of a lesson by its name.")
            lesson_name_input = gr.Textbox(label="Lesson Name")
            lesson_len = gr.Number(label="Max Length", value=1000)
            lesson_content_output = gr.Textbox(label="Lesson Content", lines=20)
            btn = gr.Button("Get")
            btn.click(get_lesson_content, [lesson_name_input, lesson_len], lesson_content_output)

    return demo


if __name__ == "__main__":
    demo = gradio_ui()

    # Launch the Gradio app with MCP server enabled.
    # NOTE: It is required to restart the app when you add or remove MCP tools.
    demo.launch(mcp_server=True)