Spaces:
Running on Zero
Running on Zero
File size: 5,702 Bytes
95ab6f4 b9c4819 95ab6f4 b9c4819 95ab6f4 b9c4819 | 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 | 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) |