Spaces:
Sleeping
Sleeping
File size: 6,342 Bytes
2e8d6bf 61edc46 2e8d6bf 61edc46 2e8d6bf 61edc46 2e8d6bf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | """Gradio UI for the Adaptive Study Agent."""
import os
import shutil
import tempfile
from datetime import datetime
import gradio as gr
from dotenv import load_dotenv
load_dotenv()
from src.graph.build_graph import build_study_graph
from src.graph.state import StudyState
from src.graph import edges
def build_report_md(state: StudyState) -> str:
"""Build a markdown session report from the final graph state."""
now = datetime.now()
questions_asked = state["questions_asked"]
questions_correct = state["questions_correct"]
mastery_score = questions_correct / questions_asked if questions_asked > 0 else 0.0
reread_count = len(state.get("weak_chunks", []))
doc_name = os.path.basename(state["document_path"])
weak_areas = []
for entry in state.get("session_history", []):
if entry["score"] < 0.75:
weak_areas.append(entry["question"])
lines = [
"# Study Session Report",
f"**Date:** {now.strftime('%Y-%m-%d %H:%M')}",
f"**Document:** {doc_name}",
"",
"## Summary",
f"- Questions asked: **{questions_asked}**",
f"- Questions correct (score >= 0.75): **{questions_correct}**",
f"- Final mastery score: **{mastery_score:.2f}**",
f"- Re-read cycles triggered: **{reread_count}**",
"",
"## Weak Areas",
]
if weak_areas:
for area in weak_areas:
lines.append(f"- {area}")
else:
lines.append("- None")
lines.extend(["", "## Q&A Log"])
for i, entry in enumerate(state.get("session_history", []), 1):
score_label = "pass" if entry["score"] >= 0.75 else "FAIL"
lines.extend([
f"### Q{i} [{score_label}]",
f"**Question:** {entry['question']}",
"",
f"**Answer:** {entry['answer']}",
"",
f"**Score:** {entry['score']} ",
f"**Reasoning:** {entry['reasoning']}",
"",
"---",
"",
])
return "\n".join(lines)
def run_study_session(file, mastery_threshold, progress=gr.Progress(track_tqdm=False)):
"""Run the adaptive study graph and yield progress updates + final report."""
if file is None:
yield "Please upload a document first.", ""
return
# Gradio 5 returns a filepath string; Gradio 4 returned an object with .name
file_path = file if isinstance(file, str) else file.name
ext = os.path.splitext(file_path)[1]
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
shutil.copy2(file_path, tmp.name)
doc_path = tmp.name
edges.MASTERY_THRESHOLD = mastery_threshold
progress(0, desc="Building study graph...")
yield "Building study graph...", ""
graph = build_study_graph()
initial_state: StudyState = {
"document_path": doc_path,
"chunks": [],
"questions_asked": 0,
"questions_correct": 0,
"current_question": "",
"current_answer": "",
"current_score": 0.0,
"weak_chunks": [],
"session_history": [],
"mastery_reached": False,
}
progress(0.05, desc="Ingesting document...")
yield "Ingesting document...", ""
status_lines = []
last_state = initial_state
for event in graph.stream(initial_state, stream_mode="updates"):
for node_name, node_output in event.items():
if isinstance(node_output, dict):
last_state = {**last_state, **node_output}
if node_name == "ingest":
n = len(last_state.get("chunks", []))
msg = f"Ingested {n} chunks."
status_lines.append(msg)
elif node_name == "generate_question":
q = last_state.get("current_question", "")
qnum = last_state.get("questions_asked", 0) + 1
msg = f"**Q{qnum}:** {q}"
status_lines.append(msg)
elif node_name == "answer":
ans = last_state.get("current_answer", "")
msg = f"Answer: {ans[:200]}..."
status_lines.append(msg)
elif node_name == "evaluate":
s = last_state.get("current_score", 0.0)
asked = last_state.get("questions_asked", 0)
correct = last_state.get("questions_correct", 0)
msg = f"Score: {s} | Progress: {correct}/{asked} correct"
status_lines.append(msg)
ratio = asked / max(edges.MIN_QUESTIONS, asked + 1)
progress(ratio, desc=f"Q{asked} scored {s}")
elif node_name == "reread":
msg = "Re-reading weak chunk for reinforcement..."
status_lines.append(msg)
elif node_name == "summarize":
msg = "Mastery reached! Generating report..."
status_lines.append(msg)
yield "\n\n".join(status_lines), ""
report = build_report_md(last_state)
try:
os.unlink(doc_path)
except OSError:
pass
yield "\n\n".join(status_lines) + "\n\n**Session complete!**", report
with gr.Blocks(title="Adaptive Study Agent") as demo:
gr.Markdown("# Adaptive Study Agent")
gr.Markdown(
"Upload a PDF or TXT document and the agent will quiz itself, "
"evaluate answers, and re-read weak areas until mastery is reached."
)
with gr.Row():
with gr.Column(scale=1):
file_input = gr.File(
label="Upload Document (PDF or TXT)",
file_types=[".pdf", ".txt"],
type="filepath",
)
threshold_slider = gr.Slider(
minimum=0.5,
maximum=1.0,
value=0.75,
step=0.05,
label="Mastery Threshold",
)
start_btn = gr.Button("Start Study Session", variant="primary")
with gr.Column(scale=2):
status_output = gr.Markdown(label="Progress", value="*Waiting to start...*")
gr.Markdown("---")
report_output = gr.Markdown(label="Session Report", value="")
start_btn.click(
fn=run_study_session,
inputs=[file_input, threshold_slider],
outputs=[status_output, report_output],
)
if __name__ == "__main__":
demo.queue().launch()
|