| """TemperCheck β Gradio app. |
| |
| Upload a social-media profile screenshot; get a playful "temper" read from a |
| small Gemma 4 vision model. For the Build Small Hackathon (model <= 32B, Gradio |
| on Hugging Face Spaces). |
| |
| Run locally (Ollama backend, default): uv run app.py |
| Run the Spaces backend locally: TEMPER_BACKEND=transformers uv run app.py |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
|
|
| import gradio as gr |
|
|
| from tempercheck import get_backend_name, score_image |
|
|
| PORT = int(os.environ.get("TEMPER_PORT", "7140")) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| PASTE_JS = """ |
| () => { |
| if (window.__temperPasteBound) return; |
| window.__temperPasteBound = true; |
| let busy = false; |
| const inject = (blob) => { |
| const root = document.querySelector('#temper_image'); |
| if (!root) return; |
| let input = root.querySelector("input[type='file']"); |
| if (!input) { |
| const clear = root.querySelector("button[aria-label='Clear'], button[title='Clear']"); |
| if (clear) { |
| clear.click(); |
| return setTimeout(() => inject(blob), 80); |
| } |
| return; |
| } |
| const dt = new DataTransfer(); |
| dt.items.add(new File([blob], 'pasted.png', { type: blob.type || 'image/png' })); |
| input.files = dt.files; |
| input.dispatchEvent(new Event('change', { bubbles: true })); |
| }; |
| window.addEventListener('paste', (e) => { |
| if (busy) return; |
| const items = (e.clipboardData || window.clipboardData)?.items || []; |
| for (const it of items) { |
| if (it.type && it.type.startsWith('image/')) { |
| const blob = it.getAsFile(); |
| if (blob) { |
| e.preventDefault(); |
| busy = true; |
| inject(blob); |
| setTimeout(() => { busy = false; }, 600); |
| } |
| return; |
| } |
| } |
| }); |
| } |
| """ |
|
|
| DISCLAIMER = ( |
| "TemperCheck is a fast LLM-based back-of-the-envelope calculation to see if a " |
| "given social media profile is run by someone with a bad temper. It performs a " |
| "quick visual analysis of a screenshot of a profile, and makes a hypothetical " |
| "judgment accordingly. *It is NOT a real personality and says nothing factual " |
| "about any actual person.*" |
| ) |
|
|
|
|
| def _render(verdict) -> tuple[str, str]: |
| bar = "β" * (verdict.score // 5) + "β" * (20 - verdict.score // 5) |
| signals = " ".join(f"`{s}`" for s in verdict.signals) if verdict.signals else "β" |
| md = ( |
| f"## {verdict.band}\n\n" |
| f"**{verdict.verdict}** β temper **{verdict.score}/100**\n\n" |
| f"`{bar}`\n\n" |
| f"{verdict.rationale}\n\n" |
| f"**Signals:** {signals}" |
| ) |
| return md, verdict.raw |
|
|
|
|
| def analyze(image): |
| if image is None: |
| return "Upload a profile screenshot to get a read.", "" |
| try: |
| return _render(score_image(image)) |
| except Exception as exc: |
| return ( |
| f"β οΈ Could not score this image.\n\n```\n{type(exc).__name__}: {exc}\n```", |
| "", |
| ) |
|
|
|
|
| def _disable_button(): |
| return gr.update(interactive=False) |
|
|
|
|
| def _enable_button(): |
| return gr.update(interactive=True) |
|
|
|
|
| with gr.Blocks(title="TemperCheck") as demo: |
| gr.Markdown("# π€ TemperCheck\n### How short a temper does this profile look to have?") |
| gr.Markdown(DISCLAIMER) |
| with gr.Row(): |
| with gr.Column(): |
| image_in = gr.Image( |
| type="pil", |
| label="Profile image / screenshot", |
| sources=["upload", "clipboard"], |
| elem_id="temper_image", |
| height=400, |
| ) |
| go = gr.Button("Check the temper β", variant="primary") |
| with gr.Column(): |
| result = gr.Markdown(label="Verdict") |
| with gr.Accordion("Raw model output (agent trace)", open=False): |
| raw = gr.Code(label="raw") |
| |
| |
| |
| go.click(_disable_button, None, go).then( |
| analyze, inputs=image_in, outputs=[result, raw] |
| ).then(_enable_button, None, go) |
| gr.Markdown(f"<sub>Backend: `{get_backend_name()}`</sub>") |
| demo.load(None, None, None, js=PASTE_JS) |
|
|
|
|
| if __name__ == "__main__": |
| if os.environ.get("SPACE_ID"): |
| |
| demo.launch(theme=gr.themes.Soft()) |
| else: |
| demo.launch( |
| server_name="0.0.0.0", server_port=PORT, theme=gr.themes.Soft() |
| ) |
|
|