developeranveshraman's picture
Update app.py
dfb3468 verified
Raw
History Blame Contribute Delete
16.3 kB
"""
===============================================
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)