import sqlite3 import gradio as gr 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 from smart_breed_matcher import SmartBreedMatcher from description_search_ui import create_description_search_tab 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("""

Tell us about your lifestyle, and we'll recommend the perfect dog breeds for you!

""") 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") with gr.Tab("Find by Description"): description_input, description_search_btn, description_output, loading_msg = create_description_search_tab() 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], 'exercise_time': args[1], 'grooming_commitment': args[2], 'experience_level': args[3], 'has_children': args[4], 'noise_tolerance': args[5], 'health_sensitivity': "medium", 'barking_acceptance': args[5] }, 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" def on_description_search(description: str): try: # 初始化匹配器 matcher = SmartBreedMatcher(dog_data) breed_recommendations = matcher.match_user_preference(description, top_n=10) # 從描述中提取用戶偏好 user_prefs = UserPreferences( living_space="apartment" if any(word in description.lower() for word in ["apartment", "flat", "condo"]) else "house_small", exercise_time=120 if any(word in description.lower() for word in ["active", "exercise", "running", "athletic", "high energy"]) else 60, grooming_commitment="high" if any(word in description.lower() for word in ["grooming", "brush", "maintain"]) else "medium", experience_level="experienced" if any(word in description.lower() for word in ["experienced", "trained", "professional"]) else "intermediate", has_children=any(word in description.lower() for word in ["children", "kids", "family", "child"]), noise_tolerance="low" if any(word in description.lower() for word in ["quiet", "peaceful", "silent"]) else "medium", space_for_play=any(word in description.lower() for word in ["yard", "garden", "outdoor", "space"]), other_pets=any(word in description.lower() for word in ["other pets", "cats", "dogs"]), climate="moderate", health_sensitivity="high" if any(word in description.lower() for word in ["health", "medical", "sensitive"]) else "medium", barking_acceptance="low" if any(word in description.lower() for word in ["quiet", "no barking"]) else None ) final_recommendations = [] for smart_rec in breed_recommendations: breed_name = smart_rec['breed'] breed_info = get_dog_description(breed_name) if not isinstance(breed_info, dict): continue # 獲取基礎分數 base_score = smart_rec.get('base_score', 0.7) similarity = smart_rec.get('similarity', 0) is_preferred = smart_rec.get('is_preferred', False) bonus_reasons = [] bonus_score = 0 # 1. 尺寸評估 size = breed_info.get('Size', '') if size in ['Small', 'Tiny']: if "apartment" in description.lower(): bonus_score += 0.05 bonus_reasons.append("Suitable size for apartment (+5%)") else: bonus_score -= 0.25 bonus_reasons.append("Size too small (-25%)") elif size == 'Medium': bonus_score += 0.15 bonus_reasons.append("Ideal size (+15%)") elif size == 'Large': if "apartment" in description.lower(): bonus_score -= 0.05 bonus_reasons.append("May be too large for apartment (-5%)") elif size == 'Giant': bonus_score -= 0.20 bonus_reasons.append("Size too large (-20%)") # 2. 運動需求評估 exercise_needs = breed_info.get('Exercise_Needs', '') if any(word in description.lower() for word in ['active', 'energetic', 'running']): if exercise_needs in ['High', 'Very High']: bonus_score += 0.20 bonus_reasons.append("Exercise needs match (+20%)") elif exercise_needs == 'Low': bonus_score -= 0.15 bonus_reasons.append("Insufficient exercise level (-15%)") else: if exercise_needs == 'Moderate': bonus_score += 0.10 bonus_reasons.append("Moderate exercise needs (+10%)") # 3. 美容需求評估 grooming = breed_info.get('Grooming_Needs', '') if user_prefs.grooming_commitment == "high": if grooming == 'High': bonus_score += 0.10 bonus_reasons.append("High grooming match (+10%)") else: if grooming == 'High': bonus_score -= 0.15 bonus_reasons.append("High grooming needs (-15%)") elif grooming == 'Low': bonus_score += 0.10 bonus_reasons.append("Low grooming needs (+10%)") # 4. 家庭適應性評估 if user_prefs.has_children: if breed_info.get('Good_With_Children'): bonus_score += 0.15 bonus_reasons.append("Excellent with children (+15%)") temperament = breed_info.get('Temperament', '').lower() if any(trait in temperament for trait in ['gentle', 'patient', 'friendly']): bonus_score += 0.05 bonus_reasons.append("Family-friendly temperament (+5%)") # 5. 噪音評估 if user_prefs.noise_tolerance == "low": noise_level = breed_noise_info.get(breed_name, {}).get('noise_level', 'Unknown') if noise_level == 'High': bonus_score -= 0.10 bonus_reasons.append("High noise level (-10%)") elif noise_level == 'Low': bonus_score += 0.10 bonus_reasons.append("Low noise level (+10%)") # 6. 健康考慮 if user_prefs.health_sensitivity == "high": health_score = smart_rec.get('health_score', 0.5) if health_score > 0.8: bonus_score += 0.10 bonus_reasons.append("Excellent health score (+10%)") elif health_score < 0.5: bonus_score -= 0.10 bonus_reasons.append("Health concerns (-10%)") # 7. 品種偏好獎勵 if is_preferred: bonus_score += 0.15 bonus_reasons.append("Directly mentioned breed (+15%)") elif similarity > 0.8: bonus_score += 0.10 bonus_reasons.append("Very similar to preferred breed (+10%)") # 計算最終分數 final_score = min(0.95, base_score + bonus_score) space_score = _calculate_space_compatibility( breed_info.get('Size', 'Medium'), user_prefs.living_space ) exercise_score = _calculate_exercise_compatibility( breed_info.get('Exercise_Needs', 'Moderate'), user_prefs.exercise_time ) grooming_score = _calculate_grooming_compatibility( breed_info.get('Grooming_Needs', 'Moderate'), user_prefs.grooming_commitment ) experience_score = _calculate_experience_compatibility( breed_info.get('Care_Level', 'Moderate'), user_prefs.experience_level ) scores = { 'overall': final_score, 'space': space_score, 'exercise': exercise_score, 'grooming': grooming_score, 'experience': experience_score, 'noise': smart_rec.get('scores', {}).get('noise', 0.0), 'health': smart_rec.get('health_score', 0.5), 'temperament': smart_rec.get('scores', {}).get('temperament', 0.0) } final_recommendations.append({ 'rank': 0, 'breed': breed_name, 'scores': scores, 'base_score': round(base_score, 4), 'bonus_score': round(bonus_score, 4), 'final_score': round(final_score, 4), 'match_reason': ' • '.join(bonus_reasons) if bonus_reasons else "Standard match", 'info': breed_info, 'noise_info': breed_noise_info.get(breed_name, {}), 'health_info': breed_health_info.get(breed_name, {}) }) # 根據最終分數排序 final_recommendations.sort(key=lambda x: (-x['final_score'], x['breed'])) # 更新排名 for i, rec in enumerate(final_recommendations, 1): rec['rank'] = i # 保存到歷史記錄 history_results = [{ 'breed': rec['breed'], 'rank': rec['rank'], 'final_score': rec['final_score'] } for rec in final_recommendations[:10]] history_component.save_search( user_preferences=None, results=history_results, search_type="description", description=description ) result = format_recommendation_html(final_recommendations, is_description_search=True) return [gr.update(value=result), gr.update(visible=False)] except Exception as e: error_msg = f"Error processing your description. Details: {str(e)}" return [gr.update(value=error_msg), gr.update(visible=False)] def _calculate_space_compatibility(size: str, living_space: str) -> float: """住宿空間適應性評分""" if living_space == "apartment": scores = { 'Tiny': 0.6, # 公寓可以,但不是最佳 'Small': 0.8, # 公寓較好 'Medium': 1.0, # 最佳選擇 'Medium-Large': 0.6, # 可能有點大 'Large': 0.4, # 太大了 'Giant': 0.2 # 不建議 } else: # house scores = { 'Tiny': 0.4, # 房子太大了 'Small': 0.6, # 可以但不是最佳 'Medium': 0.8, # 不錯的選擇 'Medium-Large': 1.0, # 最佳選擇 'Large': 0.9, # 也很好 'Giant': 0.7 # 可以考慮 } return scores.get(size, 0.5) def _calculate_exercise_compatibility(exercise_needs: str, exercise_time: int) -> float: """運動需求相容性評分""" # 轉換運動時間到評分標準 if exercise_time >= 120: # 高運動量 scores = { 'Very High': 1.0, 'High': 0.8, 'Moderate': 0.5, 'Low': 0.2 } elif exercise_time >= 60: # 中等運動量 scores = { 'Very High': 0.5, 'High': 0.7, 'Moderate': 1.0, 'Low': 0.8 } else: # 低運動量 scores = { 'Very High': 0.2, 'High': 0.4, 'Moderate': 0.7, 'Low': 1.0 } return scores.get(exercise_needs, 0.5) def _calculate_grooming_compatibility(grooming_needs: str, grooming_commitment: str) -> float: """美容需求相容性評分""" if grooming_commitment == "high": scores = { 'High': 1.0, 'Moderate': 0.8, 'Low': 0.5 } elif grooming_commitment == "medium": scores = { 'High': 0.6, 'Moderate': 1.0, 'Low': 0.8 } else: # low scores = { 'High': 0.3, 'Moderate': 0.6, 'Low': 1.0 } return scores.get(grooming_needs, 0.5) def _calculate_experience_compatibility(care_level: str, experience_level: str) -> float: if experience_level == "experienced": care_scores = { 'High': 1.0, 'Moderate': 0.8, 'Low': 0.6 } elif experience_level == "intermediate": care_scores = { 'High': 0.6, 'Moderate': 1.0, 'Low': 0.8 } else: # beginner care_scores = { 'High': 0.3, 'Moderate': 0.7, 'Low': 1.0 } return care_scores.get(care_level, 0.7) def show_loading(): return [gr.update(value=""), gr.update(visible=True)] 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 ) description_search_btn.click( fn=show_loading, # 先顯示加載消息 outputs=[description_output, loading_msg] ).then( # 然後執行搜索 fn=on_description_search, inputs=[description_input], outputs=[description_output, loading_msg] ) 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, 'description_input': description_input, 'description_search_btn': description_search_btn, 'description_output': description_output }