File size: 4,372 Bytes
0119c00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
from pathlib import Path
from typing import Any

import gradio as gr
from dotenv import load_dotenv
from openai import OpenAI

from prompts import MODE_DESCRIPTIONS, PROMPT_TEMPLATES

MODEL_NAME = "gpt-4.1-mini"

# Load environment variables from a local .env file if it exists.
load_dotenv(dotenv_path=Path(__file__).parent / ".env")

SYSTEM_PROMPT = (
    "You are Advanced Python Tutor Bot, an expert and patient teacher for beginners. "
    "Help with Python fundamentals, debugging, quizzes, and code quality. "
    "When relevant, include beginner-friendly data structures and algorithms guidance "
    "(lists, dicts, sets, stacks, queues, recursion, sorting, searching, Big-O at a simple level)."
)


def _history_to_messages(history: list[Any]) -> list[dict[str, str]]:
    """Convert Gradio chat history into OpenAI message objects.



    Supports both older history formats (list of [user, assistant]) and

    newer message formats (list of dicts).

    """
    messages: list[dict[str, str]] = []

    for item in history:
        if isinstance(item, dict):
            role = item.get("role")
            content = item.get("content", "")
            if role in {"user", "assistant"} and content:
                messages.append({"role": role, "content": str(content)})
            continue

        if isinstance(item, (list, tuple)) and len(item) == 2:
            user_text, assistant_text = item
            if user_text:
                messages.append({"role": "user", "content": str(user_text)})
            if assistant_text:
                messages.append({"role": "assistant", "content": str(assistant_text)})

    return messages


def build_messages(mode: str, user_input: str, history: list[Any]) -> list[dict[str, str]]:
    """Build OpenAI messages using mode template + ongoing chat history."""
    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    messages.extend(_history_to_messages(history))

    mode_instruction = PROMPT_TEMPLATES[mode].format(user_input=user_input)
    messages.append({"role": "user", "content": mode_instruction})
    return messages


def get_tutor_response(user_input: str, history: list[Any], mode: str) -> str:
    """Generate a response from OpenAI for the selected tutoring mode."""
    if not user_input or not user_input.strip():
        return "Please enter some text or code so I can help you."

    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        return (
            "Missing OPENAI_API_KEY. Add it to a `.env` file in this folder like: "
            "OPENAI_API_KEY=your_key_here"
        )

    try:
        client = OpenAI(api_key=api_key)
        messages = build_messages(mode, user_input.strip(), history or [])
        response = client.chat.completions.create(
            model=MODEL_NAME,
            messages=messages,
            temperature=0.4,
        )
        return response.choices[0].message.content or "I could not generate a response. Please try again."
    except Exception as error:
        return f"Something went wrong while contacting the AI service: {error}"


def build_app() -> gr.Blocks:
    theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="slate")

    with gr.Blocks(title="Advanced Python Tutor Bot", theme=theme) as app:
        gr.Markdown(
            """

        # 🧠 Advanced Python Tutor Bot

        A professional learning assistant for beginners: explanations, debugging, quizzes, and code improvements.

        """
        )

        with gr.Row():
            with gr.Column(scale=2):
                mode = gr.Dropdown(
                    choices=["Explain Concept", "Debug Code", "Quiz Me", "Improve Code"],
                    value="Explain Concept",
                    label="Tutor Mode",
                    info="Pick how you want the tutor to help in this chat.",
                )
            with gr.Column(scale=3):
                mode_note = gr.Markdown(MODE_DESCRIPTIONS["Explain Concept"])

        mode.change(lambda m: MODE_DESCRIPTIONS[m], inputs=mode, outputs=mode_note)

        gr.ChatInterface(
            fn=get_tutor_response,
            additional_inputs=[mode],
        )


    return app


if __name__ == "__main__":
    build_app().launch()