import gradio as gr from typing import Dict, List, Any, Optional import traceback import spaces from semantic_breed_recommender import get_breed_recommendations_by_description, get_enhanced_recommendations_with_unified_scoring from natural_language_processor import get_nlp_processor from recommendation_html_format import format_unified_recommendation_html def create_description_examples(): """Create HTML for description examples with dynamic visibility""" return """

💡 Example Descriptions - Try These Expression Styles:

🏡 Active Lifestyle & Space:
"I live in a large house with a big backyard, and I love hiking and outdoor activities. I don't mind if the dog is noisy, as long as it's active and playful."
🎾 Activity Preferences:
"I want an active medium to large dog for hiking and outdoor activities"
🚶 Balanced Daily Routine:
"I live in a medium-sized house, walk about 30 minutes every day, and I'm okay with a moderately vocal dog. Looking for a balanced companion."
👥 Family Situation:
"Looking for a calm, low-maintenance companion dog for elderly person"
🔍 Tips: Please describe in English, including living environment, preferred breeds, family situation, activity needs, etc. The more detailed your description, the more accurate the recommendations!
""" def create_recommendation_tab( UserPreferences, get_breed_recommendations, format_recommendation_html, history_component ): """Create the enhanced breed recommendation tab with natural language support""" with gr.TabItem("Breed Recommendation"): with gr.Tabs(): # -------------------------- # Find by Criteria # -------------------------- with gr.Tab("Find by Criteria"): gr.HTML("""

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

🔬 The matching algorithm is continuously improving. Results are for reference only.
""") 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(): size_preference = gr.Radio( choices=["no_preference", "small", "medium", "large", "giant"], label="Preference Dog Size", info="Select your preferred dog size - this will strongly filter the recommendations", value="no_preference" ) 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" ) def update_children_age_visibility(has_children_val): """Update children age visibility based on has_children checkbox""" return gr.update(visible=has_children_val) has_children.change( fn=update_children_age_visibility, inputs=[has_children], outputs=[children_age] ) # --------- 條件搜尋--------- def find_breed_matches( living_space, yard_access, exercise_time, exercise_type, grooming_commitment, size_preference, experience_level, time_availability, has_children, children_age, noise_tolerance ): """Process criteria-based breed matching and persist history""" try: # 1) 建立偏好 user_prefs = UserPreferences( living_space=living_space, yard_access=yard_access, exercise_time=exercise_time, exercise_type=exercise_type, grooming_commitment=grooming_commitment, size_preference=size_preference, experience_level=experience_level, time_availability=time_availability, has_children=has_children, children_age=children_age if has_children else None, noise_tolerance=noise_tolerance, # 其他欄位依原始設計 space_for_play=(living_space != "apartment"), other_pets=False, climate="moderate", health_sensitivity="medium", barking_acceptance=noise_tolerance ) # 2) 取得推薦 recommendations = get_breed_recommendations(user_prefs) print(f"[CRITERIA] generated={len(recommendations) if recommendations else 0}") if not recommendations: return format_recommendation_html([], is_description_search=False) # 3) 準備歷史資料(final_score / overall_score 同步) history_results = [] for idx, rec in enumerate(recommendations, start=1): final_score = rec.get("final_score", rec.get("overall_score", 0)) overall_score = final_score # Ensure consistency history_results.append({ "breed": rec.get("breed", "Unknown"), "rank": rec.get("rank", idx), "final_score": final_score, "overall_score": overall_score, "base_score": rec.get("base_score", 0), "bonus_score": rec.get("bonus_score", 0), "scores": rec.get("scores", {}) }) prefs_dict = user_prefs.__dict__ if hasattr(user_prefs, "__dict__") else user_prefs # 4) 寫入歷史(criteria) try: ok = history_component.save_search( user_preferences=prefs_dict, results=history_results, search_type="criteria", description=None ) print(f"[CRITERIA SAVE] ok={ok}, saved={len(history_results)}") except Exception as e: print(f"[CRITERIA SAVE][ERROR] {str(e)}") # 5) 顯示結果 return format_recommendation_html(recommendations, is_description_search=False) except Exception as e: print(f"[CRITERIA][ERROR] {str(e)}") print(traceback.format_exc()) return f"""

⚠️ Error generating recommendations

We encountered an issue while processing your preferences.

Error details: {str(e)}

""" find_button = gr.Button("🔍 Find My Perfect Match!", elem_id="find-match-btn", size="lg") criteria_results = gr.HTML(label="Breed Recommendations") find_button.click( fn=find_breed_matches, inputs=[living_space, yard_access, exercise_time, exercise_type, grooming_commitment, size_preference, experience_level, time_availability, has_children, children_age, noise_tolerance], outputs=criteria_results ) # -------------------------- # Find by Description # -------------------------- with gr.Tab("Find by Description") as description_tab: gr.HTML("""
NEW

