STASS / app.py
Barat123's picture
Update app.py
b1d7dbb verified
Raw History Blame Contribute Delete
8.42 kB
import os
import gradio as gr
from huggingface_hub import InferenceClient
from docx import Document
from PIL import Image
import pytesseract
import fitz
import pathlib
import threading
import time
def resolve_token(ui_token):
if ui_token and ui_token.strip():
return ui_token.strip()
env_token = os.getenv("hf")
if env_token:
return env_token.strip()
return None
SUPPORTED_EXT = (
".pdf", ".docx", ".txt", ".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff"
)
def extract_text_from_file(filepath):
if not filepath:
return ""
if isinstance(filepath, dict) and "name" in filepath:
filepath = filepath["name"]
ext = pathlib.Path(filepath).suffix.lower()
try:
if ext == ".pdf":
doc = fitz.open(filepath)
return "\n".join(page.get_text() for page in doc)
elif ext == ".docx":
doc = Document(filepath)
return "\n".join(p.text for p in doc.paragraphs)
elif ext == ".txt":
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
return f.read()
elif ext in (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff"):
img = Image.open(filepath)
return pytesseract.image_to_string(img)
else:
return "Unsupported file type"
except Exception as e:
return f"Error reading file: {str(e)}"
MODELS = {
"DeepSeek-Qwen 1.5B (Fastest)": "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
"Qwen 3 4B (Good Speed)": "Qwen/Qwen3-4B-Thinking-2507:nscale",
"Gemma 3 4B": "google/gemma-3n-E4B-it:together",
"DeepSeek-Llama 8B (Recommended)": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B:nscale",
"Qwen 3 8B": "Qwen/Qwen3-8B:nscale",
"OpenAI GPT OSS 120B": "openai/gpt-oss-120b:novita",
"Gemma 4 31B (Best)": "google/gemma-4-31B-it:novita"
}
ALL_MODEL_NAMES = list(MODELS.keys())
SYSTEM_MSG = """You are an expert Academic Instructor and Curriculum Designer, highly familiar with SASTRA University's academic structure.
- STRICTLY adhere to the provided syllabus/topic. Do not invent concepts, formulas, or fake exam papers.
- Use clean, structured Markdown. Enforce exact formatting where specified.
- Keep outputs practical, concise, and immediately usable. Avoid overcomplication.
- SASTRA RESOURCES: Actively identify and recommend SASTRA-specific resources. Mention SASTRA Material Hub, Material Base, previous year CIA/End-Sem papers, and standard textbooks prescribed by SASTRA faculty. Frame teaching points and questions to match SASTRA's typical assessment patterns (CIA 1, CIA 2, End Semester).
- GENERAL RESOURCES: If a specific standard textbook or verified platform (like NPTEL, standard authors) is widely used for this topic, cite it accurately.
- NEVER hallucinate citations or paper questions. If unsure about a specific SASTRA paper, generalize based on typical university patterns while maintaining the SASTRA difficulty curve."""
def make_prompts(syllabus):
base = f"""
Academic Context:
{syllabus}
General Requirements:
- Maintain strict alignment with the syllabus
- Format strictly in Markdown
- Keep explanations concise, practical, and exam/teaching ready
- Cite SASTRA-specific resources (Material Hub, Material Base, previous papers) or widely recognized academic platforms
"""
return {
"lesson": base + """
Generate a period-wise teaching schedule (Lesson Plan):
- Assume each period is 45-50 minutes each.
- Output MUST be a single Markdown table with exactly these columns:
| Period | Topic/Subtopic | Key Teaching Points |
- Keep descriptions brief. Focus on logical progression.
- Reserve the final period for revision.
- Output MUST be a valid GitHub-Flavored Markdown table.
- Do NOT use ASCII separators like +--- or ----
- Table must render correctly in Markdown.
- Give 'Resources' relevant SASTRA Material Hub modules, slides or materials from the interneton that topic, standard prescribed book chapters, or typical CIA question types for all topics.
- Explain each concepts: Defintion (understandable way only, like points), examples (very important)
""",
"qa": base + """ Generate 8 exam-style questions with answers:
- Match SASTRA CIA and End-Sem difficulty (include tricky and conceptual questions).
- Mix:
* 3 short answers (2–3 lines)
* 3 medium answers (5–6 lines)
* 2 long answers (8–12 lines, may include derivations or explanations or code, for diagrams verify materials/resources/text books)
- Include at least 2 tricky or application-based questions.
- Format strictly:
**Q1.** [Question]
**A:** [Answer]
- For long answers:
- Use stepwise explanation or bullet points
- Include formulas where relevant
- If aligned with SASTRA Material Base or textbook patterns, mention briefly.
""",
"mcq": base + """
Generate 8 multiple-choice questions:
- 4 options (A-D), one clearly correct, three plausible distractors.
- Format strictly:
**Q1.** [Stem]
A) ... B) ... C) ... D) ...
**Correct:** [Letter] | **Why:** [1-line rationale]
- Focus on core concepts, formula application, and common student mistakes seen in SASTRA exams.
""",
"summary": base + """
Produce a rapid-revision summary:
- List 5 core concepts/formulas
- Provide 3 high-yield takeaways for SASTRA exams
- Include 1 quick self-check question based on typical CIA patterns
- Keep strictly under 150 words. Use bullet points only. No paragraphs.
"""
}
def stream_llm(model_id, prompt, hf_token):
if not hf_token:
yield "❌ No Hugging Face API key found."
return
try:
client = InferenceClient(token=hf_token)
stream = client.chat.completions.create(
model=model_id,
messages=[
{"role": "system", "content": SYSTEM_MSG},
{"role": "user", "content": prompt},
],
max_tokens=2048,
temperature=0.7,
top_p=0.95,
stream=True,
)
partial = ""
for chunk in stream:
if chunk.choices and chunk.choices[0].delta:
token = chunk.choices[0].delta.get("content", "")
if token:
partial += token
yield partial
except Exception as e:
yield f"❌ API Error:\n{str(e)}"
def generate_content(text, file, model_label, token):
hf_token = resolve_token(token)
file_text = extract_text_from_file(file) if file else ""
syllabus = (text + "\n\n" + file_text).strip()
if not syllabus:
yield ("Provide topic or file", "", "", "")
return
prompts = make_prompts(syllabus)
model_id = MODELS[model_label]
outputs = ["", "", "", ""]
keys = ["lesson", "qa", "mcq", "summary"]
lock = threading.Lock()
def run_stream(i, key):
for chunk in stream_llm(model_id, prompts[key], hf_token):
with lock:
outputs[i] = chunk
threads = []
for i, key in enumerate(keys):
t = threading.Thread(target=run_stream, args=(i, key))
t.start()
threads.append(t)
while any(t.is_alive() for t in threads):
time.sleep(0.1)
yield tuple(outputs)
yield tuple(outputs)
CSS = """
body,.gradio-container{
font-family: Inter, sans-serif !important;
}
"""
with gr.Blocks() as demo:
gr.Markdown("# πŸŽ“ AI Study Material Generator (Streaming)")
with gr.Row():
with gr.Column():
text_input = gr.Textbox(placeholder="Paste syllabus or topic", lines=6)
file_input = gr.File(label="Upload syllabus file", file_types=list(SUPPORTED_EXT))
with gr.Column():
model_selector = gr.Dropdown(choices=ALL_MODEL_NAMES, value="DeepSeek-Llama 8B (Recommended)", label="Model")
token_box = gr.Textbox(label="HF API Key (optional)", type="password")
btn = gr.Button("Generate")
with gr.Tabs():
with gr.TabItem("Lesson Plan"):
lesson = gr.Markdown()
with gr.TabItem("Question and Anwer"):
qa = gr.Markdown()
with gr.TabItem("MCQ"):
mcq = gr.Markdown()
with gr.TabItem("Summary"):
summary = gr.Markdown()
btn.click(
fn=generate_content,
inputs=[text_input, file_input, model_selector, token_box],
outputs=[lesson, qa, mcq, summary]
)
demo.launch(css=CSS)