Spaces:
Sleeping
Sleeping
| Perfect, Cherry! ๐ Hereโs a **complete, ready-to-run Gradio AI agent** with: | |
| * โ `type="messages"` properly implemented (no more incompatible data errors) | |
| * โ Modern **dark theme** UI with rounded chat bubbles | |
| * โ **Sidebar** with subjects, planner, languages, settings, and About section | |
| * โ **Floating send button + file upload** | |
| * โ **Context box** showing current mode | |
| --- | |
| ```python | |
| import os | |
| import gradio as gr | |
| import requests | |
| import json | |
| # ======================= | |
| # Hugging Face Token | |
| # ======================= | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| if not HF_TOKEN: | |
| print("Error: HF_TOKEN not set.") | |
| else: | |
| print("HF_TOKEN loaded successfully.") | |
| # ======================= | |
| # Memory | |
| # ======================= | |
| 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) | |
| # ======================= | |
| # Web search function | |
| # ======================= | |
| 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.get("RelatedTopics", []) if "Text" in t] | |
| return " ".join(topics[:3]) | |
| else: | |
| return "No useful information found." | |
| except Exception as e: | |
| return f"Search error: {e}" | |
| # ======================= | |
| # Chat function | |
| # ======================= | |
| def chat_with_model(message, history, context, file_input=None): | |
| if history is None: | |
| history = [] | |
| if message.lower().startswith("search "): | |
| query = message[7:] | |
| search_result = search_web(query) | |
| history.append({"role": "user", "content": message}) | |
| history.append({"role": "assistant", "content": f"๐ Here's what I found online:\n\n{search_result}"}) | |
| save_memory(history) | |
| return history, history | |
| if file_input: | |
| message += f"\n\n๐ (User uploaded a file named '{file_input.name}')" | |
| # Add user message | |
| history.append({"role": "user", "content": message}) | |
| # Build conversation for API | |
| conversation = [{"role": m["role"], "content": m["content"]} for m in history] | |
| 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}") | |
| reply = response.json()["choices"][0]["message"]["content"] | |
| # Format math nicely | |
| reply = reply.replace("Step", "\n\n**Step").replace(":", ":**").replace("\\[", "\n\n\\[").replace("\\]", "\\]\n\n") | |
| # Add assistant reply | |
| history.append({"role": "assistant", "content": reply}) | |
| save_memory(history) | |
| return history, history | |
| except Exception as e: | |
| print("Backend Error:", e) | |
| error_msg = "๐ EduAI is having trouble connecting. Check HF_TOKEN or try again later!" | |
| history.append({"role": "assistant", "content": error_msg}) | |
| return history, history | |
| # ======================= | |
| # Sidebar context update | |
| # ======================= | |
| def update_context(choice): | |
| if choice is None: | |
| 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 memory | |
| # ======================= | |
| def clear_memory(): | |
| if os.path.exists(MEMORY_FILE): | |
| os.remove(MEMORY_FILE) | |
| return [], "๐งน Chat memory cleared! Start fresh." | |
| # ======================= | |
| # Custom CSS | |
| # ======================= | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'); | |
| body { font-family:'Roboto', sans-serif; background:#1e1e1e; color:#e0e0e0; min-height:100vh; } | |
| .header-title { font-size:36px; text-align:center; margin-bottom:20px; color:#007BFF; font-weight:700; text-shadow:0 0 10px rgba(0,123,255,0.5); } | |
| .sidebar { background:#2a2a2a; padding:20px; border-radius:12px; box-shadow:0 4px 12px rgba(0,0,0,0.5); border:1px solid #333; } | |
| .menu-title { font-size:20px; font-weight:500; margin-bottom:15px; color:#007BFF; } | |
| .accordion { background:#2a2a2a; border-radius:12px; padding:10px; margin-bottom:10px; box-shadow:0 3px 8px rgba(0,0,0,0.5); } | |
| .accordion-header { background:#333; color:#e0e0e0; border-radius:10px; padding:12px; font-weight:500; cursor:pointer; transition:0.3s; } | |
| .accordion-header:hover { background:#007BFF; color:#1e1e1e; } | |
| .main-chat { padding:20px; background:#1e1e1e; } | |
| .context-box { background:#2a2a2a; padding:15px; border-radius:10px; box-shadow:0 2px 6px rgba(0,0,0,0.3); margin-bottom:15px; border:1px solid #333; color:#e0e0e0; } | |
| .chatbox .message.user { background:#007BFF; color:#fff; border-radius:20px; padding:12px 18px; margin:8px 0; } | |
| .chatbox .message.bot { background:#333; color:#e0e0e0; border-radius:20px; padding:12px 18px; margin:8px 0; } | |
| .input-row { display:flex; gap:10px; margin-top:10px; } | |
| .chat-input { flex:1; border-radius:12px; padding:12px; border:1px solid #555; background:#2a2a2a; color:#e0e0e0; } | |
| .chat-input:focus { border-color:#007BFF; outline:none; } | |
| .btn-send { background:#7c3aed; color:#fff; border-radius:12px; padding:12px 18px; border:none; cursor:pointer; transition:0.3s; } | |
| .btn-send:hover { background:#5b21b6; } | |
| .file-upload { width:50px; height:50px; border:1px solid #555; border-radius:12px; background:#2a2a2a; display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:20px; color:#e0e0e0; transition:0.3s; } | |
| .file-upload:hover { background:#007BFF; color:#1e1e1e; } | |
| .btn-clear { background:#f44336; color:white; border-radius:12px; padding:10px 15px; border:none; cursor:pointer; transition:0.3s; } | |
| .btn-clear:hover { background:#d32f2f; } | |
| .about-text { font-size:14px; color:#aaa; line-height:1.5; } | |
| .radio label { color:#e0e0e0; margin:8px 0; } | |
| """ | |
| # ======================= | |
| # Gradio UI | |
| # ======================= | |
| with gr.Blocks(theme=gr.themes.Base(), css=custom_css) as iface: | |
| gr.Markdown("# ๐ **EduAI โ Professional Study Companion**", elem_classes="header-title") | |
| with gr.Row(): | |
| # Sidebar | |
| with gr.Column(scale=1, min_width=250, elem_classes="sidebar"): | |
| gr.Markdown("### ๐งญ **Main 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**.", | |
| elem_classes="about-text" | |
| ) | |
| # Main Chat Area | |
| 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="EduAI Chat", | |
| height=500, | |
| render_markdown=True, | |
| type="messages", | |
| latex_delimiters=[{"left": "$$", "right": "$$", "display": True}, | |
| {"left": "\\[", "right": "\\]", "display": True}] | |
| ) | |
| # Input row | |
| with gr.Row(elem_classes="input-row"): | |
| msg = gr.Textbox(label="Ask EduAI:", placeholder="Type your question here...", elem_classes="chat-input", show_label=False) | |
| file_input = gr.File(label="๐", file_types=[".pdf", ".docx", ".png", ".jpg"], elem_classes="file-upload", show_label=False) | |
| send_btn = gr.Button("โ๏ธ", elem_classes="btn-send") | |
| # ======================= | |
| # Event handlers | |
| # ======================= | |
| 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) | |
| send_btn.click(chat_with_model, inputs=[msg, chatbot, context_display, file_input], outputs=[chatbot, chatbot]) | |
| msg.submit(chat_with_model, inputs=[msg, chatbot, context_display, file_input], outputs=[chatbot, chatbot]) | |
| clear_btn.click(clear_memory, outputs=[chatbot, context_display]) | |
| # Launch the app | |
| iface.launch() | |
| ``` | |