|
|
import os |
|
|
import gradio as gr |
|
|
import requests |
|
|
import json |
|
|
import time |
|
|
|
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
|
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_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() |
|
|
|
|
|
|
|
|
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} <span class='timestamp'>{timestamp}</span>", f"๐ Here's what I found online:\n\n{search_result} <span class='timestamp'>{timestamp}</span>")) |
|
|
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(' <span')[0]}) |
|
|
conversation.append({"role": "assistant", "content": past_bot.split(' <span')[0]}) |
|
|
|
|
|
conversation.append({"role": "user", "content": message}) |
|
|
|
|
|
try: |
|
|
response = requests.post( |
|
|
"https://router.huggingface.co/v1/chat/completions", |
|
|
headers={ |
|
|
"Authorization": f"Bearer {HF_TOKEN}", |
|
|
"Content-Type": "application/json" |
|
|
}, |
|
|
json={ |
|
|
"model": "deepseek-ai/DeepSeek-V3.2-Exp:novita", |
|
|
"messages": conversation |
|
|
} |
|
|
) |
|
|
if response.status_code != 200: |
|
|
raise Exception(f"API Error: {response.status_code} - {response.text}") |
|
|
|
|
|
data = response.json() |
|
|
reply = data["choices"][0]["message"]["content"] |
|
|
|
|
|
reply = reply.replace("Step", "\n\n**Step").replace(":", ":**").replace("\\[", "\n\n\\[").replace("\\]", "\\]\n\n") |
|
|
|
|
|
timestamp = time.strftime("%H:%M") |
|
|
history.append((f"{message} <span class='timestamp'>{timestamp}</span>", f"{reply} <span class='timestamp'>{timestamp}</span>")) |
|
|
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! <span class='timestamp'>{timestamp}</span>" |
|
|
history.append((f"{message} <span class='timestamp'>{timestamp}</span>", error_msg)) |
|
|
return history, history |
|
|
|
|
|
|
|
|
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!" |
|
|
|
|
|
|
|
|
def clear_memory(): |
|
|
if os.path.exists(MEMORY_FILE): |
|
|
os.remove(MEMORY_FILE) |
|
|
return [], "๐งน Chat memory cleared! Start fresh." |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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() |
|
|
|