Describe your needs in natural language, and AI will find the most suitable breeds!

🚀 New Feature: Based on advanced semantic understanding technology, making search more aligned with your real needs!
""") examples_display = gr.HTML(create_description_examples()) description_input = gr.Textbox( label="🗣️ Please describe your needs", placeholder=("Example: I live in an apartment and need a quiet, small dog that's good with children. " "I prefer Border Collies and Golden Retrievers..."), lines=4, max_lines=6, elem_classes=["description-input"] ) validation_status = gr.HTML(visible=False) # Accuracy disclaimer gr.HTML("""
Accuracy Continuously Improving - Use as Reference Guide
The AI recommendation system is constantly learning and improving. Use these recommendations as a helpful reference for your pet adoption.
""") def validate_description_input(text): """Validate description input""" try: nlp = get_nlp_processor() validation = nlp.validate_input(text) if validation.get("is_valid", True): return gr.update(visible=False), True else: error_html = f"""
⚠️ {validation.get('error', 'Invalid input')}
{"
".join(f"• {s}" for s in validation.get('suggestions', []))}
""" return gr.update(value=error_html, visible=True), False except Exception as e: # 無 NLP 驗證也可放行 print(f"[DESC][VALIDATE][WARN] {str(e)}") return gr.update(visible=False), True @spaces.GPU def find_breeds_by_description(description_text): """Find breeds based on description and persist history""" try: if not description_text or not description_text.strip(): return """

Please enter your description to get personalized recommendations

""" # 驗證(若可用) try: nlp = get_nlp_processor() validation = nlp.validate_input(description_text) if not validation.get("is_valid", True): return f"""

⚠️ Input validation failed

{validation.get('error','Invalid input')}

""" except Exception as e: print(f"[DESC][VALIDATE][WARN] {str(e)} (skip validation)") # 取得增強語意推薦 recommendations = get_enhanced_recommendations_with_unified_scoring( user_description=description_text, top_k=15 ) print(f"[DESC] generated={len(recommendations) if recommendations else 0}") if not recommendations: return """

😔 No matching breeds found

No dog breeds match your specific requirements. Please try:

""" # 準備歷史資料 def _to_float(x, default=0.0): try: return float(x) except Exception: return default history_results = [] for i, rec in enumerate(recommendations, start=1): final_score = _to_float(rec.get("final_score", rec.get("overall_score", 0))) overall_score = final_score # Ensure consistency between final_score and overall_score history_results.append({ "breed": str(rec.get("breed", "Unknown")), "rank": int(rec.get("rank", i)), "final_score": final_score, "overall_score": overall_score, "semantic_score": _to_float(rec.get("semantic_score", 0)), "comparative_bonus": _to_float(rec.get("comparative_bonus", 0)), "lifestyle_bonus": _to_float(rec.get("lifestyle_bonus", 0)), "size": str(rec.get("size", "Unknown")), "scores": rec.get("scores", {}) }) # 寫入歷史(description) try: ok = history_component.save_search( user_preferences=None, results=history_results, search_type="description", description=description_text ) print(f"[DESC SAVE] ok={ok}, saved={len(history_results)}") except Exception as e: print(f"[DESC SAVE][ERROR] {str(e)}") # 使用統一HTML格式化器顯示增強推薦結果 html_output = format_unified_recommendation_html(recommendations, is_description_search=True) return html_output except RuntimeError as e: error_msg = str(e) print(f"[DESC][RUNTIME_ERROR] {error_msg}") return f"""

🔧 System Configuration Issue

{error_msg.replace(chr(10), '
').replace('•', '•')}

💡 What you can try:

""" except ValueError as e: error_msg = str(e) print(f"[DESC][VALUE_ERROR] {error_msg}") return f"""

🔍 No Matching Results

{error_msg}

💡 Suggestions to get better results:

""" except Exception as e: error_msg = str(e) print(f"[DESC][ERROR] {error_msg}") print(traceback.format_exc()) return f"""

⚠️ Unexpected Error

An unexpected error occurred while processing your description.

Show technical details
{error_msg}

Please try the "Find by Criteria" tab or contact support.

""" description_input.change( fn=lambda x: validate_description_input(x)[0], inputs=[description_input], outputs=[validation_status] ) description_button = gr.Button("🤖 Smart Breed Recommendation", elem_id="find-by-description-btn", size="lg") description_results = gr.HTML(label="AI Breed Recommendations") description_button.click( fn=find_breeds_by_description, inputs=[description_input], outputs=[description_results] ) return { 'criteria_results': locals().get('criteria_results'), 'description_results': locals().get('description_results'), 'description_input': locals().get('description_input') }