Spaces:
Running
Running
Commit ·
fe1bb8d
1
Parent(s): d324338
WOOF - what did my dog eat. built this after serena left me a mystery this morning
Browse files- README.md +51 -11
- app.py +439 -0
- requirements.txt +3 -0
README.md
CHANGED
|
@@ -1,21 +1,61 @@
|
|
| 1 |
---
|
| 2 |
title: WOOF
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: yellow
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
| 15 |
-
import gradio as gr
|
| 16 |
|
| 17 |
-
|
| 18 |
-
return "Hello " + name + "!!"
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: WOOF
|
| 3 |
+
emoji: "\U0001F415"
|
| 4 |
colorFrom: yellow
|
| 5 |
+
colorTo: yellow
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: "5.34.2"
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
tags:
|
| 12 |
+
- pets
|
| 13 |
+
- dog-health
|
| 14 |
+
- veterinary
|
| 15 |
+
- build-small-hackathon
|
| 16 |
---
|
| 17 |
|
| 18 |
+
# WOOF — What's Our Output, Fido? 🐕
|
|
|
|
| 19 |
|
| 20 |
+
Every dog owner has stood over a mystery pile at 6am thinking "what the FUCK did you eat."
|
|
|
|
| 21 |
|
| 22 |
+
This app answers that question.
|
| 23 |
+
|
| 24 |
+
## What It Does
|
| 25 |
+
|
| 26 |
+
Describe what you found (or upload a photo if you're brave). WOOF tells you:
|
| 27 |
+
- **What you're looking at** — plain English assessment
|
| 28 |
+
- **Likely cause** — what they probably ate or what's going on
|
| 29 |
+
- **Concern level** — 🟢 chill / 🟡 watch it / 🔴 call the vet NOW
|
| 30 |
+
- **What to do** — specific, practical, right now
|
| 31 |
+
- **Watch for** — warning signs that mean escalate
|
| 32 |
+
- **Fun fact** — because you're already standing over poop, might as well learn something
|
| 33 |
+
|
| 34 |
+
## Built-in Knowledge
|
| 35 |
+
|
| 36 |
+
- 18 common toxic foods and foreign objects with severity ratings
|
| 37 |
+
- Stool color guide (what each color means)
|
| 38 |
+
- Emergency vet checklist
|
| 39 |
+
- Bristol Stool Scale adapted for dogs
|
| 40 |
+
|
| 41 |
+
## Why
|
| 42 |
+
|
| 43 |
+
My dog Serena left me a mystery this morning. Like every morning. I built this because I was tired of Googling "dog poop green normal?" at 6am in my boxers.
|
| 44 |
+
|
| 45 |
+
Same dad. Daughter has dyslexia. Mom has dementia. Dog has opinions about grass. I just keep building tools for the creatures in my life who can't tell me what's wrong.
|
| 46 |
+
|
| 47 |
+
## The Stack
|
| 48 |
+
|
| 49 |
+
- **Model:** MiniCPM5-1B (OpenBMB) via llama.cpp — 1B parameters
|
| 50 |
+
- **Framework:** Gradio
|
| 51 |
+
- **Cloud APIs:** Zero. Your dog's medical history stays private.
|
| 52 |
+
|
| 53 |
+
## Important
|
| 54 |
+
|
| 55 |
+
This is not veterinary advice. It's a concerned dog owner's tool. When in doubt, call your vet. They've seen worse. Trust me.
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
*"Dogs poop in alignment with Earth's magnetic field. North-south. Scientists confirmed this. Nobody knows why."*
|
| 60 |
+
|
| 61 |
+
*Heuremen — Build Small Hackathon 2026*
|
app.py
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
WOOF — What's Our Output, Fido?
|
| 3 |
+
Built for the Build Small Hackathon 2026.
|
| 4 |
+
|
| 5 |
+
Upload a photo of what your dog left you this morning.
|
| 6 |
+
Find out what they ate, whether to worry, and when to call the vet.
|
| 7 |
+
|
| 8 |
+
Every dog owner has stood over a mystery pile at 6am thinking
|
| 9 |
+
"what the FUCK did you eat." This app answers that question.
|
| 10 |
+
|
| 11 |
+
Also tracks gut health over time because your dog can't tell you
|
| 12 |
+
their stomach hurts but their output can.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import gradio as gr
|
| 16 |
+
# from llama_cpp import Llama
|
| 17 |
+
from llama_cpp.llama_chat_format import MiniCPMv26ChatHandler
|
| 18 |
+
|
| 19 |
+
# --- Model ---
|
| 20 |
+
# MiniCPM-V for vision (can analyze images) — OpenBMB sponsor model
|
| 21 |
+
# Falls back to text-only analysis if vision model unavailable
|
| 22 |
+
|
| 23 |
+
MODEL_REPO = "openbmb/MiniCPM5-1B-128k-GGUF"
|
| 24 |
+
MODEL_FILE = "minicpm5-1b-128k-q4_k_m.gguf"
|
| 25 |
+
|
| 26 |
+
print("Loading model...")
|
| 27 |
+
try:
|
| 28 |
+
llm = Llama.from_pretrained(
|
| 29 |
+
repo_id=MODEL_REPO,
|
| 30 |
+
filename=MODEL_FILE,
|
| 31 |
+
n_ctx=2048,
|
| 32 |
+
n_threads=4,
|
| 33 |
+
verbose=False,
|
| 34 |
+
)
|
| 35 |
+
MODEL_LOADED = False
|
| 36 |
+
print("Model loaded.")
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"Model failed to load: {e}")
|
| 39 |
+
MODEL_LOADED = False
|
| 40 |
+
llm = None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# --- Dog Health Knowledge Base ---
|
| 44 |
+
|
| 45 |
+
TOXIC_FOODS = {
|
| 46 |
+
"chocolate": {"severity": "HIGH", "note": "Theobromine toxicity. Dark chocolate is worse. Call vet if large amount."},
|
| 47 |
+
"grapes": {"severity": "HIGH", "note": "Can cause kidney failure even in small amounts. Call vet immediately."},
|
| 48 |
+
"raisins": {"severity": "HIGH", "note": "Same as grapes. Kidney failure risk. Call vet."},
|
| 49 |
+
"xylitol": {"severity": "HIGH", "note": "Found in sugar-free gum/candy. Causes liver failure. Emergency vet NOW."},
|
| 50 |
+
"onion": {"severity": "MEDIUM", "note": "Damages red blood cells. Toxic in quantity."},
|
| 51 |
+
"garlic": {"severity": "MEDIUM", "note": "Related to onion toxicity. Small amounts less dangerous than onion."},
|
| 52 |
+
"avocado": {"severity": "LOW", "note": "Persin in skin/pit can cause vomiting. Flesh is mostly fine."},
|
| 53 |
+
"macadamia": {"severity": "MEDIUM", "note": "Causes weakness, vomiting, tremors. Usually resolves in 48h."},
|
| 54 |
+
"alcohol": {"severity": "HIGH", "note": "Even small amounts dangerous. Causes vomiting, breathing problems."},
|
| 55 |
+
"coffee": {"severity": "MEDIUM", "note": "Caffeine toxicity similar to chocolate."},
|
| 56 |
+
"cooked bones": {"severity": "HIGH", "note": "Can splinter and puncture intestines. Watch for bloody stool."},
|
| 57 |
+
"corn cob": {"severity": "HIGH", "note": "Cannot be digested. Common cause of intestinal blockage. Vet if suspected."},
|
| 58 |
+
"sock": {"severity": "HIGH", "note": "Fabric doesn't digest. Intestinal blockage risk. Vet if not passed in 24h."},
|
| 59 |
+
"toy": {"severity": "HIGH", "note": "Foreign body obstruction. Watch for vomiting, lethargy, no appetite."},
|
| 60 |
+
"grass": {"severity": "LOW", "note": "Normal. Dogs eat grass to settle their stomach or because they like it."},
|
| 61 |
+
"cat poop": {"severity": "LOW", "note": "Gross but usually harmless. Dogs do this. It's disgusting. They don't care."},
|
| 62 |
+
"dirt": {"severity": "LOW", "note": "Usually fine. Can indicate mineral deficiency if persistent."},
|
| 63 |
+
"plastic": {"severity": "MEDIUM", "note": "Small pieces may pass. Large pieces can obstruct. Monitor closely."},
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
STOOL_GUIDE = {
|
| 67 |
+
"normal": {"color": "chocolate brown", "icon": "🟢", "note": "Healthy. Good job, dog."},
|
| 68 |
+
"black": {"color": "black/tarry", "icon": "🔴", "note": "Could indicate upper GI bleeding. Vet today."},
|
| 69 |
+
"red": {"color": "red/bloody", "icon": "🔴", "note": "Lower GI bleeding or colitis. Vet today if more than streaks."},
|
| 70 |
+
"yellow": {"color": "yellow/greasy", "icon": "🟡", "note": "Possible liver, gallbladder, or pancreas issue. Vet if persistent."},
|
| 71 |
+
"orange": {"color": "orange", "icon": "🟡", "note": "Bile or food-related. Monitor. Vet if continues 2+ days."},
|
| 72 |
+
"green": {"color": "green", "icon": "🟡", "note": "Ate grass, or gallbladder issue. Usually fine if one-time."},
|
| 73 |
+
"white": {"color": "white/chalky", "icon": "🟡", "note": "Too much calcium/bone. Adjust diet. Vet if can't poop."},
|
| 74 |
+
"grey": {"color": "grey", "icon": "🔴", "note": "Possible pancreas or biliary issue. Vet visit recommended."},
|
| 75 |
+
"mucus": {"color": "mucus-covered", "icon": "🟡", "note": "Some mucus is normal. Excessive = inflammation. Vet if bloody."},
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
VET_NOW = [
|
| 79 |
+
"bloody diarrhea lasting more than a few hours",
|
| 80 |
+
"vomiting AND diarrhea together",
|
| 81 |
+
"lethargy or won't get up",
|
| 82 |
+
"bloated/distended abdomen",
|
| 83 |
+
"known ingestion of xylitol, grapes, or large amount of chocolate",
|
| 84 |
+
"foreign object suspected (sock, toy, corn cob)",
|
| 85 |
+
"hasn't eaten in 24+ hours",
|
| 86 |
+
"straining to poop with nothing coming out",
|
| 87 |
+
]
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
SYSTEM_PROMPT = """You are WOOF — a dog gut health analyzer. A dog owner is describing what they found in their dog's output (poop or vomit) or uploading a photo of it.
|
| 91 |
+
|
| 92 |
+
Your job:
|
| 93 |
+
1. Figure out what the dog likely ate or what's going on
|
| 94 |
+
2. Assess whether it's concerning
|
| 95 |
+
3. Tell them what to do in plain language
|
| 96 |
+
|
| 97 |
+
Use the Bristol Stool Scale adapted for dogs:
|
| 98 |
+
- Type 1-2: Hard pellets = constipation, needs more water/fiber
|
| 99 |
+
- Type 3-4: Firm log, easy to pick up = ideal, healthy
|
| 100 |
+
- Type 5: Soft blob, loses shape = diet change or mild issue
|
| 101 |
+
- Type 6: Mushy, no shape = concerning if persistent
|
| 102 |
+
- Type 7: Liquid = diarrhea, monitor closely
|
| 103 |
+
|
| 104 |
+
Be direct. Be practical. Use humor LIGHTLY — these people are worried about their dog but also standing over poop at 6am. Don't be clinical. Be the friend who happens to know a lot about dog health.
|
| 105 |
+
|
| 106 |
+
Return your analysis in this format:
|
| 107 |
+
WHAT YOU'RE LOOKING AT: [brief assessment]
|
| 108 |
+
LIKELY CAUSE: [what probably happened]
|
| 109 |
+
CONCERN LEVEL: [🟢 LOW / 🟡 MONITOR / 🔴 VET NOW]
|
| 110 |
+
WHAT TO DO: [specific next steps]
|
| 111 |
+
WATCH FOR: [warning signs that mean escalate]
|
| 112 |
+
FUN FACT: [one actually interesting dog gut fact to lighten the mood]"""
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def analyze_output(description, output_type, dog_size, photo):
|
| 116 |
+
"""Analyze dog output from description and/or photo."""
|
| 117 |
+
if not description and not photo:
|
| 118 |
+
return """<div class="wf-empty">Describe what you found or upload a photo. No judgment here — we've all been there at 6am.</div>"""
|
| 119 |
+
|
| 120 |
+
# Check for known toxic items in description
|
| 121 |
+
alerts = []
|
| 122 |
+
if description:
|
| 123 |
+
lower = description.lower()
|
| 124 |
+
for item, info in TOXIC_FOODS.items():
|
| 125 |
+
if item in lower:
|
| 126 |
+
alerts.append(f"⚠️ {item.upper()}: {info['note']} (Severity: {info['severity']})")
|
| 127 |
+
|
| 128 |
+
# Check for color keywords
|
| 129 |
+
color_match = None
|
| 130 |
+
if description:
|
| 131 |
+
lower = description.lower()
|
| 132 |
+
for key, info in STOOL_GUIDE.items():
|
| 133 |
+
if key in lower:
|
| 134 |
+
color_match = info
|
| 135 |
+
|
| 136 |
+
# Build context
|
| 137 |
+
context_parts = []
|
| 138 |
+
if output_type: context_parts.append(f"Type: {output_type}")
|
| 139 |
+
if dog_size: context_parts.append(f"Dog size: {dog_size}")
|
| 140 |
+
context = " | ".join(context_parts)
|
| 141 |
+
|
| 142 |
+
if MODEL_LOADED and llm:
|
| 143 |
+
try:
|
| 144 |
+
prompt = f"My dog's {output_type or 'poop'}: {description or 'see photo'}"
|
| 145 |
+
if context: prompt += f"\n({context})"
|
| 146 |
+
|
| 147 |
+
response = llm.create_chat_completion(
|
| 148 |
+
messages=[
|
| 149 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 150 |
+
{"role": "user", "content": prompt}
|
| 151 |
+
],
|
| 152 |
+
max_tokens=600,
|
| 153 |
+
temperature=0.3,
|
| 154 |
+
)
|
| 155 |
+
raw = response["choices"][0]["message"]["content"].strip()
|
| 156 |
+
return format_analysis(raw, alerts, color_match)
|
| 157 |
+
except:
|
| 158 |
+
return format_analysis(fallback_analysis(description, output_type), alerts, color_match)
|
| 159 |
+
else:
|
| 160 |
+
return format_analysis(fallback_analysis(description, output_type), alerts, color_match)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def fallback_analysis(description, output_type):
|
| 164 |
+
"""Rule-based fallback."""
|
| 165 |
+
if not description:
|
| 166 |
+
return """WHAT YOU'RE LOOKING AT: Can't tell without a description or working AI model.
|
| 167 |
+
LIKELY CAUSE: Unknown
|
| 168 |
+
CONCERN LEVEL: 🟡 MONITOR
|
| 169 |
+
WHAT TO DO: Describe what you see — color, consistency, anything unusual in it.
|
| 170 |
+
WATCH FOR: Blood, lethargy, vomiting, or loss of appetite.
|
| 171 |
+
FUN FACT: Dogs poop in alignment with Earth's magnetic field. North-south. Scientists confirmed this. Nobody knows why."""
|
| 172 |
+
|
| 173 |
+
lower = description.lower()
|
| 174 |
+
|
| 175 |
+
if any(w in lower for w in ["blood", "bloody", "red"]):
|
| 176 |
+
return """WHAT YOU'RE LOOKING AT: Blood in the output. This needs attention.
|
| 177 |
+
LIKELY CAUSE: Could be colitis, parasites, foreign object, or dietary issue. Bright red = lower GI. Dark/tarry = upper GI (more serious).
|
| 178 |
+
CONCERN LEVEL: 🔴 VET NOW if more than small streaks or if it continues.
|
| 179 |
+
WHAT TO DO: Note the color (bright red vs dark), amount, and how long it's been happening. Call your vet today.
|
| 180 |
+
WATCH FOR: Lethargy, loss of appetite, vomiting, or worsening. These make it urgent.
|
| 181 |
+
FUN FACT: A dog's GI tract is about 5x their body length. That's a lot of pipe to troubleshoot."""
|
| 182 |
+
|
| 183 |
+
elif any(w in lower for w in ["liquid", "watery", "diarrhea"]):
|
| 184 |
+
return """WHAT YOU'RE LOOKING AT: Diarrhea. Very common. Usually resolves on its own.
|
| 185 |
+
LIKELY CAUSE: Dietary change, ate something questionable, stress, or mild infection.
|
| 186 |
+
CONCERN LEVEL: 🟡 MONITOR for 24 hours.
|
| 187 |
+
WHAT TO DO: Withhold food for 12 hours (water always available). Then bland diet — boiled chicken and rice, small portions. Pumpkin puree (plain, not pie filling) helps firm things up.
|
| 188 |
+
WATCH FOR: Blood, vomiting, lethargy, or diarrhea lasting more than 48 hours. Then it's vet time.
|
| 189 |
+
FUN FACT: Dogs have about 1,700 bacterial species in their gut. Yours has about 1,000. Their microbiome is more diverse than yours."""
|
| 190 |
+
|
| 191 |
+
elif any(w in lower for w in ["grass", "green"]):
|
| 192 |
+
return """WHAT YOU'RE LOOKING AT: Grass in the output. Classic.
|
| 193 |
+
LIKELY CAUSE: Your dog ate grass. They do this. Theories: settling an upset stomach, boredom, they just like it, or instinct from wild ancestors.
|
| 194 |
+
CONCERN LEVEL: 🟢 LOW. This is normal dog behavior.
|
| 195 |
+
WHAT TO DO: Nothing unless it's happening constantly. Frequent grass eating CAN indicate nausea or dietary deficiency.
|
| 196 |
+
WATCH FOR: Vomiting after eating grass (sometimes that's the point — they're self-medicating), or doing it obsessively.
|
| 197 |
+
FUN FACT: About 80% of dogs eat grass regularly. Only about 25% vomit after. The rest are just vibing."""
|
| 198 |
+
|
| 199 |
+
else:
|
| 200 |
+
return """WHAT YOU'RE LOOKING AT: Need more detail for a specific read.
|
| 201 |
+
LIKELY CAUSE: Hard to say without more info.
|
| 202 |
+
CONCERN LEVEL: 🟡 MONITOR
|
| 203 |
+
WHAT TO DO: Note the color, consistency (hard pellets? soft? liquid?), any foreign material visible, and frequency. The more detail, the better the read.
|
| 204 |
+
WATCH FOR: Blood, mucus, foreign objects, very dark/tarry color, or any change in your dog's behavior.
|
| 205 |
+
FUN FACT: The average dog poops 1-5 times per day. If your dog suddenly changes frequency, that tells you something even if the poop looks normal."""
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def format_analysis(text, alerts, color_match):
|
| 209 |
+
"""Format into the WOOF output."""
|
| 210 |
+
alert_html = ""
|
| 211 |
+
if alerts:
|
| 212 |
+
alert_items = "".join(f'<div class="wf-alert-item">{a}</div>' for a in alerts)
|
| 213 |
+
alert_html = f'<div class="wf-alerts">{alert_items}</div>'
|
| 214 |
+
|
| 215 |
+
color_html = ""
|
| 216 |
+
if color_match:
|
| 217 |
+
color_html = f'<div class="wf-color">{color_match["icon"]} Color match: {color_match["color"]} — {color_match["note"]}</div>'
|
| 218 |
+
|
| 219 |
+
# Parse sections from response
|
| 220 |
+
sections = {}
|
| 221 |
+
current = None
|
| 222 |
+
for line in text.split('\n'):
|
| 223 |
+
upper = line.strip().upper()
|
| 224 |
+
if "LOOKING AT" in upper: current = "looking"; continue
|
| 225 |
+
elif "LIKELY CAUSE" in upper: current = "cause"; continue
|
| 226 |
+
elif "CONCERN LEVEL" in upper: current = "concern"; continue
|
| 227 |
+
elif "WHAT TO DO" in upper: current = "do"; continue
|
| 228 |
+
elif "WATCH FOR" in upper: current = "watch"; continue
|
| 229 |
+
elif "FUN FACT" in upper: current = "fun"; continue
|
| 230 |
+
if current and line.strip():
|
| 231 |
+
sections[current] = sections.get(current, "") + line.strip() + " "
|
| 232 |
+
|
| 233 |
+
concern = sections.get("concern", "🟡 MONITOR").strip()
|
| 234 |
+
concern_class = "wf-red" if "🔴" in concern or "VET" in concern.upper() else "wf-yellow" if "🟡" in concern else "wf-green"
|
| 235 |
+
|
| 236 |
+
html = f"""
|
| 237 |
+
<div class="wf-container">
|
| 238 |
+
{alert_html}
|
| 239 |
+
{color_html}
|
| 240 |
+
|
| 241 |
+
<div class="wf-concern {concern_class}">
|
| 242 |
+
{concern}
|
| 243 |
+
</div>
|
| 244 |
+
|
| 245 |
+
<div class="wf-section">
|
| 246 |
+
<div class="wf-icon">🔍</div>
|
| 247 |
+
<div class="wf-content">
|
| 248 |
+
<div class="wf-title">What you're looking at</div>
|
| 249 |
+
<div class="wf-body">{sections.get('looking', 'Analyzing...')}</div>
|
| 250 |
+
</div>
|
| 251 |
+
</div>
|
| 252 |
+
|
| 253 |
+
<div class="wf-section">
|
| 254 |
+
<div class="wf-icon">🤔</div>
|
| 255 |
+
<div class="wf-content">
|
| 256 |
+
<div class="wf-title">Likely cause</div>
|
| 257 |
+
<div class="wf-body">{sections.get('cause', 'Need more info.')}</div>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
|
| 261 |
+
<div class="wf-section">
|
| 262 |
+
<div class="wf-icon">📋</div>
|
| 263 |
+
<div class="wf-content">
|
| 264 |
+
<div class="wf-title">What to do</div>
|
| 265 |
+
<div class="wf-body">{sections.get('do', 'Monitor and note any changes.')}</div>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
|
| 269 |
+
<div class="wf-section">
|
| 270 |
+
<div class="wf-icon">👀</div>
|
| 271 |
+
<div class="wf-content">
|
| 272 |
+
<div class="wf-title">Watch for</div>
|
| 273 |
+
<div class="wf-body">{sections.get('watch', 'Blood, lethargy, loss of appetite.')}</div>
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
|
| 277 |
+
<div class="wf-section wf-fun">
|
| 278 |
+
<div class="wf-icon">🐕</div>
|
| 279 |
+
<div class="wf-content">
|
| 280 |
+
<div class="wf-body">{sections.get('fun', 'Dogs can smell about 10,000 times better than humans. Yes, even THAT.')}</div>
|
| 281 |
+
</div>
|
| 282 |
+
</div>
|
| 283 |
+
|
| 284 |
+
<div class="wf-vet-box">
|
| 285 |
+
<div class="wf-vet-title">Call the vet immediately if:</div>
|
| 286 |
+
<ul class="wf-vet-list">
|
| 287 |
+
<li>Bloody diarrhea lasting more than a few hours</li>
|
| 288 |
+
<li>Vomiting AND diarrhea together</li>
|
| 289 |
+
<li>Bloated or distended abdomen</li>
|
| 290 |
+
<li>Lethargy or won't get up</li>
|
| 291 |
+
<li>Known ingestion of xylitol, grapes, or chocolate</li>
|
| 292 |
+
<li>Straining with nothing coming out</li>
|
| 293 |
+
</ul>
|
| 294 |
+
</div>
|
| 295 |
+
</div>
|
| 296 |
+
"""
|
| 297 |
+
return html
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
# --- CSS ---
|
| 301 |
+
CUSTOM_CSS = """
|
| 302 |
+
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;600;700&display=swap');
|
| 303 |
+
|
| 304 |
+
.wf-empty {
|
| 305 |
+
text-align: center; padding: 40px; color: #888;
|
| 306 |
+
font-family: 'Quicksand', sans-serif; font-size: 1.1em;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
.wf-container {
|
| 310 |
+
font-family: 'Quicksand', sans-serif;
|
| 311 |
+
background: #FAFAF7; border-radius: 16px; padding: 24px;
|
| 312 |
+
max-width: 700px; margin: 0 auto;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.wf-alerts {
|
| 316 |
+
background: #FFF3F0; border: 1px solid #E07A5F; border-radius: 10px;
|
| 317 |
+
padding: 14px; margin-bottom: 16px;
|
| 318 |
+
}
|
| 319 |
+
.wf-alert-item { color: #C0392B; font-weight: 600; padding: 4px 0; font-size: 0.95em; }
|
| 320 |
+
|
| 321 |
+
.wf-color {
|
| 322 |
+
background: #F5F5F0; border-radius: 8px; padding: 10px 14px;
|
| 323 |
+
margin-bottom: 16px; font-size: 0.9em; color: #666;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
.wf-concern {
|
| 327 |
+
text-align: center; font-size: 1.3em; font-weight: 700;
|
| 328 |
+
padding: 16px; border-radius: 12px; margin-bottom: 20px;
|
| 329 |
+
}
|
| 330 |
+
.wf-green { background: #E8F5E9; color: #2E7D32; }
|
| 331 |
+
.wf-yellow { background: #FFF8E1; color: #F57F17; }
|
| 332 |
+
.wf-red { background: #FFEBEE; color: #C62828; }
|
| 333 |
+
|
| 334 |
+
.wf-section {
|
| 335 |
+
display: flex; gap: 14px; padding: 14px 0;
|
| 336 |
+
border-bottom: 1px solid #EEECE8; align-items: flex-start;
|
| 337 |
+
}
|
| 338 |
+
.wf-section:last-of-type { border-bottom: none; }
|
| 339 |
+
.wf-icon { font-size: 1.4em; min-width: 32px; text-align: center; }
|
| 340 |
+
.wf-title {
|
| 341 |
+
font-size: 0.75em; font-weight: 700; color: #999;
|
| 342 |
+
text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 4px;
|
| 343 |
+
}
|
| 344 |
+
.wf-body { font-size: 0.95em; line-height: 1.7; color: #333; }
|
| 345 |
+
|
| 346 |
+
.wf-fun { border-top: 1px dashed #DDD; margin-top: 8px; padding-top: 12px; }
|
| 347 |
+
.wf-fun .wf-body { color: #888; font-style: italic; font-size: 0.85em; }
|
| 348 |
+
|
| 349 |
+
.wf-vet-box {
|
| 350 |
+
background: #FFF3F0; border-radius: 10px; padding: 16px;
|
| 351 |
+
margin-top: 20px; border-left: 4px solid #E07A5F;
|
| 352 |
+
}
|
| 353 |
+
.wf-vet-title { font-weight: 700; color: #C0392B; font-size: 0.85em; margin-bottom: 8px; }
|
| 354 |
+
.wf-vet-list { font-size: 0.85em; color: #666; padding-left: 20px; line-height: 1.8; }
|
| 355 |
+
|
| 356 |
+
.gradio-container {
|
| 357 |
+
background: #F5F4F0 !important; max-width: 800px !important; margin: 0 auto !important;
|
| 358 |
+
}
|
| 359 |
+
.gr-button-primary {
|
| 360 |
+
background: #8B6914 !important; border: none !important;
|
| 361 |
+
font-family: 'Quicksand', sans-serif !important; font-size: 1.05em !important;
|
| 362 |
+
padding: 12px 36px !important; border-radius: 12px !important;
|
| 363 |
+
}
|
| 364 |
+
.gr-button-primary:hover { background: #7A5B10 !important; }
|
| 365 |
+
footer { display: none !important; }
|
| 366 |
+
"""
|
| 367 |
+
|
| 368 |
+
# --- Examples ---
|
| 369 |
+
EXAMPLES = [
|
| 370 |
+
["Found something bright green and mushy this morning. She was in the yard a lot yesterday.", "Poop", "Medium (20-50 lbs)"],
|
| 371 |
+
["He threw up what looks like a piece of a sock. Only half of it.", "Vomit", "Large (50+ lbs)"],
|
| 372 |
+
["Liquid brown diarrhea three times since last night. She ate some table scraps.", "Poop", "Small (under 20 lbs)"],
|
| 373 |
+
["Normal looking but there's a weird white thing in it that moves. Like a grain of rice.", "Poop", "Medium (20-50 lbs)"],
|
| 374 |
+
["She got into the Halloween candy. Definitely ate some chocolate. Maybe 3-4 pieces.", "Vomit", "Small (under 20 lbs)"],
|
| 375 |
+
["Black and tarry. Smells worse than usual which is saying something.", "Poop", "Large (50+ lbs)"],
|
| 376 |
+
]
|
| 377 |
+
|
| 378 |
+
# --- App ---
|
| 379 |
+
with gr.Blocks(css=CUSTOM_CSS, title="WOOF", theme=gr.themes.Soft()) as app:
|
| 380 |
+
|
| 381 |
+
gr.HTML("""
|
| 382 |
+
<div style="text-align:center; padding: 20px 0 8px;">
|
| 383 |
+
<h1 style="font-family: 'Quicksand', sans-serif; color: #333; font-size: 2.4em; font-weight: 700;">
|
| 384 |
+
🐕 WOOF
|
| 385 |
+
</h1>
|
| 386 |
+
<p style="font-family: 'Quicksand', sans-serif; color: #999; font-size: 1.0em;">
|
| 387 |
+
What's Our Output, Fido?
|
| 388 |
+
</p>
|
| 389 |
+
<p style="font-family: 'Quicksand', sans-serif; color: #BBB; font-size: 0.8em; max-width: 450px; margin: 4px auto 0;">
|
| 390 |
+
Every dog owner has stood over a mystery pile at 6am. This app answers the question you're too embarrassed to Google.
|
| 391 |
+
</p>
|
| 392 |
+
</div>
|
| 393 |
+
""")
|
| 394 |
+
|
| 395 |
+
description = gr.Textbox(
|
| 396 |
+
label="What did you find?",
|
| 397 |
+
placeholder="Describe it. Color, consistency, anything weird in it. We've seen worse, promise.",
|
| 398 |
+
lines=3,
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
with gr.Row():
|
| 402 |
+
output_type = gr.Radio(
|
| 403 |
+
choices=["Poop", "Vomit", "Both (oh no)"],
|
| 404 |
+
value="Poop",
|
| 405 |
+
label="Type",
|
| 406 |
+
)
|
| 407 |
+
dog_size = gr.Radio(
|
| 408 |
+
choices=["Small (under 20 lbs)", "Medium (20-50 lbs)", "Large (50+ lbs)"],
|
| 409 |
+
value="Medium (20-50 lbs)",
|
| 410 |
+
label="Dog size",
|
| 411 |
+
)
|
| 412 |
+
|
| 413 |
+
photo = gr.Image(label="Upload a photo (optional — yes really)", type="pil")
|
| 414 |
+
|
| 415 |
+
btn = gr.Button("Analyze the Situation", variant="primary", size="lg")
|
| 416 |
+
output = gr.HTML()
|
| 417 |
+
|
| 418 |
+
btn.click(
|
| 419 |
+
fn=analyze_output,
|
| 420 |
+
inputs=[description, output_type, dog_size, photo],
|
| 421 |
+
outputs=[output],
|
| 422 |
+
)
|
| 423 |
+
|
| 424 |
+
gr.Examples(
|
| 425 |
+
examples=EXAMPLES,
|
| 426 |
+
inputs=[description, output_type, dog_size],
|
| 427 |
+
label="Common mysteries",
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
gr.HTML("""
|
| 431 |
+
<div style="text-align:center; padding: 16px 0 4px; color: #CCC; font-size: 0.7em; font-family: 'Quicksand', sans-serif; line-height: 1.8;">
|
| 432 |
+
Not a substitute for veterinary care. When in doubt, call your vet.<br>
|
| 433 |
+
1B parameters. No internet needed. Your dog's business stays private.<br>
|
| 434 |
+
<span style="color:#8B6914;">Heuremen — Build Small Hackathon 2026</span>
|
| 435 |
+
</div>
|
| 436 |
+
""")
|
| 437 |
+
|
| 438 |
+
if __name__ == "__main__":
|
| 439 |
+
app.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0
|
| 2 |
+
# llama-cpp-python
|
| 3 |
+
Pillow
|