import spaces # MUST come before torch / transformers import torch import gradio as gr from transformers import AutoProcessor, AutoModelForImageTextToText MODEL_ID = "webbrain-one/webbrain-vl-2-450M" # Production six-section system prompt from WebBrain's test/vision/prompt.mjs SYSTEM_PROMPT = ( "You are the vision subsystem of a web-automation agent. " "A screenshot of the current browser viewport is attached. " "Describe what is on screen so the planning agent can decide its next action.\n\n" "Format — keep it terse, structured, no flowery prose:\n\n" "1) Page purpose: one line (e.g. \"GitHub repo issue list\", " "\"Gmail compose\", \"Stripe checkout form\").\n" "2) Visible text: list the EXACT strings on buttons, links, headings, tabs, " "and menu items. Quote them verbatim. Do not paraphrase.\n" "3) Inputs: list each visible form field with its label, placeholder, " "current value, and whether it is focused/disabled.\n" "4) State signals: loading spinners, toasts, modals, error banners, " "success messages, CAPTCHAs, cookie/consent banners, overlays.\n" "5) Blockers: anything that would prevent the next likely action " "(overlay, disabled submit, missing data, auth prompt).\n" "6) Unknowns: if you cannot read something clearly, say so. " "Do not guess numbers, names, or identifiers.\n\n" "Rules: no prose intro, no conclusion, no \"this screenshot shows...\", " "no layout description unless it matters (e.g. \"left nav is collapsed\"). " "If the page is blank or still loading, say that in one line and stop." ) USER_TEXT = ( "Describe this screenshot of the current browser viewport " "for a web-automation agent. Follow the format in the system prompt." ) processor = AutoProcessor.from_pretrained(MODEL_ID) model = AutoModelForImageTextToText.from_pretrained( MODEL_ID, dtype=torch.bfloat16, ).to("cuda") @spaces.GPU(duration=30) def analyze_screenshot(image, max_new_tokens=800, temperature=0.0): """Analyze a browser screenshot and return a structured six-section observation. Args: image: A browser viewport screenshot (PIL Image). max_new_tokens: Maximum number of tokens to generate. temperature: Sampling temperature (0 = deterministic). """ if image is None: return "Please upload a browser screenshot image." conversation = [ { "role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}], }, { "role": "user", "content": [ {"type": "image", "image": image}, {"type": "text", "text": USER_TEXT}, ], }, ] inputs = processor.apply_chat_template( conversation, add_generation_prompt=True, return_tensors="pt", return_dict=True, tokenize=True, ).to("cuda") gen_kwargs = { "max_new_tokens": int(max_new_tokens), } if temperature > 0: gen_kwargs["temperature"] = float(temperature) else: gen_kwargs["do_sample"] = False with torch.no_grad(): output_ids = model.generate(**inputs, **gen_kwargs) # Decode only the new tokens generated_ids = output_ids[0, inputs["input_ids"].shape[1]:] result = processor.decode(generated_ids, skip_special_tokens=True) return result.strip() CSS = """ #col-container { max-width: 1100px; margin: 0 auto; } .dark .gradio-container { color: var(--body-text-color); } """ with gr.Blocks() as demo: gr.Markdown( "# 🖥️ WebBrain VL 2 450M — Browser Screenshot Understanding\n" "Upload a browser screenshot to get a structured six-section observation " "for web-automation planning. Fine-tuned from " "[LFM2.5-VL-450M](https://huggingface.co/LiquidAI/LFM2.5-VL-450M)." ) with gr.Column(elem_id="col-container"): with gr.Row(): with gr.Column(scale=1): image_input = gr.Image( label="Browser Screenshot", type="pil", height=400, ) run_btn = gr.Button("Analyze Screenshot", variant="primary") with gr.Accordion("Advanced settings", open=False): max_tokens = gr.Slider( label="Max new tokens", minimum=64, maximum=2048, value=800, step=64, ) temp = gr.Slider( label="Temperature", minimum=0.0, maximum=1.0, value=0.0, step=0.05, ) with gr.Column(scale=1): output = gr.Textbox( label="Structured Observation", lines=24, max_lines=50, ) run_btn.click( fn=analyze_screenshot, inputs=[image_input, max_tokens, temp], outputs=output, api_name="analyze", ) gr.Examples( examples=[ ["example_signin.png", 800, 0.0], ["example_search.png", 800, 0.0], ["example_checkout.png", 800, 0.0], ], inputs=[image_input, max_tokens, temp], outputs=output, fn=analyze_screenshot, cache_examples=True, cache_mode="lazy", ) if __name__ == "__main__": demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS)