HopePet / app.py
mayadeeb08's picture
Update app.py
ce7f498 verified
Raw
History Blame Contribute Delete
57.3 kB
import os
import html
import uuid
import tempfile
from datetime import datetime
from urllib.parse import quote
import numpy as np
import pandas as pd
import gradio as gr
from sentence_transformers import SentenceTransformer
from transformers import pipeline
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate,
Paragraph,
Spacer,
Table,
TableStyle,
PageBreak
)
# ============================================================
# HOPEPET AI - SETTINGS
# ============================================================
APP_NAME = "HopePet AI"
DATASET_PATH = "HopePet_dataset.csv"
HF_DATASET_REPO = os.getenv("HF_DATASET_REPO", "").strip()
EMBEDDINGS_PATH = "HopePet_embeddings.npy"
WINNING_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
GENERATION_MODEL = "google/flan-t5-base"
TOP_K = 3
# ============================================================
# LOAD DATA
# ============================================================
def load_data():
if HF_DATASET_REPO:
from datasets import load_dataset
dataset = load_dataset(HF_DATASET_REPO)
split_name = "train" if "train" in dataset else list(dataset.keys())[0]
data = dataset[split_name].to_pandas()
else:
if not os.path.exists(DATASET_PATH):
raise FileNotFoundError(
f"Dataset file was not found: {DATASET_PATH}. "
"Please upload HopePet_dataset.csv to the Space."
)
data = pd.read_csv(DATASET_PATH)
data.columns = data.columns.str.strip()
optional_columns = ["secondary_symptoms", "pain_signs", "emergency_signs"]
for col in optional_columns:
if col in data.columns:
data[col] = data[col].fillna("None")
for col in data.select_dtypes(include="object").columns:
data[col] = data[col].fillna("Unknown").astype(str).str.strip()
if "retrieval_text" not in data.columns:
raise ValueError("The dataset must contain a column named 'retrieval_text'.")
return data
df = load_data()
# ============================================================
# LOAD MODELS
# ============================================================
embedder = SentenceTransformer(WINNING_EMBEDDING_MODEL)
try:
generator = pipeline(
"text2text-generation",
model=GENERATION_MODEL
)
except Exception:
generator = None
# ============================================================
# LOAD OR CREATE EMBEDDINGS
# ============================================================
def load_or_create_embeddings():
if os.path.exists(EMBEDDINGS_PATH):
embeddings = np.load(EMBEDDINGS_PATH)
if len(embeddings) == len(df):
return embeddings
texts = df["retrieval_text"].astype(str).tolist()
embeddings = embedder.encode(
texts,
batch_size=64,
show_progress_bar=True,
normalize_embeddings=True
)
embeddings = np.array(embeddings)
try:
np.save(EMBEDDINGS_PATH, embeddings)
except Exception:
pass
return embeddings
dataset_embeddings = load_or_create_embeddings()
# ============================================================
# BASIC HELPERS
# ============================================================
def h(value):
return html.escape(str(value))
def clean_value(value):
if value is None:
return "Unknown"
if isinstance(value, list):
cleaned = [
str(v).strip()
for v in value
if str(v).strip().lower() not in ["", "none", "nan"]
]
return ", ".join(cleaned) if cleaned else "None"
value = str(value).strip()
return value if value else "Unknown"
def get_choices(column_name, fallback, max_values=40):
if column_name not in df.columns:
return fallback
values = (
df[column_name]
.dropna()
.astype(str)
.str.strip()
.unique()
.tolist()
)
values = [
v for v in values
if v and v.lower() not in ["nan", "none", "unknown"]
]
values = sorted(values)[:max_values]
final_values = fallback + values
final_values = list(dict.fromkeys(final_values))
return final_values
def sentence_case_pet(pet_type, pet_age_group):
return f"{str(pet_age_group).lower()} {str(pet_type).lower()}"
# ============================================================
# RETRIEVAL SYSTEM
# ============================================================
def build_user_case_text(
pet_type,
pet_age_group,
pet_sex,
breed_size,
vaccination_status,
environment,
medical_background,
recent_change,
main_symptom,
secondary_symptoms,
symptom_duration,
appetite_status,
water_intake,
energy_level,
pain_signs,
emergency_signs,
previous_occurrence,
user_goal,
free_text
):
text = f"""
Pet type: {pet_type}.
Age group: {pet_age_group}.
Sex: {pet_sex}.
Breed size: {breed_size}.
Vaccination status: {vaccination_status}.
Environment: {environment}.
Medical background: {medical_background}.
Recent change: {recent_change}.
Main symptom: {main_symptom}.
Secondary symptoms: {clean_value(secondary_symptoms)}.
Symptom duration: {symptom_duration}.
Appetite status: {appetite_status}.
Water intake: {water_intake}.
Energy level: {energy_level}.
Pain signs: {clean_value(pain_signs)}.
Emergency signs: {clean_value(emergency_signs)}.
Previous occurrence: {previous_occurrence}.
User goal: {user_goal}.
User description: {free_text}.
"""
return " ".join(text.split())
def retrieve_similar_cases(query_text, top_k=3):
query_embedding = embedder.encode(
[query_text],
normalize_embeddings=True
)[0]
similarities = np.dot(dataset_embeddings, query_embedding)
top_indices = similarities.argsort()[::-1][:top_k]
similar_cases = df.iloc[top_indices].copy()
similar_cases["similarity_score"] = similarities[top_indices]
return similar_cases
def weighted_vote(similar_cases, column_name):
if column_name not in similar_cases.columns:
return "Unknown"
scores = {}
for _, row in similar_cases.iterrows():
value = str(row[column_name])
score = float(row["similarity_score"])
scores[value] = scores.get(value, 0) + score
return max(scores, key=scores.get)
def get_first_value(similar_cases, column_name, fallback="Not available"):
if column_name in similar_cases.columns:
value = similar_cases.iloc[0][column_name]
if pd.notna(value):
return str(value)
return fallback
# ============================================================
# RISK AND URGENCY LOGIC
# ============================================================
def emergency_override(main_symptom, emergency_signs, energy_level, pain_signs):
combined_text = " ".join([
clean_value(main_symptom).lower(),
clean_value(emergency_signs).lower(),
clean_value(energy_level).lower(),
clean_value(pain_signs).lower()
])
emergency_keywords = [
"difficulty breathing",
"seizure",
"collapse",
"uncontrolled bleeding",
"blue or pale gums",
"cannot stand",
"severe weakness"
]
return any(keyword in combined_text for keyword in emergency_keywords)
def calculate_risk_score(
pet_age_group,
main_symptom,
appetite_status,
water_intake,
energy_level,
pain_signs,
emergency_signs,
symptom_duration
):
risk = 0
main_symptom_text = clean_value(main_symptom).lower()
appetite_text = clean_value(appetite_status).lower()
water_text = clean_value(water_intake).lower()
energy_text = clean_value(energy_level).lower()
pain_text = clean_value(pain_signs).lower()
emergency_text = clean_value(emergency_signs).lower()
duration_text = clean_value(symptom_duration).lower()
if pet_age_group in ["Senior", "Puppy", "Kitten"]:
risk += 1
if "difficulty breathing" in main_symptom_text:
risk += 4
elif "bleeding" in main_symptom_text:
risk += 2
elif "not eating" in main_symptom_text:
risk += 1
elif "vomiting" in main_symptom_text or "diarrhea" in main_symptom_text:
risk += 1
elif "limping" in main_symptom_text:
risk += 1
if "not eating" in appetite_text:
risk += 2
elif "reduced" in appetite_text:
risk += 1
if "not drinking" in water_text:
risk += 2
elif "less" in water_text or "more" in water_text:
risk += 1
if "cannot stand" in energy_text:
risk += 4
elif "very tired" in energy_text:
risk += 2
elif "slightly tired" in energy_text or "restless" in energy_text:
risk += 1
if pain_text not in ["none", "unknown", "", "nan"]:
risk += 2
if emergency_text not in ["none", "unknown", "", "nan"]:
risk += 3
if "more than a week" in duration_text:
risk += 2
elif "more than 3 days" in duration_text:
risk += 1
return min(risk, 10)
def urgency_from_risk_score(risk_score, main_symptom, emergency_signs, energy_level, pain_signs):
if emergency_override(main_symptom, emergency_signs, energy_level, pain_signs):
return "Emergency", max(risk_score, 7)
if risk_score <= 2:
return "Low", risk_score
elif risk_score <= 4:
return "Medium", risk_score
elif risk_score <= 6:
return "High", risk_score
else:
return "Emergency", risk_score
def urgency_color(urgency):
colors_map = {
"Low": "#B8E0D2",
"Medium": "#FFD6A5",
"High": "#F7A8B8",
"Emergency": "#FF8FAB"
}
return colors_map.get(urgency, "#BDE0FE")
def urgency_icon(urgency):
icons = {
"Low": "🟢",
"Medium": "🟡",
"High": "🟠",
"Emergency": "🔴"
}
return icons.get(urgency, "🐾")
# ============================================================
# ACTION PLAN
# ============================================================
def build_situation_summary(
pet_type,
pet_age_group,
main_symptom,
secondary_symptoms,
symptom_duration,
appetite_status,
water_intake,
energy_level,
emergency_signs
):
pet_label = sentence_case_pet(pet_type, pet_age_group)
secondary = clean_value(secondary_symptoms)
emergency = clean_value(emergency_signs)
summary = (
f"Your {pet_label} is showing {str(main_symptom).lower()} for {str(symptom_duration).lower()}. "
f"Appetite is reported as {str(appetite_status).lower()}, water intake as {str(water_intake).lower()}, "
f"and energy level as {str(energy_level).lower()}."
)
if secondary != "None":
summary += f" Additional signs include: {secondary}."
if emergency != "None":
summary += f" Emergency signs were reported: {emergency}."
else:
summary += " No emergency signs were reported."
return summary
def build_do_now_steps(urgency, pet_type, main_symptom, appetite_status):
pet_word = str(pet_type).lower()
symptom_text = str(main_symptom).lower()
steps = [
f"Keep your {pet_word} in a calm, comfortable, and safe place.",
"Offer fresh water and monitor whether your pet drinks normally.",
"Watch for changes in breathing, energy, appetite, pain, vomiting, diarrhea, or behavior."
]
if "not eating" in symptom_text or "not eating" in str(appetite_status).lower():
steps.append("Do not force food; monitor appetite and contact a veterinarian if not eating continues.")
if "shaking" in symptom_text or "anxiety" in symptom_text or "hiding" in symptom_text:
steps.append("Reduce noise and stress, and allow your pet to rest in a quiet area.")
if "limping" in symptom_text:
steps.append("Limit running, jumping, and stairs until the pet is checked or improves.")
if "scratching" in symptom_text:
steps.append("Prevent excessive scratching and check the skin for redness, wounds, or swelling.")
if urgency == "High":
steps.append("Contact a veterinarian today for professional guidance.")
elif urgency == "Emergency":
steps = [
"Seek emergency veterinary care immediately.",
"Keep your pet as calm and still as possible while arranging urgent care.",
"Do not wait to see if severe emergency signs improve on their own."
]
return steps
def build_avoid_steps(urgency):
steps = [
"Do not give human medication unless a veterinarian specifically tells you to.",
"Do not ignore worsening symptoms or sudden changes in breathing, energy, or behavior.",
"Do not force food or water if your pet is struggling, vomiting repeatedly, or seems very weak."
]
if urgency in ["High", "Emergency"]:
steps.append("Do not delay contacting a veterinarian if the condition looks serious or is getting worse.")
return steps
def build_vet_contact_message(urgency):
if urgency == "Low":
return "Contact a veterinarian if the symptoms continue, become worse, or if new warning signs appear."
if urgency == "Medium":
return "Contact a veterinarian if there is no improvement soon, if symptoms continue, or if your pet becomes weaker."
if urgency == "High":
return "Contact a veterinarian today. The signs are concerning enough to need professional guidance."
return "Contact an emergency veterinarian or veterinary clinic immediately."
def build_why_message(category, risk_score, similar_cases):
top_symptom = get_first_value(similar_cases, "main_symptom", "similar symptoms")
top_category = get_first_value(similar_cases, "problem_category", category)
top_urgency = get_first_value(similar_cases, "urgency_level", "Unknown")
return (
f"HopePet AI combined the structured information you entered with a rule-based risk score "
f"and retrieved similar cases from the dataset. The closest cases involved '{top_symptom}', "
f"with dataset category '{top_category}' and dataset urgency '{top_urgency}'. "
f"The final urgency shown here is based mainly on the calculated risk score ({risk_score}/10) "
f"and emergency safety logic."
)
def generate_short_ai_note(query_text, urgency, category):
if generator is None:
return ""
prompt = f"""
You are HopePet AI, a responsible pet-care assistant.
Do not diagnose. Do not replace a veterinarian.
Write two short, calm sentences for the pet owner.
Mention the urgency level and safe next step.
Case:
{query_text}
Category: {category}
Urgency: {urgency}
"""
try:
generated = generator(
prompt,
max_new_tokens=80,
do_sample=False
)[0]["generated_text"].strip()
if len(generated) < 25:
return ""
return generated
except Exception:
return ""
def create_action_plan_html(action_plan):
urgency = action_plan["urgency"]
color = urgency_color(urgency)
icon = urgency_icon(urgency)
do_now_html = "".join([f"<li>{h(step)}</li>" for step in action_plan["do_now"]])
avoid_html = "".join([f"<li>{h(step)}</li>" for step in action_plan["avoid"]])
ai_note_block = ""
if action_plan["ai_note"]:
ai_note_block = f"""
<div class="plan-box soft-purple">
<div class="plan-label">✨ AI-Written Note</div>
<p>{h(action_plan["ai_note"])}</p>
</div>
"""
return f"""
<div class="action-plan-card">
<div class="plan-header">
<div class="plan-kicker">Personalized pet-care guidance</div>
<h2>🐾 HopePet AI Action Plan</h2>
<p>Clear first-step guidance based on your pet's details, similar cases, embeddings, and safety logic.</p>
</div>
<div class="urgency-panel" style="background:{color};">
<div class="urgency-main">{icon} {h(urgency)}</div>
<div class="urgency-sub">Risk Score: {h(action_plan["risk_score"])}/10</div>
</div>
<div class="plan-box">
<div class="plan-label">1. Situation Summary</div>
<p>{h(action_plan["situation_summary"])}</p>
</div>
<div class="plan-grid">
<div class="plan-box">
<div class="plan-label">2. Estimated Category</div>
<p><b>{h(action_plan["category"])}</b></p>
</div>
<div class="plan-box">
<div class="plan-label">3. Recommended Next Step</div>
<p>{h(action_plan["vet_contact"])}</p>
</div>
</div>
<div class="plan-box soft-blue">
<div class="plan-label">4. What You Should Do Now</div>
<ul>{do_now_html}</ul>
</div>
<div class="plan-box soft-pink">
<div class="plan-label">5. What You Should Avoid</div>
<ul>{avoid_html}</ul>
</div>
<div class="plan-box warning-plan">
<div class="plan-label">6. When to Contact a Vet</div>
<p>{h(action_plan["vet_contact"])}</p>
</div>
<div class="plan-box">
<div class="plan-label">7. Why HopePet Suggests This</div>
<p>{h(action_plan["why"])}</p>
</div>
{ai_note_block}
<div class="disclaimer-card">
HopePet AI does not provide a medical diagnosis and does not replace a veterinarian.
</div>
</div>
"""
# ============================================================
# SIMILAR CASES DISPLAY
# ============================================================
def create_similar_cases_html(similar_cases):
cards = ""
for i, (_, row) in enumerate(similar_cases.iterrows(), start=1):
pet = f"{row.get('pet_age_group', 'Unknown')} {row.get('pet_type', 'Unknown')}"
similarity = float(row.get("similarity_score", 0)) * 100
cards += f"""
<div class="similar-case-card">
<div class="similar-top">
<div>
<div class="case-number">Similar Case {i}</div>
<h3>{h(pet)}</h3>
</div>
<div class="similar-score">{similarity:.1f}% match</div>
</div>
<div class="case-grid">
<div><b>Main symptom:</b><br>{h(row.get("main_symptom", "Unknown"))}</div>
<div><b>Secondary symptoms:</b><br>{h(row.get("secondary_symptoms", "None"))}</div>
<div><b>Duration:</b><br>{h(row.get("symptom_duration", "Unknown"))}</div>
<div><b>Appetite:</b><br>{h(row.get("appetite_status", "Unknown"))}</div>
<div><b>Water intake:</b><br>{h(row.get("water_intake", "Unknown"))}</div>
<div><b>Energy level:</b><br>{h(row.get("energy_level", "Unknown"))}</div>
<div><b>Dataset category:</b><br>{h(row.get("problem_category", "Unknown"))}</div>
<div><b>Dataset urgency:</b><br>{h(row.get("urgency_level", "Unknown"))}</div>
</div>
<div class="case-section">
<b>Recommended next step:</b>
<p>{h(row.get("recommended_next_step", "Not available"))}</p>
</div>
<div class="case-section">
<b>Triage reason:</b>
<p>{h(row.get("triage_reason", "Not available"))}</p>
</div>
<div class="case-section">
<b>Safe first steps:</b>
<p>{h(row.get("safe_first_steps", "Not available"))}</p>
</div>
</div>
"""
return f"""
<div class="similar-wrapper">
<h2>🔍 Top 3 Similar Cases</h2>
<p class="muted">
These are the closest synthetic cases found by the recommendation system.
They are shown as case snapshots instead of only showing an ID.
</p>
{cards}
</div>
"""
# ============================================================
# PDF REPORT
# ============================================================
def pdf_paragraph(text, style):
safe_text = html.escape(str(text)).replace("\n", "<br/>")
return Paragraph(safe_text, style)
def create_pdf_report(user_inputs, action_plan, similar_cases):
filename = f"HopePet_AI_Report_{uuid.uuid4().hex[:8]}.pdf"
pdf_path = os.path.join(tempfile.gettempdir(), filename)
doc = SimpleDocTemplate(
pdf_path,
pagesize=A4,
rightMargin=1.5 * cm,
leftMargin=1.5 * cm,
topMargin=1.4 * cm,
bottomMargin=1.4 * cm
)
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
name="HopeTitle",
parent=styles["Title"],
fontName="Helvetica-Bold",
fontSize=24,
textColor=colors.HexColor("#1F2940"),
spaceAfter=12
))
styles.add(ParagraphStyle(
name="HopeHeading",
parent=styles["Heading2"],
fontName="Helvetica-Bold",
fontSize=15,
textColor=colors.HexColor("#6C4A78"),
spaceBefore=14,
spaceAfter=8
))
styles.add(ParagraphStyle(
name="HopeBody",
parent=styles["BodyText"],
fontName="Helvetica",
fontSize=10.5,
leading=15,
textColor=colors.HexColor("#333333"),
spaceAfter=6
))
styles.add(ParagraphStyle(
name="HopeSmall",
parent=styles["BodyText"],
fontName="Helvetica",
fontSize=9,
leading=13,
textColor=colors.HexColor("#555555")
))
story = []
story.append(pdf_paragraph("HopePet AI Pet Care Report", styles["HopeTitle"]))
story.append(pdf_paragraph(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M')}", styles["HopeSmall"]))
story.append(pdf_paragraph(
"This report summarizes the information entered by the user, the HopePet AI Action Plan, and the most similar retrieved cases.",
styles["HopeBody"]
))
story.append(Spacer(1, 10))
story.append(pdf_paragraph("1. Pet Profile", styles["HopeHeading"]))
profile_items = [
["Pet type", user_inputs["pet_type"]],
["Age group", user_inputs["pet_age_group"]],
["Sex", user_inputs["pet_sex"]],
["Breed size", user_inputs["breed_size"]],
["Vaccination status", user_inputs["vaccination_status"]],
["Environment", user_inputs["environment"]],
["Medical background", user_inputs["medical_background"]],
["Recent change", user_inputs["recent_change"]],
]
profile_table = Table(profile_items, colWidths=[5 * cm, 10 * cm])
profile_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#EAF6FF")),
("BACKGROUND", (1, 0), (1, -1), colors.HexColor("#FFF9FB")),
("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#DDDDDD")),
("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("RIGHTPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
]))
story.append(profile_table)
story.append(pdf_paragraph("2. Symptoms and Context", styles["HopeHeading"]))
symptom_items = [
["Main symptom", user_inputs["main_symptom"]],
["Secondary symptoms", clean_value(user_inputs["secondary_symptoms"])],
["Symptom duration", user_inputs["symptom_duration"]],
["Appetite status", user_inputs["appetite_status"]],
["Water intake", user_inputs["water_intake"]],
["Energy level", user_inputs["energy_level"]],
["Pain signs", clean_value(user_inputs["pain_signs"])],
["Emergency signs", clean_value(user_inputs["emergency_signs"])],
["Previous occurrence", user_inputs["previous_occurrence"]],
["User goal", user_inputs["user_goal"]],
["Free text description", user_inputs["free_text"] if user_inputs["free_text"] else "None"],
]
symptom_table = Table(symptom_items, colWidths=[5 * cm, 10 * cm])
symptom_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#FDEAF1")),
("BACKGROUND", (1, 0), (1, -1), colors.HexColor("#FFFFFF")),
("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#DDDDDD")),
("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("RIGHTPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
]))
story.append(symptom_table)
story.append(pdf_paragraph("3. HopePet AI Action Plan", styles["HopeHeading"]))
story.append(pdf_paragraph(f"Situation summary: {action_plan['situation_summary']}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Estimated category: {action_plan['category']}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Estimated urgency: {action_plan['urgency']}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Risk score: {action_plan['risk_score']}/10", styles["HopeBody"]))
story.append(pdf_paragraph("What you should do now:", styles["HopeBody"]))
for step in action_plan["do_now"]:
story.append(pdf_paragraph(f"- {step}", styles["HopeBody"]))
story.append(pdf_paragraph("What you should avoid:", styles["HopeBody"]))
for step in action_plan["avoid"]:
story.append(pdf_paragraph(f"- {step}", styles["HopeBody"]))
story.append(pdf_paragraph(f"When to contact a vet: {action_plan['vet_contact']}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Why HopePet suggests this: {action_plan['why']}", styles["HopeBody"]))
if action_plan["ai_note"]:
story.append(pdf_paragraph(f"AI-written note: {action_plan['ai_note']}", styles["HopeBody"]))
story.append(PageBreak())
story.append(pdf_paragraph("4. Similar Retrieved Cases", styles["HopeHeading"]))
for i, (_, row) in enumerate(similar_cases.iterrows(), start=1):
similarity = float(row.get("similarity_score", 0)) * 100
story.append(pdf_paragraph(f"Similar Case {i} - {similarity:.1f}% match", styles["HopeHeading"]))
story.append(pdf_paragraph(
f"Pet: {row.get('pet_age_group', 'Unknown')} {row.get('pet_type', 'Unknown')}",
styles["HopeBody"]
))
story.append(pdf_paragraph(f"Main symptom: {row.get('main_symptom', 'Unknown')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Secondary symptoms: {row.get('secondary_symptoms', 'None')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Duration: {row.get('symptom_duration', 'Unknown')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Appetite: {row.get('appetite_status', 'Unknown')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Energy level: {row.get('energy_level', 'Unknown')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Dataset category: {row.get('problem_category', 'Unknown')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Dataset urgency: {row.get('urgency_level', 'Unknown')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Recommended next step: {row.get('recommended_next_step', 'Not available')}", styles["HopeBody"]))
story.append(pdf_paragraph(f"Triage reason: {row.get('triage_reason', 'Not available')}", styles["HopeBody"]))
story.append(Spacer(1, 8))
story.append(pdf_paragraph("5. Safety Disclaimer", styles["HopeHeading"]))
story.append(pdf_paragraph(
"HopePet AI does not provide a medical diagnosis and does not replace a veterinarian. "
"If your pet has emergency signs, becomes worse, or you are unsure, contact a veterinarian or emergency veterinary clinic.",
styles["HopeBody"]
))
doc.build(story)
return pdf_path
# ============================================================
# WHATSAPP SHARE
# ============================================================
def create_whatsapp_html(user_inputs, action_plan):
step_1 = action_plan["do_now"][0] if len(action_plan["do_now"]) > 0 else ""
step_2 = action_plan["do_now"][1] if len(action_plan["do_now"]) > 1 else ""
step_3 = action_plan["do_now"][2] if len(action_plan["do_now"]) > 2 else ""
message = f"""🐾 HopePet AI Action Plan
Pet: {user_inputs['pet_age_group']} {user_inputs['pet_type']}
Main symptom: {user_inputs['main_symptom']}
Urgency: {action_plan['urgency']}
Risk Score: {action_plan['risk_score']}/10
Situation:
{action_plan['situation_summary']}
What to do now:
- {step_1}
- {step_2}
- {step_3}
When to contact a vet:
{action_plan['vet_contact']}
Important:
HopePet AI does not replace a veterinarian.
"""
url = "https://wa.me/?text=" + quote(message)
return f"""
<div class="share-card">
<h2>📲 Share on WhatsApp</h2>
<p>Share a short version of the HopePet AI Action Plan through WhatsApp.</p>
<a class="whatsapp-button" href="{url}" target="_blank">
📲 Share Action Plan on WhatsApp
</a>
</div>
"""
# ============================================================
# MAIN ANALYSIS FUNCTION
# ============================================================
def analyze_case(
pet_type,
pet_age_group,
pet_sex,
breed_size,
vaccination_status,
environment,
medical_background,
recent_change,
main_symptom,
secondary_symptoms,
symptom_duration,
appetite_status,
water_intake,
energy_level,
pain_signs,
emergency_signs,
previous_occurrence,
user_goal,
free_text
):
user_inputs = {
"pet_type": pet_type,
"pet_age_group": pet_age_group,
"pet_sex": pet_sex,
"breed_size": breed_size,
"vaccination_status": vaccination_status,
"environment": environment,
"medical_background": medical_background,
"recent_change": recent_change,
"main_symptom": main_symptom,
"secondary_symptoms": secondary_symptoms,
"symptom_duration": symptom_duration,
"appetite_status": appetite_status,
"water_intake": water_intake,
"energy_level": energy_level,
"pain_signs": pain_signs,
"emergency_signs": emergency_signs,
"previous_occurrence": previous_occurrence,
"user_goal": user_goal,
"free_text": free_text
}
query_text = build_user_case_text(
pet_type,
pet_age_group,
pet_sex,
breed_size,
vaccination_status,
environment,
medical_background,
recent_change,
main_symptom,
secondary_symptoms,
symptom_duration,
appetite_status,
water_intake,
energy_level,
pain_signs,
emergency_signs,
previous_occurrence,
user_goal,
free_text
)
similar_cases = retrieve_similar_cases(query_text, TOP_K)
category = weighted_vote(similar_cases, "problem_category")
risk_score = calculate_risk_score(
pet_age_group,
main_symptom,
appetite_status,
water_intake,
energy_level,
pain_signs,
emergency_signs,
symptom_duration
)
urgency, risk_score = urgency_from_risk_score(
risk_score,
main_symptom,
emergency_signs,
energy_level,
pain_signs
)
situation_summary = build_situation_summary(
pet_type,
pet_age_group,
main_symptom,
secondary_symptoms,
symptom_duration,
appetite_status,
water_intake,
energy_level,
emergency_signs
)
do_now = build_do_now_steps(
urgency,
pet_type,
main_symptom,
appetite_status
)
avoid = build_avoid_steps(urgency)
vet_contact = build_vet_contact_message(urgency)
why = build_why_message(category, risk_score, similar_cases)
ai_note = generate_short_ai_note(query_text, urgency, category)
action_plan = {
"situation_summary": situation_summary,
"category": category,
"urgency": urgency,
"risk_score": risk_score,
"do_now": do_now,
"avoid": avoid,
"vet_contact": vet_contact,
"why": why,
"ai_note": ai_note
}
action_plan_html = create_action_plan_html(action_plan)
similar_cases_html = create_similar_cases_html(similar_cases)
pdf_path = create_pdf_report(user_inputs, action_plan, similar_cases)
whatsapp_html = create_whatsapp_html(user_inputs, action_plan)
return action_plan_html, similar_cases_html, pdf_path, whatsapp_html, query_text
# ============================================================
# APP CHOICES
# ============================================================
pet_type_choices = ["Dog", "Cat"]
age_group_choices = ["Puppy", "Kitten", "Adult", "Senior"]
sex_choices = ["Female", "Male", "Unknown"]
breed_choices = get_choices("breed_size", ["Small", "Medium", "Large", "Unknown"])
vaccination_choices = get_choices("vaccination_status", ["Up to date", "Not up to date", "Unknown"])
environment_choices = get_choices("environment", ["Indoor only", "Indoor mostly", "Outdoor access", "Mostly outdoor", "Unknown"])
medical_choices = get_choices(
"medical_background",
[
"No known medical condition",
"Kidney disease",
"Diabetes",
"Heart disease",
"Arthritis",
"Allergies",
"Recent surgery",
"Unknown"
]
)
recent_change_choices = get_choices(
"recent_change",
[
"No recent change",
"New food",
"New home",
"New pet",
"Travel",
"Fireworks or loud noise",
"Weather change",
"Unknown"
]
)
main_symptom_choices = get_choices(
"main_symptom",
[
"Vomiting",
"Diarrhea",
"Not eating",
"Limping",
"Shaking",
"Hiding",
"Coughing",
"Scratching",
"Biting",
"Anxiety",
"Low energy",
"Bleeding",
"Difficulty breathing"
]
)
secondary_symptom_choices = [
"None",
"Vomiting",
"Diarrhea",
"Hiding",
"Shaking",
"Coughing",
"Scratching",
"Restlessness",
"Whining",
"Tiredness",
"Low energy",
"Refusing water",
"Jumping",
"Excitement"
]
duration_choices = get_choices(
"symptom_duration",
["A few hours", "1 day", "2-3 days", "More than 3 days", "More than a week", "Unknown"]
)
appetite_choices = get_choices("appetite_status", ["Normal", "Reduced", "Not eating", "Increased", "Unknown"])
water_choices = get_choices("water_intake", ["Normal", "Drinking less", "Drinking more", "Not drinking", "Unknown"])
energy_choices = get_choices("energy_level", ["Normal", "Slightly tired", "Very tired", "Restless", "Cannot stand", "Unknown"])
pain_choices = [
"None",
"Limping",
"Whining",
"Hiding",
"Yelping",
"Sensitivity to touch",
"Cannot stand",
"Restlessness"
]
emergency_choices = [
"None",
"Difficulty breathing",
"Seizure",
"Uncontrolled bleeding",
"Collapse",
"Cannot stand",
"Severe weakness",
"Blue or pale gums",
"Repeated vomiting"
]
previous_choices = ["No", "Yes", "Unknown"]
goal_choices = [
"Understand urgency",
"Know safe first steps",
"Decide whether to call a vet",
"Calm my pet safely",
"Training advice",
"Nutrition guidance",
"General guidance"
]
# ============================================================
# QUICK STARTERS
# ============================================================
def quick_senior_cat():
return (
"Cat",
"Senior",
"Unknown",
"Unknown",
"Unknown",
"Indoor only",
"Kidney disease",
"No recent change",
"Not eating",
["Vomiting", "Hiding"],
"1 day",
"Not eating",
"Drinking less",
"Very tired",
["Hiding"],
["None"],
"No",
"Understand urgency",
"My senior cat stopped eating today, seems very tired, and vomited once."
)
def quick_fireworks_dog():
return (
"Dog",
"Adult",
"Unknown",
"Medium",
"Up to date",
"Indoor mostly",
"No known medical condition",
"Fireworks or loud noise",
"Shaking",
["Hiding", "Restlessness"],
"A few hours",
"Normal",
"Normal",
"Restless",
["None"],
["None"],
"Yes",
"Calm my pet safely",
"My dog is shaking and hiding because of fireworks outside."
)
def quick_puppy_biting():
return (
"Dog",
"Puppy",
"Unknown",
"Small",
"Up to date",
"Indoor mostly",
"No known medical condition",
"New home",
"Biting",
["Jumping", "Excitement"],
"More than 3 days",
"Normal",
"Normal",
"Normal",
["None"],
["None"],
"Yes",
"Training advice",
"My puppy keeps biting my hands during playtime and gets very excited."
)
# ============================================================
# DESIGN
# ============================================================
custom_css = """
.gradio-container {
background: radial-gradient(circle at top left, #F7DDE8 0%, transparent 28%),
radial-gradient(circle at top right, #CFE9FF 0%, transparent 30%),
linear-gradient(135deg, #FFF9FB 0%, #F3FBFF 48%, #FFF2F8 100%) !important;
color: #1F2940 !important;
}
.hero-wrap {
display: flex;
justify-content: center;
margin-bottom: 24px;
}
.hero-card {
width: 100%;
max-width: 1380px;
background: linear-gradient(135deg, #BDE0FE 0%, #E1D7F5 48%, #F7A8B8 100%);
border-radius: 36px;
padding: 42px 32px;
text-align: center;
color: #1F2940;
box-shadow: 0 20px 50px rgba(110, 110, 160, 0.22);
border: 1px solid rgba(255,255,255,0.9);
position: relative;
overflow: hidden;
}
.hero-card:before {
content: "";
position: absolute;
top: -90px;
left: -70px;
width: 250px;
height: 250px;
background: rgba(255,255,255,0.20);
border-radius: 50%;
}
.hero-card:after {
content: "";
position: absolute;
bottom: -100px;
right: -70px;
width: 300px;
height: 300px;
background: rgba(255,255,255,0.18);
border-radius: 50%;
}
.hero-badge {
display: inline-block;
background: rgba(255,255,255,0.78);
padding: 9px 18px;
border-radius: 999px;
font-size: 13px;
font-weight: 800;
letter-spacing: 0.4px;
color: #59446B;
box-shadow: 0 8px 18px rgba(100,100,140,0.10);
margin-bottom: 14px;
}
.hero-logo {
font-size: 46px;
margin-bottom: 8px;
}
.hero-title {
font-size: 56px;
font-weight: 950;
margin: 0;
letter-spacing: -1px;
color: #1D2742;
}
.hero-title span {
color: #754E7D;
}
.hero-subtitle {
font-size: 19px;
font-weight: 700;
margin-top: 12px;
color: #27314A;
}
.hero-description {
max-width: 900px;
margin: 12px auto 0 auto;
font-size: 15px;
line-height: 1.8;
color: #34415C;
}
.hero-pills {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 24px;
}
.hero-pill {
background: rgba(255,255,255,0.75);
border: 1px solid rgba(255,255,255,0.95);
border-radius: 999px;
padding: 10px 16px;
font-size: 13px;
font-weight: 700;
color: #39445D;
box-shadow: 0 8px 18px rgba(100, 100, 140, 0.09);
}
.info-strip {
background: rgba(255,255,255,0.88);
border-radius: 22px;
padding: 16px 22px;
border: 1px solid rgba(255,255,255,0.95);
box-shadow: 0 10px 26px rgba(140,140,180,0.12);
margin-bottom: 20px;
color: #354056;
}
.page-card {
background: rgba(255,255,255,0.92);
border-radius: 28px;
padding: 28px;
box-shadow: 0 14px 38px rgba(120,120,160,0.16);
border: 1px solid rgba(255,255,255,0.95);
}
button {
border-radius: 14px !important;
font-weight: 800 !important;
}
.action-plan-card {
background: rgba(255,255,255,0.96);
border-radius: 30px;
padding: 30px;
box-shadow: 0 16px 40px rgba(120,120,160,0.18);
border: 1px solid white;
}
.plan-header {
text-align: center;
margin-bottom: 20px;
}
.plan-header h2 {
font-size: 34px;
margin: 6px 0;
color: #1F2940;
}
.plan-kicker {
display: inline-block;
background: #FDEAF1;
border-radius: 999px;
padding: 8px 14px;
font-size: 13px;
font-weight: 800;
color: #754E7D;
}
.urgency-panel {
border-radius: 24px;
padding: 22px;
text-align: center;
margin: 20px 0;
color: #1F2940;
box-shadow: 0 12px 26px rgba(120,120,160,0.14);
}
.urgency-main {
font-size: 28px;
font-weight: 950;
}
.urgency-sub {
font-size: 16px;
font-weight: 800;
margin-top: 6px;
}
.plan-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.plan-box {
background: #FFFFFF;
border: 1px solid #EEF0F6;
border-radius: 22px;
padding: 18px;
margin-top: 14px;
box-shadow: 0 8px 18px rgba(140,140,180,0.08);
}
.plan-label {
font-size: 14px;
font-weight: 950;
color: #6C4A78;
margin-bottom: 8px;
}
.soft-blue {
background: #F3FAFF;
border-color: #DCEEFF;
}
.soft-pink {
background: #FFF5F8;
border-color: #FFDCE8;
}
.soft-purple {
background: #F8F3FF;
border-color: #E7D8FF;
}
.warning-plan {
background: #FFF1F5;
border-color: #FFC8D8;
}
.disclaimer-card {
margin-top: 18px;
border-radius: 20px;
padding: 16px;
background: #FFF8E8;
border: 1px solid #FFE5A8;
font-weight: 800;
color: #53442A;
}
.similar-wrapper {
background: rgba(255,255,255,0.94);
border-radius: 30px;
padding: 30px;
box-shadow: 0 16px 40px rgba(120,120,160,0.16);
}
.muted {
color: #5B6478;
}
.similar-case-card {
background: #FFFFFF;
border: 1px solid #EEF0F6;
border-radius: 24px;
padding: 22px;
margin-top: 18px;
box-shadow: 0 10px 24px rgba(140,140,180,0.10);
}
.similar-top {
display: flex;
justify-content: space-between;
gap: 14px;
align-items: center;
}
.case-number {
display: inline-block;
background: #EAF6FF;
color: #35516D;
padding: 7px 13px;
border-radius: 999px;
font-weight: 900;
font-size: 13px;
}
.similar-score {
background: #FDEAF1;
color: #754E7D;
padding: 10px 14px;
border-radius: 999px;
font-weight: 950;
}
.case-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 14px;
}
.case-grid div {
background: #FAFBFF;
border: 1px solid #EEF0F6;
border-radius: 16px;
padding: 12px;
}
.case-section {
background: #FFF9FB;
border: 1px solid #FFE0EC;
border-radius: 16px;
padding: 14px;
margin-top: 12px;
}
.share-card {
background: rgba(255,255,255,0.96);
border-radius: 28px;
padding: 28px;
box-shadow: 0 14px 34px rgba(120,120,160,0.16);
border: 1px solid white;
text-align: center;
}
.whatsapp-button {
display: inline-block;
background: linear-gradient(135deg, #B8E0D2, #93D7B8);
color: #19372B !important;
text-decoration: none !important;
padding: 15px 22px;
border-radius: 999px;
font-weight: 950;
box-shadow: 0 10px 22px rgba(100,160,130,0.18);
margin-top: 12px;
}
.placeholder-box {
background: #F8F3FF;
border: 1px solid #E7D8FF;
border-radius: 22px;
padding: 20px;
color: #514261;
font-weight: 700;
}
@media (max-width: 800px) {
.hero-title {
font-size: 42px;
}
.plan-grid,
.case-grid {
grid-template-columns: 1fr;
}
.similar-top {
flex-direction: column;
align-items: flex-start;
}
}
"""
# ============================================================
# BUILD GRADIO APP - STABLE TABS VERSION
# ============================================================
with gr.Blocks(
title="HopePet AI",
theme=gr.themes.Soft(primary_hue="pink", secondary_hue="blue"),
css=custom_css
) as demo:
gr.HTML(
"""
<div class="hero-wrap">
<div class="hero-card">
<div class="hero-badge">Smart Pet-Care Assistant • Final Project</div>
<div class="hero-logo">🐾✨</div>
<h1 class="hero-title">Hope<span>Pet</span> AI</h1>
<div class="hero-subtitle">
Gentle AI guidance for dog and cat owners
</div>
<div class="hero-description">
HopePet AI helps pet owners understand symptoms through structured input,
similar case retrieval, safe risk logic, AI-generated guidance, PDF reports,
and WhatsApp sharing.
</div>
<div class="hero-pills">
<div class="hero-pill">🐶 Dogs & Cats</div>
<div class="hero-pill">🧠 Embeddings</div>
<div class="hero-pill">🔍 Similar Cases</div>
<div class="hero-pill">✨ Action Plan</div>
<div class="hero-pill">📄 PDF Report</div>
<div class="hero-pill">📲 WhatsApp</div>
</div>
</div>
</div>
"""
)
gr.HTML(
"""
<div class="info-strip">
<b>Safety note:</b> HopePet AI does not replace a veterinarian.
It provides responsible first-step guidance based on synthetic pet-care cases and safety logic.
</div>
"""
)
with gr.Tabs():
with gr.Tab("🐾 Pet Profile"):
with gr.Group(elem_classes=["page-card"]):
gr.Markdown("## 🐾 Tell us about your pet")
gr.Markdown(
"""
Fill in the basic pet profile.
Most fields use fixed choices to make the analysis more structured and accurate.
"""
)
with gr.Row():
pet_type = gr.Radio(
pet_type_choices,
label="Pet type",
value="Dog"
)
pet_age_group = gr.Radio(
age_group_choices,
label="Age group",
value="Adult"
)
with gr.Row():
pet_sex = gr.Radio(
sex_choices,
label="Pet sex",
value="Unknown"
)
breed_size = gr.Dropdown(
breed_choices,
label="Breed size",
value="Unknown",
allow_custom_value=True
)
with gr.Row():
vaccination_status = gr.Dropdown(
vaccination_choices,
label="Vaccination status",
value="Unknown",
allow_custom_value=True
)
environment = gr.Dropdown(
environment_choices,
label="Environment",
value="Indoor mostly",
allow_custom_value=True
)
with gr.Row():
medical_background = gr.Dropdown(
medical_choices,
label="Medical background",
value="No known medical condition",
allow_custom_value=True
)
recent_change = gr.Dropdown(
recent_change_choices,
label="Recent change",
value="No recent change",
allow_custom_value=True
)
gr.Markdown("## ⚡ Quick Starters")
gr.Markdown(
"""
Use one of these examples to instantly fill the app and test how HopePet AI works.
"""
)
with gr.Row():
senior_cat_btn = gr.Button("🐱 Senior cat not eating")
fireworks_dog_btn = gr.Button("🎆 Dog afraid of fireworks")
puppy_biting_btn = gr.Button("🐕 Puppy biting hands")
with gr.Tab("🩺 Symptoms & Context"):
with gr.Group(elem_classes=["page-card"]):
gr.Markdown("## 🩺 Describe the current problem")
gr.Markdown(
"""
Add the symptoms and current situation.
These details are used to calculate the risk score and retrieve similar cases.
"""
)
with gr.Row():
main_symptom = gr.Dropdown(
main_symptom_choices,
label="Main symptom",
value="Vomiting",
allow_custom_value=True
)
symptom_duration = gr.Dropdown(
duration_choices,
label="Symptom duration",
value="A few hours",
allow_custom_value=True
)
secondary_symptoms = gr.CheckboxGroup(
secondary_symptom_choices,
label="Secondary symptoms",
value=["None"]
)
with gr.Row():
appetite_status = gr.Radio(
appetite_choices,
label="Appetite status",
value="Normal"
)
water_intake = gr.Radio(
water_choices,
label="Water intake",
value="Normal"
)
energy_level = gr.Radio(
energy_choices,
label="Energy level",
value="Normal"
)
with gr.Row():
pain_signs = gr.CheckboxGroup(
pain_choices,
label="Pain signs",
value=["None"]
)
emergency_signs = gr.CheckboxGroup(
emergency_choices,
label="Emergency signs",
value=["None"]
)
with gr.Row():
previous_occurrence = gr.Radio(
previous_choices,
label="Did this happen before?",
value="Unknown"
)
user_goal = gr.Radio(
goal_choices,
label="What do you need help with?",
value="Understand urgency"
)
free_text = gr.Textbox(
label="Optional free text description",
placeholder="Example: My senior cat stopped eating today and seems very tired.",
lines=4
)
with gr.Tab("✨ AI Action Plan"):
with gr.Group(elem_classes=["page-card"]):
gr.Markdown("## ✨ Generate your HopePet AI Action Plan")
gr.Markdown(
"""
After filling the pet profile and symptoms, click the button below.
The app will calculate risk, retrieve similar cases, generate an action plan, create a PDF report, and prepare a WhatsApp sharing link.
"""
)
analyze_btn = gr.Button("✨ Analyze Pet Case")
action_plan_output = gr.HTML(
"""
<div class="placeholder-box">
Your personalized HopePet AI Action Plan will appear here after clicking Analyze.
</div>
"""
)
with gr.Tab("🔍 Similar Cases"):
with gr.Group(elem_classes=["page-card"]):
similar_cases_output = gr.HTML(
"""
<div class="placeholder-box">
The top 3 similar cases will appear here after clicking Analyze Pet Case.
</div>
"""
)
with gr.Accordion("Show retrieval text created by the app", open=False):
query_text_output = gr.Textbox(
label="Retrieval text",
lines=6,
interactive=False
)
with gr.Tab("📄 PDF & WhatsApp"):
with gr.Group(elem_classes=["page-card"]):
gr.Markdown("## 📄 Download your report and share the action plan")
gr.Markdown(
"""
After analysis, download a structured PDF report or share a short action plan through WhatsApp.
"""
)
pdf_output = gr.File(
label="📄 Download HopePet AI PDF Report",
interactive=False
)
whatsapp_output = gr.HTML(
"""
<div class="placeholder-box">
The WhatsApp sharing button will appear here after clicking Analyze Pet Case.
</div>
"""
)
gr.Markdown(
"""
### ℹ️ Safety Reminder
HopePet AI does not provide a medical diagnosis and does not replace a veterinarian.
If your pet shows emergency signs or becomes worse, contact a veterinarian immediately.
"""
)
with gr.Tab("ℹ️ About & Safety"):
with gr.Group(elem_classes=["page-card"]):
gr.Markdown(
"""
## About HopePet AI
HopePet AI is an AI-powered pet-care assistant for dog and cat owners.
The application uses:
- Structured user input
- A synthetic pet-care dataset
- The winning embedding model from Part 3
- Similar case retrieval
- Rule-based risk and urgency logic
- AI-generated guidance
- PDF report generation
- WhatsApp sharing
## Important Safety Notice
HopePet AI does **not** provide a medical diagnosis.
It does **not** replace a veterinarian.
If your pet has difficulty breathing, seizures, collapse, uncontrolled bleeding, severe weakness, cannot stand, or shows any emergency sign, contact a veterinarian or emergency veterinary clinic immediately.
"""
)
all_inputs = [
pet_type,
pet_age_group,
pet_sex,
breed_size,
vaccination_status,
environment,
medical_background,
recent_change,
main_symptom,
secondary_symptoms,
symptom_duration,
appetite_status,
water_intake,
energy_level,
pain_signs,
emergency_signs,
previous_occurrence,
user_goal,
free_text
]
analyze_btn.click(
fn=analyze_case,
inputs=all_inputs,
outputs=[
action_plan_output,
similar_cases_output,
pdf_output,
whatsapp_output,
query_text_output
]
)
senior_cat_btn.click(
fn=quick_senior_cat,
inputs=[],
outputs=all_inputs
)
fireworks_dog_btn.click(
fn=quick_fireworks_dog,
inputs=[],
outputs=all_inputs
)
puppy_biting_btn.click(
fn=quick_puppy_biting,
inputs=[],
outputs=all_inputs
)
demo.queue().launch(show_error=True)