Spaces:
Running
Running
from typing import Dict, List, Union, Any, Optional, Callable | |
from urllib.parse import quote | |
from breed_health_info import breed_health_info | |
from breed_noise_info import breed_noise_info | |
def get_akc_breeds_link(breed: str) -> str: | |
"""Generate AKC breed page URL with intelligent name handling.""" | |
breed_name = breed.lower() | |
breed_name = breed_name.replace('_', '-') | |
breed_name = breed_name.replace("'", '') | |
breed_name = breed_name.replace(" ", '-') | |
special_cases = { | |
'mexican-hairless': 'xoloitzcuintli', | |
'brabancon-griffon': 'brussels-griffon', | |
'bull-mastiff': 'bullmastiff', | |
'walker-hound': 'treeing-walker-coonhound' | |
} | |
breed_name = special_cases.get(breed_name, breed_name) | |
return f"https://www.akc.org/dog-breeds/{breed_name}/" | |
def get_color_scheme(is_single_dog: bool) -> Union[str, List[str]]: | |
"""Get color scheme for dog detection visualization.""" | |
single_dog_color = '#34C759' # 清爽的綠色作為單狗顏色 | |
color_list = [ | |
'#FF5733', # 珊瑚紅 | |
'#28A745', # 深綠色 | |
'#3357FF', # 寶藍色 | |
'#FF33F5', # 粉紫色 | |
'#FFB733', # 橙黃色 | |
'#33FFF5', # 青藍色 | |
'#A233FF', # 紫色 | |
'#FF3333', # 紅色 | |
'#33FFB7', # 青綠色 | |
'#FFE033' # 金黃色 | |
] | |
return single_dog_color if is_single_dog else color_list | |
def format_warning_html(message: str) -> str: | |
"""Format warning messages in a consistent style.""" | |
return f''' | |
<div class="dog-info-card"> | |
<div class="breed-info"> | |
<p class="warning-message"> | |
<span class="icon">⚠️</span> | |
{message} | |
</p> | |
</div> | |
</div> | |
''' | |
def format_error_message(color: str, index: int) -> str: | |
"""Format error message when confidence is too low.""" | |
return f''' | |
<div class="dog-info-card" style="border-left: 8px solid {color};"> | |
<div class="dog-info-header" style="background-color: {color}10;"> | |
<span class="dog-label" style="color: {color};">Dog {index}</span> | |
</div> | |
<div class="breed-info"> | |
<div class="warning-message"> | |
<span class="icon">⚠️</span> | |
The image is unclear or the breed is not in the dataset. Please upload a clearer image. | |
</div> | |
</div> | |
</div> | |
''' | |
def format_description_html(description: Dict[str, Any], breed: str) -> str: | |
"""Format basic breed description with tooltips.""" | |
if not isinstance(description, dict): | |
return f"<p>{description}</p>" | |
fields_order = [ | |
"Size", "Lifespan", "Temperament", "Exercise Needs", | |
"Grooming Needs", "Care Level", "Good with Children", | |
"Description" | |
] | |
html_parts = [] | |
for field in fields_order: | |
if field in description: | |
value = description[field] | |
tooltip_html = format_tooltip(field, value) | |
html_parts.append(f'<li style="margin-bottom: 10px;">{tooltip_html}</li>') | |
# Add any remaining fields | |
for key, value in description.items(): | |
if key not in fields_order and key != "Breed": | |
html_parts.append(f'<li style="margin-bottom: 10px;"><strong>{key}:</strong> {value}</li>') | |
return f'<ul style="list-style-type: none; padding-left: 0;">{" ".join(html_parts)}</ul>' | |
def format_tooltip(key: str, value: str) -> str: | |
"""Format tooltip with content for each field.""" | |
tooltip_contents = { | |
"Size": { | |
"title": "Size Categories", | |
"items": [ | |
"Small: Under 20 pounds", | |
"Medium: 20-60 pounds", | |
"Large: Over 60 pounds", | |
"Giant: Over 100 pounds", | |
"Varies: Depends on variety" | |
] | |
}, | |
"Exercise Needs": { | |
"title": "Exercise Needs", | |
"items": [ | |
"Low: Short walks and play sessions", | |
"Moderate: 1-2 hours of daily activity", | |
"High: Extensive exercise (2+ hours/day)", | |
"Very High: Constant activity and mental stimulation needed" | |
] | |
}, | |
"Grooming Needs": { | |
"title": "Grooming Requirements", | |
"items": [ | |
"Low: Basic brushing, occasional baths", | |
"Moderate: Weekly brushing, occasional grooming", | |
"High: Daily brushing, frequent professional grooming needed", | |
"Professional care recommended for all levels" | |
] | |
}, | |
"Care Level": { | |
"title": "Care Level Explained", | |
"items": [ | |
"Low: Basic care and attention needed", | |
"Moderate: Regular care and routine needed", | |
"High: Significant time and attention needed", | |
"Very High: Extensive care, training and attention required" | |
] | |
}, | |
"Good with Children": { | |
"title": "Child Compatibility", | |
"items": [ | |
"Yes: Excellent with kids, patient and gentle", | |
"Moderate: Good with older children", | |
"No: Better suited for adult households" | |
] | |
}, | |
"Lifespan": { | |
"title": "Average Lifespan", | |
"items": [ | |
"Short: 6-8 years", | |
"Average: 10-15 years", | |
"Long: 12-20 years", | |
"Varies by size: Larger breeds typically have shorter lifespans" | |
] | |
}, | |
"Temperament": { | |
"title": "Temperament Guide", | |
"items": [ | |
"Describes the dog's natural behavior and personality", | |
"Important for matching with owner's lifestyle", | |
"Can be influenced by training and socialization" | |
] | |
} | |
} | |
tooltip = tooltip_contents.get(key, {"title": key, "items": []}) | |
tooltip_content = "<br>".join([f"• {item}" for item in tooltip["items"]]) | |
return f''' | |
<span class="tooltip"> | |
<strong>{key}:</strong> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>{tooltip["title"]}:</strong><br> | |
{tooltip_content} | |
</span> | |
</span> {value} | |
''' | |
def format_single_dog_result(breed: str, description: Dict[str, Any], color: str = "#34C759") -> str: | |
"""Format single dog detection result into HTML.""" | |
# 獲取noise和health資訊 | |
noise_info = breed_noise_info.get(breed, {}) | |
health_info = breed_health_info.get(breed, {}) | |
# 處理噪音資訊 | |
noise_notes = noise_info.get('noise_notes', '').split('\n') | |
noise_characteristics = [] | |
barking_triggers = [] | |
noise_level = noise_info.get('noise_level', 'Information not available') | |
in_section = None | |
for line in noise_notes: | |
line = line.strip() | |
if 'Typical noise characteristics:' in line: | |
in_section = 'characteristics' | |
elif 'Barking triggers:' in line: | |
in_section = 'triggers' | |
elif line.startswith('•'): | |
if in_section == 'characteristics': | |
noise_characteristics.append(line[1:].strip()) | |
elif in_section == 'triggers': | |
barking_triggers.append(line[1:].strip()) | |
# 處理健康資訊 | |
health_notes = health_info.get('health_notes', '').split('\n') | |
health_considerations = [] | |
health_screenings = [] | |
in_section = None | |
for line in health_notes: | |
line = line.strip() | |
if 'Common breed-specific health considerations' in line: | |
in_section = 'considerations' | |
elif 'Recommended health screenings:' in line: | |
in_section = 'screenings' | |
elif line.startswith('•'): | |
if in_section == 'considerations': | |
health_considerations.append(line[1:].strip()) | |
elif in_section == 'screenings': | |
health_screenings.append(line[1:].strip()) | |
return f''' | |
<div class="dog-info-card" style="background: {color}10;"> | |
<div class="breed-title" style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 20px;"> | |
<span class="icon" style="font-size: 2.0em;">🐾</span> | |
<h2 style=" | |
margin: 0; | |
padding: 10px 20px; | |
background: ${color}15; | |
border-left: 4px solid ${color}; | |
border-radius: 6px; | |
color: ${color}; | |
font-weight: 600; | |
font-size: 2.0em; | |
letter-spacing: 0.5px;"> | |
{breed.replace('_', ' ')} | |
</h2> | |
</div> | |
<div class="breed-info"> | |
<!-- Basic Information --> | |
<div class="section-header" style="margin-bottom: 15px;"> | |
<h3> | |
<span class="icon">📋</span> BASIC INFORMATION | |
</h3> | |
</div> | |
<div class="info-cards" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;"> | |
<div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);"> | |
<span class="tooltip"> | |
<span class="icon">📏</span> | |
<span class="label">Size</span> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>Size Categories:</strong><br> | |
• Small: Under 20 pounds<br> | |
• Medium: 20-60 pounds<br> | |
• Large: Over 60 pounds<br> | |
• Giant: Over 100 pounds | |
</span> | |
</span> | |
<span>{description['Size']}</span> | |
</div> | |
<div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);"> | |
<span class="tooltip"> | |
<span class="icon">⏳</span> | |
<span class="label">Lifespan</span> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>Lifespan Categories:</strong><br> | |
• Short: 6-8 years<br> | |
• Average: 10-15 years<br> | |
• Long: 12-20 years | |
</span> | |
</span> | |
<span>{description['Lifespan']}</span> | |
</div> | |
</div> | |
<!-- Care Requirements --> | |
<div class="section-header" style="margin-bottom: 15px;"> | |
<h3> | |
<span class="icon">💪</span> CARE REQUIREMENTS | |
</h3> | |
</div> | |
<div class="info-cards" style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-bottom: 30px;"> | |
<div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);"> | |
<span class="tooltip"> | |
<span class="icon">🏃</span> | |
<span class="label">Exercise</span> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>Exercise Needs:</strong><br> | |
• Low: Short walks<br> | |
• Moderate: 1-2 hours daily<br> | |
• High: 2+ hours daily | |
</span> | |
</span> | |
<span>{description['Exercise Needs']}</span> | |
</div> | |
<div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);"> | |
<span class="tooltip"> | |
<span class="icon">✂️</span> | |
<span class="label">Grooming</span> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>Grooming Requirements:</strong><br> | |
• Low: Basic brushing<br> | |
• Moderate: Weekly grooming<br> | |
• High: Daily maintenance | |
</span> | |
</span> | |
<span>{description['Grooming Needs']}</span> | |
</div> | |
<div class="info-card" style="background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);"> | |
<span class="tooltip"> | |
<span class="icon">⭐</span> | |
<span class="label">Care Level</span> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>Care Level:</strong><br> | |
• Low: Basic care<br> | |
• Moderate: Regular care<br> | |
• High: Extensive care | |
</span> | |
</span> | |
<span>{description['Care Level']}</span> | |
</div> | |
</div> | |
<!-- Noise Behavior --> | |
<div class="section-header" style="margin-bottom: 15px;"> | |
<h3> | |
<span class="tooltip"> | |
<span class="icon">🔊</span> | |
<span>NOISE BEHAVIOR</span> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>Noise Behavior:</strong><br> | |
• Typical vocalization patterns<br> | |
• Common triggers and frequency<br> | |
• Based on breed characteristics | |
</span> | |
</span> | |
</h3> | |
</div> | |
<div class="noise-section" style="background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 30px;"> | |
<div class="noise-level" style="margin-bottom: 15px;"> | |
<span class="label">Noise Level:</span> | |
<span class="value">{noise_level}</span> | |
</div> | |
<div class="characteristics-list"> | |
{format_noise_items(noise_characteristics[:2])} | |
</div> | |
<details class="expandable-section" style="margin-top: 15px;"> | |
<summary class="expand-header" style="cursor: pointer; padding: 10px; background: #f8f9fa; border-radius: 6px;"> | |
Show Complete Noise Information | |
<span class="expand-icon">▼</span> | |
</summary> | |
<div class="expanded-content" style="margin-top: 15px;"> | |
<div class="characteristics-list"> | |
<h4>All Characteristics:</h4> | |
{format_noise_items(noise_characteristics)} | |
</div> | |
<div class="triggers-list"> | |
<h4>Barking Triggers:</h4> | |
{format_noise_items(barking_triggers)} | |
</div> | |
<div class="disclaimer-section" style="margin-top: 15px; font-size: 0.9em; color: #666;"> | |
<p class="disclaimer-text source-text">Source: Compiled from various breed behavior resources, 2024</p> | |
<p class="disclaimer-text">Individual dogs may vary in their vocalization patterns.</p> | |
<p class="disclaimer-text">Training can significantly influence barking behavior.</p> | |
<p class="disclaimer-text">Environmental factors may affect noise levels.</p> | |
</div> | |
</div> | |
</details> | |
</div> | |
<!-- Health Insights --> | |
<div class="section-header" style="margin-bottom: 15px;"> | |
<h3> | |
<span class="tooltip"> | |
<span class="icon">🏥</span> | |
<span>HEALTH INSIGHTS</span> | |
<span class="tooltip-icon">ⓘ</span> | |
<span class="tooltip-text"> | |
<strong>Health Information:</strong><br> | |
• Common breed-specific conditions<br> | |
• Recommended health screenings<br> | |
• General health considerations | |
</span> | |
</span> | |
</h3> | |
</div> | |
<div class="health-section" style="background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 30px;"> | |
<div class="key-considerations"> | |
{format_health_items(health_considerations[:2])} | |
</div> | |
<details class="expandable-section" style="margin-top: 15px;"> | |
<summary class="expand-header" style="cursor: pointer; padding: 10px; background: #f8f9fa; border-radius: 6px;"> | |
Show Complete Health Information | |
<span class="expand-icon">▼</span> | |
</summary> | |
<div class="expanded-content" style="margin-top: 15px;"> | |
<div class="considerations-list"> | |
<h4>All Health Considerations:</h4> | |
{format_health_items(health_considerations)} | |
</div> | |
<div class="screenings-list"> | |
<h4>Recommended Screenings:</h4> | |
{format_health_items(health_screenings)} | |
</div> | |
<div class="disclaimer-section" style="margin-top: 15px; font-size: 0.9em; color: #666;"> | |
<p class="disclaimer-text source-text">Source: Compiled from various veterinary and breed information resources, 2024</p> | |
<p class="disclaimer-text">This information is for reference only and based on breed tendencies.</p> | |
<p class="disclaimer-text">Each dog is unique and may not develop any or all of these conditions.</p> | |
<p class="disclaimer-text">Always consult with qualified veterinarians for professional advice.</p> | |
</div> | |
</div> | |
</details> | |
</div> | |
<!-- Description --> | |
<div class="section-header" style="margin-bottom: 15px;"> | |
<h3> | |
<span class="icon">📝</span> DESCRIPTION | |
</h3> | |
</div> | |
<div class="description-section" style="background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 30px;"> | |
<p style="line-height: 1.6;">{description.get('Description', '')}</p> | |
</div> | |
<!-- Action Section --> | |
<div class="action-section" style="text-align: center; margin-top: 30px;"> | |
<a href="{get_akc_breeds_link(breed)}" | |
target="_blank" | |
class="akc-button" | |
style="display: inline-block; padding: 12px 24px; background: linear-gradient(90deg, #4299e1, #48bb78); color: white; text-decoration: none; border-radius: 6px;"> | |
<span class="icon">🌐</span> | |
Learn more about {breed} on AKC website | |
</a> | |
</div> | |
</div> | |
</div> | |
''' | |
def format_noise_items(items: List[str]) -> str: | |
"""Format noise-related items into HTML list items.""" | |
if not items: | |
return "<div class='list-item'>Information not available</div>" | |
return "\n".join([f"<div class='list-item'>• {item}</div>" for item in items]) | |
def format_health_items(items: List[str]) -> str: | |
"""Format health-related items into HTML list items.""" | |
if not items: | |
return "<div class='list-item'>Information not available</div>" | |
return "\n".join([f"<div class='list-item'>• {item}</div>" for item in items]) | |
def format_multiple_breeds_result( | |
topk_breeds: List[str], | |
relative_probs: List[str], | |
color: str, | |
index: int, | |
get_dog_description: Callable | |
) -> str: | |
"""Format multiple breed predictions into HTML with complete information.""" | |
result = f''' | |
<!-- 主標題區塊 --> | |
<div style="background: {color}10; border-radius: 12px 12px 0 0; padding: 20px;"> | |
<div style="display: flex; align-items: center;"> | |
<span style="font-size: 1.6em; color: {color}; margin-right: 12px;">🐾</span> | |
<span style="font-size: 1.4em; font-weight: 600; color: {color};">Dog {index}</span> | |
</div> | |
</div> | |
<!-- 內容區塊 --> | |
<div style="padding: 20px;"> | |
<!-- 不確定性提示 --> | |
<div style="margin-bottom: 20px;"> | |
<div style="display: flex; align-items: center; gap: 8px; padding: 12px; background: #f8f9fa; border-radius: 8px;"> | |
<span>ℹ️</span> | |
<span>Note: The model is showing some uncertainty in its predictions. | |
Here are the most likely breeds based on the available visual features.</span> | |
</div> | |
</div> | |
<!-- 品種列表容器 --> | |
<div class="breeds-list" style="display: grid; gap: 20px;"> | |
''' | |
for j, (breed, prob) in enumerate(zip(topk_breeds, relative_probs)): | |
description = get_dog_description(breed) | |
noise_info = breed_noise_info.get(breed, {}) | |
health_info = breed_health_info.get(breed, {}) | |
# 處理噪音資訊 | |
noise_notes = noise_info.get('noise_notes', '').split('\n') | |
noise_characteristics = [] | |
barking_triggers = [] | |
noise_level = noise_info.get('noise_level', 'Information not available') | |
in_section = None | |
for line in noise_notes: | |
line = line.strip() | |
if 'Typical noise characteristics:' in line: | |
in_section = 'characteristics' | |
elif 'Barking triggers:' in line: | |
in_section = 'triggers' | |
elif line.startswith('•'): | |
if in_section == 'characteristics': | |
noise_characteristics.append(line[1:].strip()) | |
elif in_section == 'triggers': | |
barking_triggers.append(line[1:].strip()) | |
# 處理健康資訊 | |
health_notes = health_info.get('health_notes', '').split('\n') | |
health_considerations = [] | |
health_screenings = [] | |
in_section = None | |
for line in health_notes: | |
line = line.strip() | |
if 'Common breed-specific health considerations' in line: | |
in_section = 'considerations' | |
elif 'Recommended health screenings:' in line: | |
in_section = 'screenings' | |
elif line.startswith('•'): | |
if in_section == 'considerations': | |
health_considerations.append(line[1:].strip()) | |
elif in_section == 'screenings': | |
health_screenings.append(line[1:].strip()) | |
result += f''' | |
<div class="dog-info-card" style="background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 48px;"> | |
<div style="border-left: 8px solid {color};"> | |
<!-- Title Section --> | |
<div class="breed-title" style="padding: 24px; background: #f8f9fa;"> | |
<div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #fff; border-radius: 8px; box-shadow: 0 1px 2px rgba(0,0,0,0.05);"> | |
<div style="display: flex; align-items: center; gap: 14px;"> | |
<span style="font-size: 1.4em;">🐾</span> | |
<h2 style="margin: 0; font-size: 1.8em; color: #1a202c; font-weight: 600;"> | |
{'Option ' + str(j+1) + ': ' if prob else ''}{breed} | |
</h2> | |
</div> | |
{f'<span style="background: {color}12; color: {color}; padding: 8px 16px; border-radius: 8px; font-size: 1em; font-weight: 500; box-shadow: 0 1px 2px {color}20;">Confidence: {prob}</span>' if prob else ''} | |
</div> | |
</div> | |
<div class="breed-info" style="padding: 24px;"> | |
<!-- Basic Information --> | |
<div style="margin-bottom: 32px;"> | |
<h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;"> | |
<span style="font-size: 1.2em;">📋</span> | |
<span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">BASIC INFORMATION</span> | |
</h3> | |
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px;"> | |
<!-- Size --> | |
<div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;"> | |
<div style="display: flex; align-items: center; gap: 10px;"> | |
<span style="font-size: 1.1em;">📏</span> | |
<span style="font-weight: 500;">Size</span> | |
<div style="position: relative; display: inline-block;" | |
onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';" | |
onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';"> | |
<span style="cursor: help; color: #718096;">ⓘ</span> | |
<div class="tooltip-content" style=" | |
visibility: hidden; | |
opacity: 0; | |
position: absolute; | |
background: #2C3E50; | |
color: white; | |
padding: 12px; | |
border-radius: 8px; | |
font-size: 14px; | |
width: 250px; | |
{f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'}; | |
z-index: 99999; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
pointer-events: none;"> | |
<strong style="display: block; margin-bottom: 8px; color: white;">Size Categories:</strong> | |
• Small: Under 20 pounds<br> | |
• Medium: 20-60 pounds<br> | |
• Large: Over 60 pounds<br> | |
• Giant: Over 100 pounds | |
<div style="position: absolute; | |
{f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'}; | |
width: 0; | |
height: 0; | |
border-left: 8px solid transparent; | |
border-right: 8px solid transparent;"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<span style="display: block; margin-top: 8px; color: #4a5568;">{description['Size']}</span> | |
</div> | |
<!-- Lifespan --> | |
<div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;"> | |
<div style="display: flex; align-items: center; gap: 10px;"> | |
<span style="font-size: 1.1em;">⏳</span> | |
<span style="font-weight: 500;">Lifespan</span> | |
<div style="position: relative; display: inline-block;" | |
onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';" | |
onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';"> | |
<span style="cursor: help; color: #718096;">ⓘ</span> | |
<div class="tooltip-content" style=" | |
visibility: hidden; | |
opacity: 0; | |
position: absolute; | |
background: #2C3E50; | |
color: white; | |
padding: 12px; | |
border-radius: 8px; | |
font-size: 14px; | |
width: 250px; | |
{f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'}; | |
z-index: 99999; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
pointer-events: none;"> | |
<strong style="display: block; margin-bottom: 8px; color: white;">Lifespan Categories:</strong> | |
• Short: 6-8 years<br> | |
• Average: 10-15 years<br> | |
• Long: 12-20 years | |
<div style="position: absolute; | |
{f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'}; | |
width: 0; | |
height: 0; | |
border-left: 8px solid transparent; | |
border-right: 8px solid transparent;"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<span style="display: block; margin-top: 8px; color: #4a5568;">{description['Lifespan']}</span> | |
</div> | |
</div> | |
</div> | |
<!-- Care Requirements --> | |
<div style="margin-bottom: 32px;"> | |
<h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;"> | |
<span style="font-size: 1.2em;">💪</span> | |
<span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">CARE REQUIREMENTS</span> | |
</h3> | |
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 24px;"> | |
<!-- Exercise --> | |
<div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;"> | |
<div style="display: flex; align-items: center; gap: 10px;"> | |
<span style="font-size: 1.1em;">🏃</span> | |
<span style="font-weight: 500;">Exercise</span> | |
<div style="position: relative; display: inline-block;" | |
onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';" | |
onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';"> | |
<span style="cursor: help; color: #718096;">ⓘ</span> | |
<div class="tooltip-content" style=" | |
visibility: hidden; | |
opacity: 0; | |
position: absolute; | |
background: #2C3E50; | |
color: white; | |
padding: 12px; | |
border-radius: 8px; | |
font-size: 14px; | |
width: 250px; | |
{f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'}; | |
z-index: 99999; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
pointer-events: none;"> | |
<strong style="display: block; margin-bottom: 8px; color: white;">Exercise Needs:</strong> | |
• Low: Short walks<br> | |
• Moderate: 1-2 hours daily<br> | |
• High: 2+ hours daily | |
<div style="position: absolute; | |
{f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'}; | |
width: 0; | |
height: 0; | |
border-left: 8px solid transparent; | |
border-right: 8px solid transparent;"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<span style="display: block; margin-top: 8px; color: #4a5568;">{description['Exercise Needs']}</span> | |
</div> | |
<!-- Grooming --> | |
<div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;"> | |
<div style="display: flex; align-items: center; gap: 10px;"> | |
<span style="font-size: 1.1em;">✂️</span> | |
<span style="font-weight: 500;">Grooming</span> | |
<div style="position: relative; display: inline-block;" | |
onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';" | |
onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';"> | |
<span style="cursor: help; color: #718096;">ⓘ</span> | |
<div class="tooltip-content" style=" | |
visibility: hidden; | |
opacity: 0; | |
position: absolute; | |
background: #2C3E50; | |
color: white; | |
padding: 12px; | |
border-radius: 8px; | |
font-size: 14px; | |
width: 250px; | |
{f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'}; | |
z-index: 99999; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
pointer-events: none;"> | |
<strong style="display: block; margin-bottom: 8px; color: white;">Grooming Requirements:</strong> | |
• Low: Basic brushing<br> | |
• Moderate: Weekly grooming<br> | |
• High: Daily maintenance | |
<div style="position: absolute; | |
{f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'}; | |
width: 0; | |
height: 0; | |
border-left: 8px solid transparent; | |
border-right: 8px solid transparent;"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<span style="display: block; margin-top: 8px; color: #4a5568;">{description['Grooming Needs']}</span> | |
</div> | |
<!-- Care Level --> | |
<div style="padding: 16px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0;"> | |
<div style="display: flex; align-items: center; gap: 10px;"> | |
<span style="font-size: 1.1em;">⭐</span> | |
<span style="font-weight: 500;">Care Level</span> | |
<div style="position: relative; display: inline-block;" | |
onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';" | |
onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';"> | |
<span style="cursor: help; color: #718096;">ⓘ</span> | |
<div class="tooltip-content" style=" | |
visibility: hidden; | |
opacity: 0; | |
position: absolute; | |
background: #2C3E50; | |
color: white; | |
padding: 12px; | |
border-radius: 8px; | |
font-size: 14px; | |
width: 250px; | |
{f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'}; | |
z-index: 99999; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
pointer-events: none;"> | |
<strong style="display: block; margin-bottom: 8px; color: white;">Care Level:</strong> | |
• Low: Basic care<br> | |
• Moderate: Regular care<br> | |
• High: Extensive care | |
<div style="position: absolute; | |
{f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'}; | |
width: 0; | |
height: 0; | |
border-left: 8px solid transparent; | |
border-right: 8px solid transparent;"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<span style="display: block; margin-top: 8px; color: #4a5568;">{description['Care Level']}</span> | |
</div> | |
</div> | |
</div> | |
<!-- Noise Behavior --> | |
<div style="margin-bottom: 32px;"> | |
<h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;"> | |
<span style="font-size: 1.2em;">🔊</span> | |
<span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">NOISE BEHAVIOR</span> | |
<div style="position: relative; display: inline-block;" | |
onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';" | |
onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';"> | |
<span style="cursor: help; color: #718096;">ⓘ</span> | |
<div class="tooltip-content" style=" | |
visibility: hidden; | |
opacity: 0; | |
position: absolute; | |
background: #2C3E50; | |
color: white; | |
padding: 12px; | |
border-radius: 8px; | |
font-size: 14px; | |
width: 250px; | |
{f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'}; | |
z-index: 99999; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
pointer-events: none;"> | |
<strong style="display: block; margin-bottom: 8px; color: white;">Noise Behavior:</strong> | |
• Typical vocalization patterns<br> | |
• Common triggers and frequency<br> | |
• Based on breed characteristics | |
<div style="position: absolute; | |
{f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'}; | |
width: 0; | |
height: 0; | |
border-left: 8px solid transparent; | |
border-right: 8px solid transparent;"> | |
</div> | |
</div> | |
</div> | |
</h3> | |
<div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0;"> | |
<div style="margin-bottom: 16px;"> | |
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px;"> | |
<span style="font-weight: 500;">Noise Level:</span> | |
<span>{noise_level}</span> | |
</div> | |
<div style="margin-bottom: 16px;"> | |
{format_noise_items(noise_characteristics[:2])} | |
</div> | |
</div> | |
<details style="margin-top: 20px;"> | |
<summary style="cursor: pointer; padding: 12px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e2e8f0; outline: none; list-style-type: none;"> | |
<span style="display: flex; align-items: center; justify-content: space-between;"> | |
<span style="font-weight: 500;">Show Complete Noise Information</span> | |
<span style="transition: transform 0.2s;">▶</span> | |
</span> | |
</summary> | |
<!-- 展開內容部分 --> | |
<div style="margin-top: 16px; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px; background: #fff;"> | |
<div style="margin-bottom: 24px;"> | |
<h4 style="margin: 0 0 12px 0; color: #2d3748;">All Characteristics</h4> | |
{format_noise_items(noise_characteristics)} | |
</div> | |
<div style="margin-bottom: 24px;"> | |
<h4 style="margin: 0 0 12px 0; color: #2d3748;">Barking Triggers</h4> | |
{format_noise_items(barking_triggers)} | |
</div> | |
<div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid #e2e8f0;"> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Source: Compiled from various breed behavior resources, 2024</p> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Individual dogs may vary in their vocalization patterns.</p> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Training can significantly influence barking behavior.</p> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Environmental factors may affect noise levels.</p> | |
</div> | |
</div> | |
</details> | |
</div> | |
</div> | |
<!-- Health Insights --> | |
<div style="margin-bottom: 32px;"> | |
<h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;"> | |
<span style="font-size: 1.2em;">🏥</span> | |
<span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">HEALTH INSIGHTS</span> | |
<div style="position: relative; display: inline-block;" | |
onmouseover="this.querySelector('.tooltip-content').style.visibility='visible';this.querySelector('.tooltip-content').style.opacity='1';" | |
onmouseout="this.querySelector('.tooltip-content').style.visibility='hidden';this.querySelector('.tooltip-content').style.opacity='0';"> | |
<span style="cursor: help; color: #718096;">ⓘ</span> | |
<div class="tooltip-content" style=" | |
visibility: hidden; | |
opacity: 0; | |
position: absolute; | |
background: #2C3E50; | |
color: white; | |
padding: 12px; | |
border-radius: 8px; | |
font-size: 14px; | |
width: 250px; | |
{f'top: -130px; left: 0;' if not prob else 'top: 50%; right: -270px; transform: translateY(-50%); left: auto;'}; | |
z-index: 99999; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
pointer-events: none;"> | |
<strong style="display: block; margin-bottom: 8px; color: white;">Health Information:</strong> | |
• Common breed-specific conditions<br> | |
• Recommended health screenings<br> | |
• General health considerations | |
<div style="position: absolute; | |
{f'left: 20px; bottom: -8px; border-top: 8px solid #2C3E50;' if not prob else 'top: 50%; right: 100%; transform: translateY(-50%); border-right: 8px solid #2C3E50;'}; | |
width: 0; | |
height: 0; | |
border-left: 8px solid transparent; | |
border-right: 8px solid transparent;"> | |
</div> | |
</div> | |
</div> | |
</h3> | |
<div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0;"> | |
<div style="margin-bottom: 16px;"> | |
{format_health_items(health_considerations[:2])} | |
</div> | |
<details style="margin-top: 20px;"> | |
<summary style="cursor: pointer; padding: 12px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e2e8f0; outline: none; list-style-type: none;"> | |
<span style="display: flex; align-items: center; justify-content: space-between;"> | |
<span style="font-weight: 500;">Show Complete Health Information</span> | |
<span style="transition: transform 0.2s;">▶</span> | |
</span> | |
</summary> | |
<div style="margin-top: 16px; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px; background: #fff;"> | |
<div style="margin-bottom: 24px;"> | |
<h4 style="margin: 0 0 12px 0; color: #2d3748;">All Health Considerations</h4> | |
{format_health_items(health_considerations)} | |
</div> | |
<div style="margin-bottom: 24px;"> | |
<h4 style="margin: 0 0 12px 0; color: #2d3748;">Recommended Screenings</h4> | |
{format_health_items(health_screenings)} | |
</div> | |
<div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid #e2e8f0;"> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Source: Compiled from veterinary resources and breed health studies, 2024</p> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Regular vet check-ups are essential for all breeds.</p> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Early detection and prevention are key to managing health issues.</p> | |
<p style="margin: 0 0 8px 0; color: #4a5568; font-size: 0.9em;">Not all dogs will develop these conditions.</p> | |
</div> | |
</div> | |
</details> | |
</div> | |
</div> | |
<!-- Description --> | |
<div style="margin-bottom: 32px;"> | |
<h3 style="display: flex; align-items: center; gap: 10px; margin: 0 0 20px 0; padding: 12px; background: #f8f9fa; border-radius: 6px;"> | |
<span style="font-size: 1.2em;">📝</span> | |
<span style="font-size: 1.2em; font-weight: 600; color: #2d3748;">DESCRIPTION</span> | |
</h3> | |
<div style="background: #fff; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0;"> | |
<p style="line-height: 1.6; margin: 0; color: #2d3748;">{description.get('Description', '')}</p> | |
</div> | |
</div> | |
<!-- Action Section --> | |
<div style="text-align: center; margin-top: 32px;"> | |
<a href="{get_akc_breeds_link(breed)}" | |
target="_blank" | |
rel="noopener noreferrer" | |
style="display: inline-flex; align-items: center; gap: 8px; padding: 14px 28px; background: linear-gradient(90deg, #4299e1, #48bb78); color: white; text-decoration: none; border-radius: 8px; font-weight: 500; transition: opacity 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> | |
<span style="font-size: 1.2em;">🌐</span> | |
Learn more about {breed} on AKC website | |
</a> | |
</div> | |
</div> | |
</div> | |
</div> | |
''' | |
return result | |
def format_multi_dog_container(dogs_info: str) -> str: | |
"""Wrap multiple dog detection results in a container.""" | |
return f""" | |
<div class="dog-info-card"> | |
{dogs_info} | |
</div> | |
""" | |
def format_breed_details_html(description: Dict[str, Any], breed: str) -> str: | |
"""Format breed details for the show_details_html function.""" | |
return f""" | |
<div class="dog-info"> | |
<h2>{breed}</h2> | |
<div class="breed-details"> | |
{format_description_html(description, breed)} | |
<div class="action-section"> | |
<a href="{get_akc_breeds_link(breed)}" target="_blank" class="akc-button"> | |
<span class="icon">🌐</span> | |
Learn more about {breed} on AKC website | |
</a> | |
</div> | |
</div> | |
</div> | |
""" | |
def format_comparison_result(breed1: str, breed2: str, comparison_data: Dict) -> str: | |
"""Format breed comparison results into HTML.""" | |
return f""" | |
<div class="comparison-container"> | |
<div class="comparison-header"> | |
<h3>Comparison: {breed1} vs {breed2}</h3> | |
</div> | |
<div class="comparison-content"> | |
<div class="breed-column"> | |
<h4>{breed1}</h4> | |
{format_comparison_details(comparison_data[breed1])} | |
</div> | |
<div class="breed-column"> | |
<h4>{breed2}</h4> | |
{format_comparison_details(comparison_data[breed2])} | |
</div> | |
</div> | |
</div> | |
""" | |
def format_comparison_details(breed_data: Dict) -> str: | |
"""Format individual breed details for comparison.""" | |
original_data = breed_data.get('Original_Data', {}) | |
return f""" | |
<div class="comparison-details"> | |
<p><strong>Size:</strong> {original_data.get('Size', 'N/A')}</p> | |
<p><strong>Exercise Needs:</strong> {original_data.get('Exercise Needs', 'N/A')}</p> | |
<p><strong>Care Level:</strong> {original_data.get('Care Level', 'N/A')}</p> | |
<p><strong>Grooming Needs:</strong> {original_data.get('Grooming Needs', 'N/A')}</p> | |
<p><strong>Good with Children:</strong> {original_data.get('Good with Children', 'N/A')}</p> | |
<p><strong>Temperament:</strong> {original_data.get('Temperament', 'N/A')}</p> | |
</div> | |
""" | |
def format_header_html() -> str: | |
"""Format the application header HTML.""" | |
return """ | |
<header style='text-align: center; padding: 20px; margin-bottom: 20px;'> | |
<h1 style='font-size: 2.5em; margin-bottom: 10px; color: #2D3748;'> | |
🐾 PawMatch AI | |
</h1> | |
<h2 style='font-size: 1.2em; font-weight: normal; color: #4A5568; margin-top: 5px;'> | |
Your Smart Dog Breed Guide | |
</h2> | |
<div style='width: 50px; height: 3px; background: linear-gradient(90deg, #4299e1, #48bb78); margin: 15px auto;'></div> | |
<p style='color: #718096; font-size: 0.9em;'> | |
Powered by AI • Breed Recognition • Smart Matching • Companion Guide | |
</p> | |
</header> | |
""" | |