Wayfinder6 commited on
Commit
fe1bb8d
·
1 Parent(s): d324338

WOOF - what did my dog eat. built this after serena left me a mystery this morning

Browse files
Files changed (3) hide show
  1. README.md +51 -11
  2. app.py +439 -0
  3. requirements.txt +3 -0
README.md CHANGED
@@ -1,21 +1,61 @@
1
  ---
2
  title: WOOF
3
- emoji: 🔥
4
  colorFrom: yellow
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.17.3
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
- short_description: 'Woof tells you the output of your dog. '
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
- import gradio as gr
16
 
17
- def greet(name):
18
- return "Hello " + name + "!!"
19
 
20
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
21
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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