PawMatchAI / breed_recommendation.py
DawnC's picture
Update breed_recommendation.py
fb97212
raw
history blame
33.7 kB
import sqlite3
import gradio as gr
import asyncio
from typing import Generator
from dog_database import get_dog_description, dog_data
from breed_health_info import breed_health_info
from breed_noise_info import breed_noise_info
from scoring_calculation_system import UserPreferences, calculate_compatibility_score
from recommendation_html_format import format_recommendation_html, get_breed_recommendations
from search_history import create_history_tab, create_history_component
# def create_recommendation_tab(UserPreferences, get_breed_recommendations, format_recommendation_html, history_component):
# with gr.TabItem("Breed Recommendation"):
# with gr.Tabs():
# with gr.Tab("Find by Criteria"):
# gr.HTML("""
# <div style='
# text-align: center;
# position: relative;
# padding: 20px 0;
# margin: 15px 0;
# background: linear-gradient(to right, rgba(66, 153, 225, 0.1), rgba(72, 187, 120, 0.1));
# border-radius: 10px;
# '>
# <!-- BETA 標籤 -->
# <div style='
# position: absolute;
# top: 10px;
# right: 20px;
# background: linear-gradient(90deg, #4299e1, #48bb78);
# color: white;
# padding: 4px 12px;
# border-radius: 15px;
# font-size: 0.85em;
# font-weight: 600;
# letter-spacing: 1px;
# box-shadow: 0 2px 4px rgba(0,0,0,0.1);
# '>BETA</div>
# <!-- 主標題 -->
# <p style='
# font-size: 1.2em;
# margin: 0;
# padding: 0 20px;
# line-height: 1.5;
# background: linear-gradient(90deg, #4299e1, #48bb78);
# -webkit-background-clip: text;
# -webkit-text-fill-color: transparent;
# font-weight: 600;
# '>
# Tell us about your lifestyle, and we'll recommend the perfect dog breeds for you!
# </p>
# <!-- 提示訊息 -->
# <div style='
# margin-top: 15px;
# padding: 10px 20px;
# background: linear-gradient(to right, rgba(66, 153, 225, 0.15), rgba(72, 187, 120, 0.15));
# border-radius: 8px;
# font-size: 0.9em;
# color: #2D3748;
# display: flex;
# align-items: center;
# justify-content: center;
# gap: 8px;
# '>
# <span style="font-size: 1.2em;">🔬</span>
# <span style="
# letter-spacing: 0.3px;
# line-height: 1.4;
# "><strong>Beta Feature:</strong> Our matching algorithm is continuously improving. Results are for reference only.</span>
# </div>
# </div>
# """)
# with gr.Row():
# with gr.Column():
# living_space = gr.Radio(
# choices=["apartment", "house_small", "house_large"],
# label="What type of living space do you have?",
# info="Choose your current living situation",
# value="apartment"
# )
# yard_access = gr.Radio(
# choices=["no_yard", "shared_yard", "private_yard"],
# label="Yard Access Type",
# info="Available outdoor space",
# value="no_yard"
# )
# exercise_time = gr.Slider(
# minimum=0,
# maximum=180,
# value=60,
# label="Daily exercise time (minutes)",
# info="Consider walks, play time, and training"
# )
# exercise_type = gr.Radio(
# choices=["light_walks", "moderate_activity", "active_training"],
# label="Exercise Style",
# info="What kind of activities do you prefer?",
# value="moderate_activity"
# )
# grooming_commitment = gr.Radio(
# choices=["low", "medium", "high"],
# label="Grooming commitment level",
# info="Low: monthly, Medium: weekly, High: daily",
# value="medium"
# )
# with gr.Column():
# experience_level = gr.Radio(
# choices=["beginner", "intermediate", "advanced"],
# label="Dog ownership experience",
# info="Be honest - this helps find the right match",
# value="beginner"
# )
# time_availability = gr.Radio(
# choices=["limited", "moderate", "flexible"],
# label="Time Availability",
# info="Time available for dog care daily",
# value="moderate"
# )
# has_children = gr.Checkbox(
# label="Have children at home",
# info="Helps recommend child-friendly breeds"
# )
# children_age = gr.Radio(
# choices=["toddler", "school_age", "teenager"],
# label="Children's Age Group",
# info="Helps match with age-appropriate breeds",
# visible=False # 默認隱藏,只在has_children=True時顯示
# )
# noise_tolerance = gr.Radio(
# choices=["low", "medium", "high"],
# label="Noise tolerance level",
# info="Some breeds are more vocal than others",
# value="medium"
# )
# def update_children_age_visibility(has_children):
# return gr.update(visible=has_children)
# has_children.change(
# fn=update_children_age_visibility,
# inputs=has_children,
# outputs=children_age
# )
# get_recommendations_btn = gr.Button("Find My Perfect Match! 🔍", variant="primary")
# recommendation_output = gr.HTML(
# label="Breed Recommendations",
# visible=True, # 確保可見性
# elem_id="recommendation-output"
# )
# def on_find_match_click(*args):
# try:
# user_prefs = UserPreferences(
# living_space=args[0],
# yard_access=args[1],
# exercise_time=args[2],
# exercise_type=args[3],
# grooming_commitment=args[4],
# experience_level=args[5],
# time_availability=args[6],
# has_children=args[7],
# children_age=args[8] if args[7] else None,
# noise_tolerance=args[9],
# space_for_play=True if args[0] != "apartment" else False,
# other_pets=False,
# climate="moderate",
# health_sensitivity="medium",
# barking_acceptance=args[9]
# )
# recommendations = get_breed_recommendations(user_prefs, top_n=10)
# history_results = [{
# 'breed': rec['breed'],
# 'rank': rec['rank'],
# 'overall_score': rec['final_score'],
# 'base_score': rec['base_score'],
# 'bonus_score': rec['bonus_score'],
# 'scores': rec['scores']
# } for rec in recommendations]
# history_component.save_search(
# user_preferences={
# 'living_space': args[0],
# 'yard_access': args[1],
# 'exercise_time': args[2],
# 'exercise_type': args[3],
# 'grooming_commitment': args[4],
# 'experience_level': args[5],
# 'time_availability': args[6],
# 'has_children': args[7],
# 'children_age': args[8] if args[7] else None,
# 'noise_tolerance': args[9],
# 'search_type': 'Criteria'
# },
# results=history_results
# )
# return format_recommendation_html(recommendations, is_description_search=False)
# except Exception as e:
# print(f"Error in find match: {str(e)}")
# import traceback
# print(traceback.format_exc())
# return "Error getting recommendations"
# get_recommendations_btn.click(
# fn=on_find_match_click,
# inputs=[
# living_space,
# yard_access,
# exercise_time,
# exercise_type,
# grooming_commitment,
# experience_level,
# time_availability,
# has_children,
# children_age,
# noise_tolerance
# ],
# outputs=recommendation_output
# )
# return {
# 'living_space': living_space,
# 'exercise_time': exercise_time,
# 'grooming_commitment': grooming_commitment,
# 'experience_level': experience_level,
# 'has_children': has_children,
# 'noise_tolerance': noise_tolerance,
# 'get_recommendations_btn': get_recommendations_btn,
# 'recommendation_output': recommendation_output,
# }
def create_recommendation_tab(UserPreferences, get_breed_recommendations, format_recommendation_html, history_component):
"""
創建狗品種推薦頁面的主要函數
包含用戶輸入界面、推薦結果顯示和 loading 狀態
"""
with gr.TabItem("Breed Recommendation"):
with gr.Tabs():
with gr.Tab("Find by Criteria"):
# 頁面頂部的介紹性內容
gr.HTML("""
<div style='
text-align: center;
position: relative;
padding: 20px 0;
margin: 15px 0;
background: linear-gradient(to right, rgba(66, 153, 225, 0.1), rgba(72, 187, 120, 0.1));
border-radius: 10px;
'>
<!-- BETA 標籤 -->
<div style='
position: absolute;
top: 10px;
right: 20px;
background: linear-gradient(90deg, #4299e1, #48bb78);
color: white;
padding: 4px 12px;
border-radius: 15px;
font-size: 0.85em;
font-weight: 600;
letter-spacing: 1px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
'>BETA</div>
<!-- 主標題 -->
<p style='
font-size: 1.2em;
margin: 0;
padding: 0 20px;
line-height: 1.5;
background: linear-gradient(90deg, #4299e1, #48bb78);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 600;
'>
Tell us about your lifestyle, and we'll recommend the perfect dog breeds for you!
</p>
<!-- 提示訊息 -->
<div style='
margin-top: 15px;
padding: 10px 20px;
background: linear-gradient(to right, rgba(66, 153, 225, 0.15), rgba(72, 187, 120, 0.15));
border-radius: 8px;
font-size: 0.9em;
color: #2D3748;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
'>
<span style="font-size: 1.2em;">🔬</span>
<span style="
letter-spacing: 0.3px;
line-height: 1.4;
"><strong>Beta Feature:</strong> Our matching algorithm is continuously improving. Results are for reference only.</span>
</div>
</div>
""")
# 添加 Loading 狀態顯示
loading_html = gr.HTML("""
<div id="loading-status" style="
text-align: center;
margin: 30px 0;
opacity: 0;
transition: opacity 0.3s ease-in-out;
">
<div style="
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 15px;
padding: 25px;
background: linear-gradient(to right, rgba(66, 153, 225, 0.1), rgba(72, 187, 120, 0.1));
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
">
<!-- 可愛的狗狗圖示 -->
<div style="font-size: 40px;">🐕</div>
<!-- 載入中主要訊息 -->
<div style="
color: #2D3748;
font-size: 1.2em;
font-weight: 500;
margin-bottom: 5px;
">Sniffing out your perfect match...</div>
<!-- 有趣的載入訊息 -->
<div id="loading-message" style="
color: #4A5568;
font-size: 0.95em;
font-style: italic;
">Checking all the good boys and girls...</div>
<!-- 載入動畫 -->
<div style="
display: flex;
gap: 8px;
margin-top: 10px;
">
<div class="paw" style="
width: 12px;
height: 12px;
background: #4299e1;
border-radius: 50%;
animation: pawAnimation 1s infinite;
animation-delay: 0s;
"></div>
<div class="paw" style="
width: 12px;
height: 12px;
background: #4299e1;
border-radius: 50%;
animation: pawAnimation 1s infinite;
animation-delay: 0.2s;
"></div>
<div class="paw" style="
width: 12px;
height: 12px;
background: #4299e1;
border-radius: 50%;
animation: pawAnimation 1s infinite;
animation-delay: 0.4s;
"></div>
</div>
</div>
</div>
<style>
@keyframes pawAnimation {
0% { transform: translateY(0); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0); }
}
#loading-status {
display: none;
}
#loading-status.visible {
display: block;
opacity: 1;
}
</style>
<script>
// 有趣的載入訊息列表
const messages = [
"Checking all the good boys and girls...",
"Fetching the perfect matches...",
"Calculating belly rub compatibility...",
"Analyzing tail wag frequencies...",
"Measuring treat enthusiasm levels...",
"Evaluating walkies requirements...",
"Consulting our canine experts...",
"Counting tennis balls...",
"Reviewing park visit preferences...",
"Assessing cuddle potential..."
];
// 定期更新載入訊息
function updateLoadingMessage() {
const messageElement = document.getElementById('loading-message');
if (messageElement) {
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
messageElement.textContent = randomMessage;
}
}
setInterval(updateLoadingMessage, 2000);
</script>
""", visible=False)
# 用戶輸入區域
with gr.Row():
with gr.Column():
living_space = gr.Radio(
choices=["apartment", "house_small", "house_large"],
label="What type of living space do you have?",
info="Choose your current living situation",
value="apartment"
)
yard_access = gr.Radio(
choices=["no_yard", "shared_yard", "private_yard"],
label="Yard Access Type",
info="Available outdoor space",
value="no_yard"
)
exercise_time = gr.Slider(
minimum=0,
maximum=180,
value=60,
label="Daily exercise time (minutes)",
info="Consider walks, play time, and training"
)
exercise_type = gr.Radio(
choices=["light_walks", "moderate_activity", "active_training"],
label="Exercise Style",
info="What kind of activities do you prefer?",
value="moderate_activity"
)
grooming_commitment = gr.Radio(
choices=["low", "medium", "high"],
label="Grooming commitment level",
info="Low: monthly, Medium: weekly, High: daily",
value="medium"
)
with gr.Column():
experience_level = gr.Radio(
choices=["beginner", "intermediate", "advanced"],
label="Dog ownership experience",
info="Be honest - this helps find the right match",
value="beginner"
)
time_availability = gr.Radio(
choices=["limited", "moderate", "flexible"],
label="Time Availability",
info="Time available for dog care daily",
value="moderate"
)
has_children = gr.Checkbox(
label="Have children at home",
info="Helps recommend child-friendly breeds"
)
children_age = gr.Radio(
choices=["toddler", "school_age", "teenager"],
label="Children's Age Group",
info="Helps match with age-appropriate breeds",
visible=False
)
noise_tolerance = gr.Radio(
choices=["low", "medium", "high"],
label="Noise tolerance level",
info="Some breeds are more vocal than others",
value="medium"
)
# 控制 children_age 顯示邏輯
def update_children_age_visibility(has_children):
return gr.update(visible=has_children)
has_children.change(
fn=update_children_age_visibility,
inputs=has_children,
outputs=children_age
)
# 推薦按鈕
get_recommendations_btn = gr.Button(
"Find My Perfect Match! 🔍",
variant="primary"
)
# 推薦結果顯示區域
recommendation_output = gr.HTML(
label="Breed Recommendations",
visible=True,
elem_id="recommendation-output"
)
def on_find_match_click(*args) -> Generator:
"""
處理推薦按鈕點擊事件的函數
使用生成器來處理中間狀態和最終結果
"""
try:
# 首先返回 loading 狀態
yield gr.HTML("""
<div style="
text-align: center;
padding: 20px;
background: linear-gradient(to right, rgba(66, 153, 225, 0.1), rgba(72, 187, 120, 0.1));
border-radius: 10px;
margin: 20px 0;
animation: fadeIn 0.5s ease-in-out;
">
<div style="font-size: 40px; margin-bottom: 10px;">🐕</div>
<div style="
color: #2D3748;
font-size: 1.2em;
margin-bottom: 15px;
font-weight: 500;
">Finding your perfect match...</div>
<!-- 進度指示動畫 -->
<div style="
display: flex;
justify-content: center;
gap: 8px;
margin: 15px 0;
">
<div class="paw-print" style="
width: 12px;
height: 12px;
background: #4299e1;
border-radius: 50%;
animation: bounce 0.6s infinite ease-in-out;
animation-delay: 0s;
"></div>
<div class="paw-print" style="
width: 12px;
height: 12px;
background: #4299e1;
border-radius: 50%;
animation: bounce 0.6s infinite ease-in-out;
animation-delay: 0.2s;
"></div>
<div class="paw-print" style="
width: 12px;
height: 12px;
background: #4299e1;
border-radius: 50%;
animation: bounce 0.6s infinite ease-in-out;
animation-delay: 0.4s;
"></div>
</div>
<!-- 提示訊息 -->
<div style="
color: #4A5568;
font-size: 0.9em;
font-style: italic;
margin-top: 10px;
">Analyzing your preferences and our database of good boys and girls...</div>
</div>
<style>
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
""")
# 添加短暫延遲使 loading 動畫可見
yield asyncio.sleep(1)
# 創建用戶偏好對象並獲取推薦
user_prefs = UserPreferences(
living_space=args[0],
yard_access=args[1],
exercise_time=args[2],
exercise_type=args[3],
grooming_commitment=args[4],
experience_level=args[5],
time_availability=args[6],
has_children=args[7],
children_age=args[8] if args[7] else None,
noise_tolerance=args[9],
space_for_play=True if args[0] != "apartment" else False,
other_pets=False,
climate="moderate",
health_sensitivity="medium",
barking_acceptance=args[9]
)
# 獲取推薦結果
recommendations = get_breed_recommendations(user_prefs, top_n=10)
# 儲存搜尋歷史
history_results = [{
'breed': rec['breed'],
'rank': rec['rank'],
'overall_score': rec['final_score'],
'base_score': rec['base_score'],
'bonus_score': rec['bonus_score'],
'scores': rec['scores']
} for rec in recommendations]
history_component.save_search(
user_preferences={
'living_space': args[0],
'yard_access': args[1],
'exercise_time': args[2],
'exercise_type': args[3],
'grooming_commitment': args[4],
'experience_level': args[5],
'time_availability': args[6],
'has_children': args[7],
'children_age': args[8] if args[7] else None,
'noise_tolerance': args[9],
'search_type': 'Criteria'
},
results=history_results
)
# 返回最終結果
yield format_recommendation_html(recommendations, is_description_search=False)
except Exception as e:
print(f"Error in find match: {str(e)}")
import traceback
print(traceback.format_exc())
yield gr.HTML("""
<div style="
text-align: center;
padding: 20px;
background: rgba(255, 0, 0, 0.1);
border-radius: 10px;
margin: 20px 0;
">
<p style="
color: #d32f2f;
font-size: 1.1em;
margin: 0;
">
🐾 Oops! Something went wrong while finding your perfect match.
<br>Please try again!
</p>
</div>
""")
# 設置按鈕點擊事件
get_recommendations_btn.click(
fn=on_find_match_click,
inputs=[
living_space,
yard_access,
exercise_time,
exercise_type,
grooming_commitment,
experience_level,
time_availability,
has_children,
children_age,
noise_tolerance
],
outputs=recommendation_output
)
# 返回頁面組件
return {
'living_space': living_space,
'exercise_time': exercise_time,
'grooming_commitment': grooming_commitment,
'experience_level': experience_level,
'has_children': has_children,
'noise_tolerance': noise_tolerance,
'get_recommendations_btn': get_recommendations_btn,
'recommendation_output': recommendation_output,
}