import os
import gradio as gr
import requests
import json
import time
# Test HF_TOKEN
HF_TOKEN = os.getenv("HF_TOKEN")
if not HF_TOKEN:
print("Error: HF_TOKEN not set. Please set your Hugging Face API token.")
else:
print("HF_TOKEN loaded successfully.")
# ๐ Web search function (unchanged)
def search_web(query):
try:
url = "https://api.duckduckgo.com/"
params = {"q": query, "format": "json", "no_html": 1, "skip_disambig": 1}
response = requests.get(url, params=params)
data = response.json()
if data.get("AbstractText"):
return data["AbstractText"]
elif data.get("RelatedTopics"):
topics = [t.get("Text", "") for t in data["RelatedTopics"] if "Text" in t]
return " ".join(topics[:3])
else:
return "No useful information found."
except Exception as e:
return f"Search error: {e}"
# ๐ง Memory setup (unchanged)
MEMORY_FILE = "memory.json"
def load_memory():
if os.path.exists(MEMORY_FILE):
with open(MEMORY_FILE, "r") as f:
return json.load(f)
return []
def save_memory(memory):
with open(MEMORY_FILE, "w") as f:
json.dump(memory, f)
memory = load_memory()
# ๐ฌ Chat function (unchanged)
def chat_with_model(message, history, context):
if not isinstance(history, list):
history = []
if message.lower().startswith("search "):
query = message[7:]
search_result = search_web(query)
timestamp = time.strftime("%H:%M")
history.append((f"{message} {timestamp}", f"๐ Here's what I found online:\n\n{search_result} {timestamp}"))
save_memory(history)
return history, history
conversation = [
{"role": "system", "content": (
"You are EduAI, a multilingual educational AI assistant created by a Sri Lankan student named Wafa Fazly. "
"When solving math, explain step-by-step like a professional tutor. "
"Use Markdown and LaTeX formatting for equations (use \\[ and \\]). "
"Keep answers neat, structured, and student-friendly."
)}
]
for past_user, past_bot in history[-5:]:
conversation.append({"role": "user", "content": past_user.split(' {timestamp}", f"{reply} {timestamp}"))
save_memory(history)
return history, history
except Exception as e:
print("Backend Error:", e)
timestamp = time.strftime("%H:%M")
error_msg = f"๐
EduAI is having trouble connecting right now. Check your HF_TOKEN or try again later! {timestamp}"
history.append((f"{message} {timestamp}", error_msg))
return history, history
# ๐ Sidebar context update (unchanged)
def update_context(choice):
if not choice:
return "๐ **You are in General Mode.** Ask EduAI anything about your studies!"
return f"๐ **You selected {choice} mode.** Ask anything related to this topic!"
# ๐งน Clear chat memory (unchanged)
def clear_memory():
if os.path.exists(MEMORY_FILE):
os.remove(MEMORY_FILE)
return [], "๐งน Chat memory cleared! Start fresh."
# ๐ Custom CSS for light theme, full-screen, responsive, cyan buttons with animations
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
body {
font-family: 'Inter', sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
color: #334155;
height: 100vh;
overflow: hidden;
}
.gradio-container {
background: transparent;
box-shadow: none;
height: 100vh;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
border-bottom: 1px solid #e2e8f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header-title {
font-size: 24px;
font-weight: 600;
color: #00BCD4;
text-shadow: 0 0 10px rgba(0, 188, 212, 0.3);
}
.toggle-btn {
background: #00BCD4;
color: white;
border: none;
border-radius: 8px;
padding: 10px;
cursor: pointer;
font-size: 18px;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(0, 188, 212, 0.3);
}
.toggle-btn:hover {
background: #0097a7;
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 188, 212, 0.4);
}
.sidebar {
position: fixed;
top: 70px;
left: 0;
width: 300px;
height: calc(100vh - 70px);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
padding: 20px;
border-radius: 0 12px 12px 0;
box-shadow: 4px 0 12px rgba(0,0,0,0.1);
border: 1px solid #e2e8f0;
border-left: none;
z-index: 999;
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.sidebar.visible {
transform: translateX(0);
}
.menu-title {
font-size: 18px;
font-weight: 500;
margin-bottom: 15px;
color: #00BCD4;
}
.accordion {
border-radius: 10px;
margin-bottom: 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.accordion-header {
background: #f1f5f9;
color: #334155;
border-radius: 10px;
padding: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.accordion-header:hover {
background: #00BCD4;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 188, 212, 0.3);
}
.main-chat {
position: fixed;
top: 70px;
left: 0;
right: 0;
bottom: 0;
padding: 20px;
background: transparent;
display: flex;
flex-direction: column;
z-index: 1;
}
.context-box {
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
margin-bottom: 15px;
border: 1px solid #e2e8f0;
color: #334155;
font-size: 14px;
}
.chatbox {
flex: 1;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
background: rgba(255, 255, 255, 0.9);
border: 1px solid #e2e8f0;
padding: 15px;
overflow-y: auto;
}
.message {
display: flex;
margin: 15px 0;
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.message.user {
justify-content: flex-end;
}
.message.ai {
justify-content: flex-start;
}
.message .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin: 0 10px;
background: #00BCD4;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 16px;
}
.message.ai .avatar {
background: #e2e8f0;
color: #334155;
}
.message .bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
position: relative;
}
.message.user .bubble {
background: #00BCD4;
color: white;
}
.message.ai .bubble {
background: #f8fafc;
color: #334155;
border: 1px solid #e2e8f0;
}
.timestamp {
font-size: 10px;
opacity: 0.7;
margin-top: 5px;
display: block;
}
.typing {
display: none;
font-style: italic;
color: #64748b;
padding: 10px;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
.input-row {
margin-top: 15px;
}
.chat-input {
width: 100%;
border-radius: 20px;
padding: 12px 16px;
border: 1px solid #cbd5e1;
background: rgba(255, 255, 255, 0.9);
color: #334155;
transition: all 0.3s ease;
font-size: 14px;
}
.chat-input:focus {
border-color: #00BCD4;
outline: none;
box-shadow: 0 0 0 2px rgba(0, 188, 212, 0.2);
}
.btn-clear {
background: #00BCD4;
color: white;
border-radius: 10px;
padding: 10px 15px;
border: none;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(0, 188, 212, 0.3);
}
.btn-clear:hover {
background: #0097a7;
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 188, 212, 0.4);
}
.about-text {
font-size: 14px;
color: #64748b;
line-height: 1.5;
}
.radio {
margin: 10px 0;
}
.radio label {
color: #334155;
}
@media (max-width: 768px) {
.sidebar {
width: 250px;
}
.header-title {
font-size: 20px;
}
}
"""
# ๐จ Gradio Interface with light theme, full-screen, responsive, cyan buttons with animations
with gr.Blocks(theme=gr.themes.Base(), css=custom_css) as iface:
with gr.Row(elem_classes="header"):
gr.Markdown("# ๐ค **EduAI**", elem_classes="header-title")
toggle_btn = gr.Button("โฐ", elem_classes="toggle-btn")
sidebar = gr.Column(scale=1, elem_classes="sidebar", visible=False)
with sidebar:
gr.Markdown("### ๐งญ **Menu**", elem_classes="menu-title")
with gr.Accordion("๐ Subject Tutor", open=False):
subj = gr.Radio(
["Science ๐งช", "ICT ๐ป", "English ๐", "Mathematics โ"],
label="Choose a subject",
type="index"
)
with gr.Accordion("๐ Study Planner", open=False):
planner = gr.Radio(
["View Plan ๐
", "Add Task โ๏ธ", "Study Tips ๐ก"],
label="Planner Options",
type="index"
)
with gr.Accordion("๐ Languages", open=False):
lang = gr.Radio(
["Learn Sinhala ๐ฑ๐ฐ", "Learn Tamil ๐ฎ๐ณ", "Learn English ๐ฌ๐ง", "Learn Spanish ๐ช๐ธ"],
label="Language Options",
type="index"
)
with gr.Accordion("โ๏ธ Settings", open=False):
clear_btn = gr.Button("๐งน Clear Memory", elem_classes="btn-clear")
with gr.Accordion("๐ฉโ๐ About", open=False):
gr.Markdown("""
EduAI โ developed by **Wafa Fazly** using a pre-trained AI model.
Helps learners understand **Science, ICT, English, and more** in a simple, friendly way! ๐ฌ
""", elem_classes="about-text")
with gr.Column(scale=4, elem_classes="main-chat"):
context_display = gr.Markdown(
"๐ **You are in General Mode.** Ask EduAI anything about your studies!",
elem_classes="context-box"
)
chatbot = gr.Chatbot(
label="",
height=500,
render_markdown=True,
type="messages",
latex_delimiters=[{"left": "$$", "right": "$$", "display": True}, {"left": "\\[", "right": "\\]", "display": True}]
)
typing_indicator = gr.Markdown("", elem_classes="typing")
with gr.Row(elem_classes="input-row"):
msg = gr.Textbox(placeholder="Message EduAI...", elem_classes="chat-input", show_label=False)
# Event Handlers
toggle_btn.click(lambda: gr.update(visible=not sidebar.visible), outputs=sidebar)
subj.change(update_context, inputs=subj, outputs=context_display)
planner.change(update_context, inputs=planner, outputs=context_display)
lang.change(update_context, inputs=lang, outputs=context_display)
msg.submit(lambda: "EduAI is typing...", outputs=typing_indicator).then(
chat_with_model, inputs=[msg, chatbot, context_display], outputs=[chatbot, chatbot]
).then(lambda: "", outputs=typing_indicator)
clear_btn.click(clear_memory, outputs=[chatbot, context_display])
iface.launch()