Spaces:
Running on Zero
Running on Zero
Download app.py from lsadouk1111/VisualStep-app: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/spaces/lsadouk1111/VisualStep-app/resolve/main/app.py
- Command line
-
hf download hf://spaces/lsadouk1111/VisualStep-app/app.py
-
curl -L -o app.py https://huggingface.co/spaces/lsadouk1111/VisualStep-app/resolve/main/app.py
10.8 kB
| """ | |
| VisualStep β ADHD Classroom Instruction Decomposer | |
| Gradio demo app for Hugging Face Spaces | |
| """ | |
| import json | |
| import gradio as gr | |
| import torch | |
| import spaces | |
| # ββ Model loading βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| MODEL_ID = "lsadouk1111/VisualStep" | |
| model = None | |
| tokenizer = None | |
| # ββ System prompt βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| SYSTEM_PROMPT = """You are an expert in ADHD classroom accommodations for primary school children aged 5-10. | |
| Transform the teacher's spoken classroom instruction into an ADHD-adapted visual step card. | |
| Rules: | |
| - Maximum 5 steps | |
| - Each step: one concrete physical action only | |
| - Each action: maximum 6 words | |
| - Start each step with an imperative verb | |
| - Include a pictogram keyword in the icon field | |
| - Output JSON only, no other text | |
| Output format: | |
| {"steps":[{"id":1,"action":"Open your book","detail":"page 23","icon":"book","check":true}],"n_steps":1,"support_level":"medium"} | |
| support_level: "light" (1-2 steps), "medium" (3-4 steps), "high" (5 steps)""" | |
| # ββ Icon to emoji ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| EMOJI = { | |
| # core actions | |
| "book": "π", "page": "π", "pencil": "βοΈ", "write": "βοΈ", "writing": "βοΈ", | |
| "read": "ποΈ", "eye": "ποΈ", "hand": "β", "finger": "π", | |
| "talk": "π£οΈ", "listen": "π", "think": "π§ ", "look": "ποΈ", "show": "ποΈ", | |
| "draw": "π¨", "paper": "π", "notebook": "π", "board": "π", | |
| "number": "π’", "count": "π’", "answer": "β ", "sort": "ποΈ", | |
| "match": "π", "circle": "β", "plant": "π±", "seed": "π°", | |
| "water": "π§", "animal": "πΎ", "picture": "πΌοΈ", "image": "πΌοΈ", "photo": "πΌοΈ", | |
| "partner": "π₯", "find": "π", "search": "π", "observe": "π", | |
| "measure": "π", "ruler": "π", "line": "π", | |
| "record": "π", "bar": "π", "label": "π·οΈ", | |
| "colour": "ποΈ", "color": "ποΈ", "paintbrush": "ποΈ", | |
| "cut": "βοΈ", "touch": "π", "quiet": "π€«", "ready": "β ", "check": "β ", | |
| "open": "π", "put": "π", "place": "π", "sit": "πͺ", "chair": "πͺ", | |
| "carpet": "πͺ", "desk": "πͺ", | |
| "science": "π¬", "maths": "β", "english": "π", | |
| # people / social | |
| "people": "π₯", "two_people": "π₯", "two-people": "π₯", "friend": "π₯", | |
| "group": "π₯", "class": "π«", "classroom": "π«", "pairs": "π₯", "share": "π₯", | |
| # speech / thought | |
| "pause": "βΈοΈ", "ear": "π", "speak": "π£οΈ", "discussion": "π£οΈ", | |
| "buzz": "π£οΈ", "feedback": "π¬", "speech_bubble": "π¬", "speech-bubble": "π¬", | |
| "thought": "π", "thought-bubble": "π", "thought_bubble": "π", | |
| # writing tools | |
| "marker": "ποΈ", "pen": "ποΈ", "highlighter": "ποΈ", "eraser": "βοΈ", | |
| "edit": "βοΈ", "underline": "π", "sentence": "π", "word": "π", | |
| "text": "π", "paragraph": "π", "title": "π", "story": "π", | |
| # maths | |
| "math": "β", "equation": "β", "plus": "β", "add": "β", "addend": "β", | |
| "divide": "β", "calculator": "π’", "blocks": "π§±", "build": "π§±", | |
| "model": "π§±", "pattern": "π’", "seven": "7οΈβ£", | |
| # organising / tasks | |
| "select": "π", "choose": "π", "pick": "π", | |
| "folder": "π", "task": "π", "tasks": "π", "list": "π", "work": "π", | |
| "fact": "π", "options": "π", "details": "π", "rules": "π", | |
| "examples": "π", "problem": "π", "compare": "π", "checklist": "β ", | |
| "solve": "β ", "questions": "β", "question": "β", | |
| # ideas / feedback | |
| "idea": "π‘", "lightbulb": "π‘", "decision": "π‘", | |
| # time / waiting | |
| "wait": "β³", "clock": "β°", "repeat": "π", "play": "βΆοΈ", | |
| # objects / materials | |
| "box": "π¦", "materials": "π¦", "coat": "π§₯", "wire": "π", | |
| "cup": "π₯", "spoon": "π₯", "beaker": "π§ͺ", "thermometer": "π‘οΈ", | |
| "worksheet": "π", "homework": "π", "exercise": "π", | |
| # nature / misc | |
| "tree": "π³", "blueberry": "π«", "nightshade": "πΏ", "garden": "π±", | |
| "cloud": "βοΈ", "fire": "π₯", "lightning": "β‘", "explosion": "π₯", | |
| "light": "π‘", "breathe": "π¬οΈ", "matter": "π¬", | |
| # feedback / emotion | |
| "thumb": "π", "happy": "π", "heart": "β€οΈ", | |
| # navigation | |
| "door": "πͺ", "broken": "β", "close": "β", | |
| # misc | |
| "marble": "βͺ", "period": "π", "photo": "πΌοΈ", | |
| } | |
| def get_emoji(icon): | |
| if not icon: | |
| return "π" | |
| k = str(icon).lower() | |
| for key, em in EMOJI.items(): | |
| if key in k: | |
| return em | |
| return "π" | |
| # ββ Generation ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate_step_card(instruction, year, subject): | |
| global model, tokenizer | |
| if not instruction.strip(): | |
| return "Please enter a classroom instruction." | |
| # Load model on first call (inside GPU context) | |
| if model is None: | |
| from unsloth import FastModel | |
| model, tokenizer = FastModel.from_pretrained( | |
| model_name=MODEL_ID, | |
| max_seq_length=512, | |
| load_in_4bit=True, | |
| dtype=None, | |
| ) | |
| FastModel.for_inference(model) | |
| model.eval() | |
| messages = [ | |
| {"role": "system", "content": SYSTEM_PROMPT}, | |
| {"role": "user", "content": f"Year: {year} | Subject: {subject} | Instruction: {instruction}"}, | |
| ] | |
| prompt = tokenizer.apply_chat_template( | |
| messages, tokenize=False, add_generation_prompt=True | |
| ) | |
| inputs = tokenizer(prompt, return_tensors="pt").to(model.device) | |
| with torch.no_grad(): | |
| outputs = model.generate( | |
| **inputs, | |
| max_new_tokens=256, | |
| do_sample=False, | |
| pad_token_id=tokenizer.eos_token_id, | |
| ) | |
| generated = outputs[0][inputs["input_ids"].shape[1]:] | |
| raw = tokenizer.decode(generated, skip_special_tokens=True).strip() | |
| raw = raw.replace("```json", "").replace("```", "").strip() | |
| try: | |
| data = json.loads(raw) | |
| steps = data.get("steps", []) | |
| support = data.get("support_level", "medium") | |
| n = data.get("n_steps", len(steps)) | |
| # Build visual card | |
| lines = ["## π WHAT TO DO NOW\n"] | |
| for s in steps: | |
| emoji = get_emoji(s.get("icon", "")) | |
| action = s.get("action", "") | |
| detail = s.get("detail", "") | |
| line = f"β **{s.get('id', '')}.** {emoji} {action}" | |
| if detail: | |
| line += f" β *{detail}*" | |
| lines.append(line) | |
| lines.append(f"\n---") | |
| lines.append(f"*Support level: {support} Β· {n} step(s)*") | |
| return "\n\n".join(lines) | |
| except json.JSONDecodeError: | |
| return f"β οΈ Model output could not be parsed as JSON.\n\nRaw output:\n```\n{raw}\n```" | |
| # ββ Gradio interface βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| EXAMPLES = [ | |
| ["Ok everyone, open your books to page 23, read the paragraph quietly, then answer questions 1 to 3 in your notebook and put your hand up when you're done.", "2", "english"], | |
| ["Right, so, um, can we all take our science books and turn to page 10? And then, after that, I want you to look at the picture and think about what you see.", "1", "science"], | |
| ["Everyone stop what you're doing, pack away your things, and line up quietly at the door please.", "3", "routine"], | |
| ["I want you to sort these animals into mammals and not mammals using the sorting hoops, then write one sentence explaining how you decided.", "1", "science"], | |
| ["OK so, find your maths book, open to page 15, do the first three problems and check your answers with your partner.", "2", "maths"], | |
| ] | |
| with gr.Blocks( | |
| title="VisualStep β ADHD Instruction Decomposer", | |
| theme=gr.themes.Soft(primary_hue="blue"), | |
| css=""" | |
| .card-output { font-size: 1.1em; line-height: 1.8; } | |
| h1 { color: #1F4E79; } | |
| .subtitle { color: #666; font-size: 0.95em; margin-top: -10px; } | |
| """ | |
| ) as demo: | |
| gr.Markdown(""" | |
| # VisualStep | |
| ### ADHD-Adapted Visual Instruction Decomposition for Primary School Classrooms | |
| *Fine-tuned Phi-3 Mini (3.8B) Β· Lamyaa Sadouk Β· EMSI Casablanca* | |
| --- | |
| Enter a classroom instruction as a teacher would say it out loud. | |
| VisualStep will decompose it into a structured, ADHD-adapted visual step card. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| instruction = gr.Textbox( | |
| label="Teacher's spoken instruction", | |
| placeholder="e.g. Ok everyone, open your books to page 23, read the paragraph quietly, then answer questions 1 to 3...", | |
| lines=4, | |
| ) | |
| with gr.Row(): | |
| year = gr.Dropdown( | |
| choices=["1", "2", "3", "4", "5", "6"], | |
| value="2", | |
| label="Year group", | |
| ) | |
| subject = gr.Dropdown( | |
| choices=["english", "maths", "science", "routine", "transition"], | |
| value="english", | |
| label="Subject", | |
| ) | |
| btn = gr.Button("Generate Step Card", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| output = gr.Markdown( | |
| label="ADHD-adapted step card", | |
| elem_classes=["card-output"], | |
| value="*Your step card will appear here.*" | |
| ) | |
| btn.click( | |
| fn=generate_step_card, | |
| inputs=[instruction, year, subject], | |
| outputs=output, | |
| ) | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| inputs=[instruction, year, subject], | |
| label="Try these examples", | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| **About VisualStep:** | |
| This demo accompanies the paper *"VisualStep: A Fine-Tuned Small Language Model for ADHD-Adapted | |
| Visual Instruction Decomposition in Primary School Classrooms"*. | |
| The model was fine-tuned on VisualStep-2K, a dataset of 2,000 spoken classroom instructionβstep card pairs. | |
| All inference runs locally β no data is sent to external servers. | |
| """) | |
| demo.launch() |