Spaces:
Sleeping
Sleeping
| """ | |
| =============================================== | |
| AnveshAI Edge v2 - HuggingFace Spaces (Gradio) | |
| =============================================== | |
| Offline-first AI tutor for JEE Advanced. | |
| Correctness-first pipeline: deterministic engines, then LLM explanation. | |
| """ | |
| import os | |
| import sys | |
| from typing import Optional | |
| sys.path.insert(0, os.path.dirname(__file__)) | |
| import gradio as gr | |
| from router import classify_intent | |
| from math_engine import evaluate as math_evaluate | |
| from advanced_math_engine import solve as advanced_math_solve | |
| from knowledge_engine import KnowledgeEngine | |
| from conversation_engine import ConversationEngine | |
| from llm_engine import LLMEngine, MATH_SYSTEM_PROMPT, MATH_TEMPERATURE, CHAT_SYSTEM_PROMPT | |
| from reasoning_engine import ReasoningEngine | |
| from inference_engine import InferenceEngine | |
| from physics_engine import PhysicsEngine | |
| from chemistry_engine import ChemistryEngine | |
| from memory import ( | |
| initialize_db, save_interaction, format_history, clear_history, | |
| save_progress, get_progress_summary, get_weak_topics, get_due_topics, | |
| ) | |
| from mock_test import run_mock_test | |
| from formula_sheet import get_formula_sheet | |
| from hint_engine import get_hints | |
| from spaced_repetition import run_review_session | |
| from benchmark_report import generate_report | |
| from main import compose_response | |
| initialize_db() | |
| knowledge_engine = KnowledgeEngine() | |
| conversation_engine = ConversationEngine() | |
| inference_engine = InferenceEngine() | |
| physics_engine_obj = PhysicsEngine() | |
| chemistry_engine_obj = ChemistryEngine() | |
| llm_engine = LLMEngine() | |
| reasoning_eng = ReasoningEngine() | |
| _last_question: list[str] = [""] | |
| WELCOME = ( | |
| "Hi, I am **AnveshAI Edge v2**.\n\n" | |
| "What are we solving today?" | |
| ) | |
| STARTER_HISTORY = [{"role": "assistant", "content": WELCOME}] | |
| APP_CSS = """ | |
| :root { | |
| --edge-bg: #f7f5ef; | |
| --edge-panel: #fffdfa; | |
| --edge-ink: #1f2328; | |
| --edge-muted: #6b7280; | |
| --edge-line: #e5e0d6; | |
| --edge-sidebar: #111318; | |
| --edge-sidebar-soft: #1c2028; | |
| --edge-sidebar-text: #f8f5ec; | |
| --edge-teal: #0f766e; | |
| --edge-amber: #b45309; | |
| --edge-indigo: #4f46e5; | |
| } | |
| .gradio-container { | |
| background: var(--edge-bg) !important; | |
| color: var(--edge-ink) !important; | |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important; | |
| } | |
| #anvesh-app { | |
| max-width: 1380px; | |
| margin: 0 auto; | |
| } | |
| .app-shell { | |
| min-height: calc(100vh - 32px); | |
| gap: 0 !important; | |
| overflow: hidden; | |
| border: 1px solid var(--edge-line); | |
| border-radius: 18px; | |
| background: var(--edge-panel); | |
| box-shadow: 0 20px 60px rgba(17, 19, 24, 0.08); | |
| } | |
| .sidebar { | |
| min-width: 260px !important; | |
| max-width: 300px !important; | |
| background: var(--edge-sidebar); | |
| color: var(--edge-sidebar-text); | |
| padding: 18px !important; | |
| } | |
| .sidebar .prose, | |
| .sidebar .prose *, | |
| .sidebar label, | |
| .sidebar span { | |
| color: var(--edge-sidebar-text) !important; | |
| } | |
| .brand-lockup { | |
| padding: 2px 2px 16px; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.12); | |
| } | |
| .brand-lockup h1 { | |
| margin: 0; | |
| font-size: 1.1rem; | |
| line-height: 1.2; | |
| letter-spacing: 0; | |
| } | |
| .brand-lockup p { | |
| margin: 8px 0 0; | |
| color: #c9c2b8 !important; | |
| font-size: 0.88rem; | |
| } | |
| .rail-card { | |
| margin-top: 16px; | |
| padding: 14px; | |
| border: 1px solid rgba(255, 255, 255, 0.11); | |
| border-radius: 8px; | |
| background: var(--edge-sidebar-soft); | |
| } | |
| .rail-card h2, | |
| .rail-card h3, | |
| .rail-card p { | |
| margin-top: 0; | |
| } | |
| .rail-card code { | |
| color: #f7d08a !important; | |
| background: rgba(255, 255, 255, 0.08) !important; | |
| border-radius: 6px; | |
| } | |
| .sidebar button { | |
| border-radius: 8px !important; | |
| } | |
| .quick-grid { | |
| gap: 8px !important; | |
| } | |
| .main-panel { | |
| min-height: calc(100vh - 32px); | |
| background: var(--edge-panel); | |
| padding: 0 !important; | |
| } | |
| .topbar { | |
| align-items: center; | |
| gap: 16px !important; | |
| padding: 18px 24px !important; | |
| border-bottom: 1px solid var(--edge-line); | |
| background: rgba(255, 253, 250, 0.92); | |
| } | |
| .topbar .prose { | |
| margin: 0 !important; | |
| } | |
| .topbar h2 { | |
| margin: 0; | |
| font-size: 1.15rem; | |
| line-height: 1.25; | |
| letter-spacing: 0; | |
| } | |
| .topbar p { | |
| margin: 4px 0 0; | |
| color: var(--edge-muted); | |
| font-size: 0.9rem; | |
| } | |
| .status-pill { | |
| justify-content: flex-end; | |
| } | |
| .status-pill .prose { | |
| display: flex; | |
| justify-content: flex-end; | |
| } | |
| .status-pill p { | |
| width: fit-content; | |
| margin: 0; | |
| padding: 8px 10px; | |
| border: 1px solid #d7ecdf; | |
| border-radius: 999px; | |
| color: #14532d; | |
| background: #edf8f1; | |
| font-size: 0.82rem; | |
| font-weight: 650; | |
| } | |
| .chat-region { | |
| padding: 20px 24px 0 !important; | |
| } | |
| #chatbot { | |
| min-height: 56vh; | |
| border: 0 !important; | |
| background: transparent !important; | |
| } | |
| #chatbot .message, | |
| #chatbot .bubble-wrap, | |
| #chatbot .message-row { | |
| font-size: 0.98rem; | |
| line-height: 1.6; | |
| } | |
| #chatbot .message.user, | |
| #chatbot .user .message, | |
| #chatbot .user-message { | |
| background: #ebe7ff !important; | |
| color: #211f35 !important; | |
| border: 1px solid #d9d2ff !important; | |
| border-radius: 16px !important; | |
| } | |
| #chatbot .message.bot, | |
| #chatbot .bot .message, | |
| #chatbot .assistant-message { | |
| background: #ffffff !important; | |
| color: var(--edge-ink) !important; | |
| border: 1px solid var(--edge-line) !important; | |
| border-radius: 16px !important; | |
| box-shadow: 0 8px 24px rgba(17, 19, 24, 0.05); | |
| } | |
| #chatbot code { | |
| white-space: pre-wrap; | |
| } | |
| .composer { | |
| margin: 12px 24px 18px !important; | |
| padding: 12px !important; | |
| border: 1px solid var(--edge-line) !important; | |
| border-radius: 14px !important; | |
| background: #ffffff !important; | |
| box-shadow: 0 12px 32px rgba(17, 19, 24, 0.08); | |
| } | |
| .composer textarea { | |
| min-height: 58px !important; | |
| border: 0 !important; | |
| box-shadow: none !important; | |
| font-size: 1rem !important; | |
| } | |
| .composer .form { | |
| border: 0 !important; | |
| } | |
| .send-row { | |
| align-items: center; | |
| border-top: 1px solid #f0ece3; | |
| padding-top: 10px !important; | |
| } | |
| .send-row .prose p { | |
| margin: 0; | |
| color: var(--edge-muted); | |
| font-size: 0.84rem; | |
| } | |
| .examples-wrap { | |
| padding: 0 24px 24px !important; | |
| } | |
| .examples-wrap .examples { | |
| border: 1px solid var(--edge-line) !important; | |
| border-radius: 8px !important; | |
| background: #fbfaf7 !important; | |
| } | |
| footer { | |
| display: none !important; | |
| } | |
| @media (max-width: 900px) { | |
| .app-shell { | |
| border-radius: 0; | |
| min-height: 100vh; | |
| } | |
| .sidebar { | |
| max-width: none !important; | |
| min-width: 100% !important; | |
| } | |
| .main-panel { | |
| min-height: auto; | |
| } | |
| .topbar, | |
| .chat-region, | |
| .examples-wrap { | |
| padding-left: 14px !important; | |
| padding-right: 14px !important; | |
| } | |
| .composer { | |
| margin-left: 14px !important; | |
| margin-right: 14px !important; | |
| } | |
| } | |
| """ | |
| def _initial_history() -> list[dict]: | |
| return [message.copy() for message in STARTER_HISTORY] | |
| def new_chat() -> tuple[list[dict], list[dict], str]: | |
| _last_question[0] = "" | |
| history = _initial_history() | |
| return history, history, "" | |
| def chat(user_input: str, history: Optional[list[dict]]) -> tuple[list[dict], list[dict], str]: | |
| history = list(history or _initial_history()) | |
| user_input = (user_input or "").strip() | |
| if not user_input: | |
| return history, history, "" | |
| intent = classify_intent(user_input) | |
| if intent == "system": | |
| parts = user_input.lower().split() | |
| cmd = parts[0] | |
| if cmd == "/help": | |
| reply = ( | |
| "**Available commands**\n\n" | |
| "| Command | Description |\n" | |
| "|---|---|\n" | |
| "| `/help` | Show this message |\n" | |
| "| `/history` | Last 10 interactions |\n" | |
| "| `/clear` | Clear conversation history |\n" | |
| "| `/test` | JEE mock test (20 q, +4/-1) |\n" | |
| "| `/formulas` | Full formula sheet |\n" | |
| "| `/formulas p` | Physics formulas |\n" | |
| "| `/formulas c` | Chemistry formulas |\n" | |
| "| `/formulas m` | Math formulas |\n" | |
| "| `/hint` | Conceptual hint for last question |\n" | |
| "| `/hint2` | Hint + relevant formula |\n" | |
| "| `/hint3` | Hint + formula + first step |\n" | |
| "| `/review` | Spaced repetition review (SM-2) |\n" | |
| "| `/progress` | Topic-level accuracy summary |\n" | |
| "| `/benchmark` | Full JEE readiness report |\n" | |
| ) | |
| elif cmd == "/history": | |
| reply = f"**Conversation History**\n\n```\n{format_history()}\n```" | |
| elif cmd == "/clear": | |
| clear_history() | |
| _last_question[0] = "" | |
| reply = "Conversation history cleared." | |
| elif cmd == "/test": | |
| reply = f"```\n{run_mock_test()}\n```" | |
| elif cmd == "/formulas": | |
| subj_arg = parts[1] if len(parts) > 1 else None | |
| subj_map = { | |
| "p": "physics", "ph": "physics", "physics": "physics", | |
| "c": "chemistry", "ch": "chemistry", "chem": "chemistry", "chemistry": "chemistry", | |
| "m": "math", "ma": "math", "math": "math", "maths": "math", | |
| } | |
| subject = subj_map.get(subj_arg, None) if subj_arg else None | |
| reply = f"```\n{get_formula_sheet(subject)}\n```" | |
| elif cmd in ("/hint", "/hint1"): | |
| if _last_question[0]: | |
| reply = get_hints(_last_question[0], level=1) | |
| else: | |
| reply = "Ask a question first, then use /hint." | |
| elif cmd == "/hint2": | |
| if _last_question[0]: | |
| reply = get_hints(_last_question[0], level=2) | |
| else: | |
| reply = "Ask a question first, then use /hint2." | |
| elif cmd == "/hint3": | |
| if _last_question[0]: | |
| reply = get_hints(_last_question[0], level=3) | |
| else: | |
| reply = "Ask a question first, then use /hint3." | |
| elif cmd == "/review": | |
| reply = f"```\n{run_review_session()}\n```" | |
| elif cmd == "/progress": | |
| summary = get_progress_summary() | |
| if not summary: | |
| reply = "No progress data yet. Use `/test` or ask JEE questions." | |
| else: | |
| lines = ["**Progress Summary**\n"] | |
| for subj, data in sorted(summary.items()): | |
| a = data['attempted'] | |
| c = data['correct'] | |
| acc = data['accuracy'] | |
| lines.append(f"**{subj}** - {c}/{a} correct ({acc:.1f}%)") | |
| for topic, td in sorted(data['topics'].items()): | |
| tacc = td['correct'] / td['attempted'] * 100 if td['attempted'] else 0 | |
| marker = "OK" if tacc >= 60 else "Needs work" | |
| lines.append(f" {marker} {topic}: {td['correct']}/{td['attempted']} ({tacc:.0f}%)") | |
| reply = "\n".join(lines) | |
| elif cmd == "/benchmark": | |
| summary = get_progress_summary() | |
| due_topics = get_due_topics() | |
| weak = get_weak_topics() | |
| reply = f"```\n{generate_report(summary, due_topics, weak)}\n```" | |
| else: | |
| reply = f"Unknown command `{user_input}`. Type `/help` for available commands." | |
| else: | |
| _last_question[0] = user_input | |
| label, response = compose_response( | |
| user_input, intent, knowledge_engine, | |
| conversation_engine, llm_engine, reasoning_eng, inference_engine, | |
| physics_engine_obj, chemistry_engine_obj, | |
| ) | |
| reply = f"**[{label}]**\n\n{response}" | |
| save_interaction(user_input, response) | |
| if intent in ("physics", "chemistry", "advanced_math"): | |
| subj_map = {"physics": "Physics", "chemistry": "Chemistry", "advanced_math": "Mathematics"} | |
| subj = subj_map[intent] | |
| topic = label.split("+")[0] if "+" in label else label | |
| is_correct = ( | |
| "Could not" not in response | |
| and "Provide" not in response[:80] | |
| and response.strip() != "" | |
| ) | |
| save_progress(subj, topic, is_correct, question=user_input) | |
| history.append({"role": "user", "content": user_input}) | |
| history.append({"role": "assistant", "content": reply}) | |
| return history, history, "" | |
| QUICK_COMMANDS = [ | |
| ("Help", "/help"), | |
| ("Formula sheet", "/formulas"), | |
| ("Physics formulas", "/formulas p"), | |
| ("Hint", "/hint"), | |
| ("Review", "/review"), | |
| ("Progress", "/progress"), | |
| ("Benchmark", "/benchmark"), | |
| ] | |
| EXAMPLES = [ | |
| ["integrate x^2 sin(x)"], | |
| ["solve x^2 - 5x + 6 = 0"], | |
| ["A car accelerates at 3 m/s^2 for 5 s from rest. Find final velocity."], | |
| ["pH of 0.01 M HCl"], | |
| ["Molar mass of Ca(OH)2"], | |
| ["If it rains then the ground is wet. It is raining. Therefore?"], | |
| ["/formulas p"], | |
| ["/test"], | |
| ["/help"], | |
| ] | |
| with gr.Blocks( | |
| title="AnveshAI Edge v2", | |
| elem_id="anvesh-app", | |
| ) as demo: | |
| with gr.Row(elem_classes=["app-shell"]): | |
| with gr.Column(scale=1, elem_classes=["sidebar"]): | |
| gr.Markdown( | |
| """ | |
| <div class="brand-lockup"> | |
| <h1>AnveshAI Edge v2</h1> | |
| <p>JEE Advanced tutor for physics, chemistry, and mathematics.</p> | |
| </div> | |
| """ | |
| ) | |
| new_chat_btn = gr.Button("New chat", variant="secondary") | |
| with gr.Group(elem_classes=["rail-card"]): | |
| gr.Markdown( | |
| "### Study tools\n" | |
| "`/help` `/test` `/formulas` `/hint` `/review` `/progress` `/benchmark`" | |
| ) | |
| command_buttons = [] | |
| with gr.Group(elem_classes=["rail-card"]): | |
| gr.Markdown("### Quick actions") | |
| with gr.Column(elem_classes=["quick-grid"]): | |
| for label, command in QUICK_COMMANDS: | |
| command_buttons.append((gr.Button(label, variant="secondary"), command)) | |
| with gr.Group(elem_classes=["rail-card"]): | |
| gr.Markdown( | |
| "### Engine\n" | |
| "Deterministic solvers first. LLM explanation second." | |
| ) | |
| with gr.Column(scale=4, elem_classes=["main-panel"]): | |
| with gr.Row(elem_classes=["topbar"]): | |
| with gr.Column(scale=4): | |
| gr.Markdown( | |
| "## Ask AnveshAI\n" | |
| "Fast, offline-first help for JEE reasoning and revision." | |
| ) | |
| with gr.Column(scale=1, elem_classes=["status-pill"]): | |
| gr.Markdown("Ready") | |
| with gr.Column(elem_classes=["chat-region"]): | |
| chatbot = gr.Chatbot( | |
| value=_initial_history(), | |
| height=560, | |
| show_label=False, | |
| elem_id="chatbot", | |
| ) | |
| with gr.Group(elem_classes=["composer"]): | |
| txt = gr.Textbox( | |
| placeholder="Message AnveshAI Edge...", | |
| show_label=False, | |
| lines=2, | |
| max_lines=6, | |
| container=False, | |
| ) | |
| with gr.Row(elem_classes=["send-row"]): | |
| gr.Markdown("Physics | Chemistry | Mathematics") | |
| send_btn = gr.Button("Send", variant="primary", scale=1, min_width=112) | |
| with gr.Column(elem_classes=["examples-wrap"]): | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| inputs=txt, | |
| label="Starter prompts", | |
| ) | |
| state = gr.State(_initial_history()) | |
| send_btn.click(chat, [txt, state], [chatbot, state, txt]) | |
| txt.submit(chat, [txt, state], [chatbot, state, txt]) | |
| new_chat_btn.click(new_chat, None, [chatbot, state, txt]) | |
| for button, command in command_buttons: | |
| button.click(lambda history, c=command: chat(c, history), [state], [chatbot, state, txt]) | |
| if __name__ == "__main__": | |
| demo.launch(theme=gr.themes.Soft(), css=APP_CSS) | |