Spaces:
Sleeping
Sleeping
import gradio as gr | |
import traceback | |
from typing import Optional, Dict, List | |
from history_manager import UserHistoryManager | |
class SearchHistoryComponent: | |
def __init__(self): | |
"""初始化搜索歷史組件""" | |
self.history_manager = UserHistoryManager() | |
# def format_history_html(self, history_data: Optional[List[Dict]] = None) -> str: | |
# try: | |
# if history_data is None: | |
# history_data = self.history_manager.get_history() | |
# if not history_data: | |
# return """ | |
# <div style=' | |
# text-align: center; | |
# padding: 40px 20px; | |
# color: #666; | |
# background: linear-gradient(to right, rgba(66, 153, 225, 0.05), rgba(72, 187, 120, 0.05)); | |
# border-radius: 10px; | |
# margin: 20px 0; | |
# '> | |
# <p style=' | |
# font-size: 1.1em; | |
# margin: 0; | |
# background: linear-gradient(90deg, #4299e1, #48bb78); | |
# -webkit-background-clip: text; | |
# -webkit-text-fill-color: transparent; | |
# font-weight: 600; | |
# '> | |
# No search history yet. Try making some breed recommendations! | |
# </p> | |
# </div> | |
# """ | |
# html = "<div class='history-container'>" | |
# for entry in reversed(history_data): | |
# timestamp = entry.get('timestamp', 'Unknown time') | |
# search_type = entry.get('search_type', 'criteria') | |
# results = entry.get('results', []) | |
# html += f""" | |
# <div class="history-entry"> | |
# <div class="history-header" style="border-left: 4px solid #4299e1; padding-left: 10px;"> | |
# <span class="timestamp">🕒 {timestamp}</span> | |
# <span class="search-type" style="color: #4299e1; font-weight: bold; margin-left: 10px;"> | |
# Search History | |
# </span> | |
# </div> | |
# """ | |
# if search_type == "criteria": | |
# prefs = entry.get('preferences', {}) | |
# html += f""" | |
# <div class="params-list" style="background: #f8fafc; padding: 16px; border-radius: 8px; margin-bottom: 16px;"> | |
# <h4 style="font-size: 1.1em; font-weight: 600; color: #2D3748; margin-bottom: 12px; border-bottom: 2px solid #E2E8F0; padding-bottom: 8px;">Search Parameters:</h4> | |
# <ul style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; list-style: none; padding: 0;"> | |
# <li><span class="param-label" style="font-weight: 600; color: #2D3748;">Living Space:</span> {prefs.get('living_space', 'N/A')}</li> | |
# <li><span class="param-label" style="font-weight: 600; color: #2D3748;">Exercise Time:</span> {prefs.get('exercise_time', 'N/A')} minutes</li> | |
# <li><span class="param-label" style="font-weight: 600; color: #2D3748;">Grooming:</span> {prefs.get('grooming_commitment', 'N/A')}</li> | |
# <li><span class="param-label" style="font-weight: 600; color: #2D3748;">Experience:</span> {prefs.get('experience_level', 'N/A')}</li> | |
# <li><span class="param-label" style="font-weight: 600; color: #2D3748;">Children at Home:</span> {"Yes" if prefs.get('has_children') else "No"}</li> | |
# <li><span class="param-label" style="font-weight: 600; color: #2D3748;">Noise Tolerance:</span> {prefs.get('noise_tolerance', 'N/A')}</li> | |
# </ul> | |
# </div> | |
# """ | |
# # 結果顯示邏輯 | |
# html += """ | |
# <div class="results-list"> | |
# <h4 style="font-size: 1.1em; font-weight: 600; color: #2D3748; margin-bottom: 12px; border-bottom: 2px solid #E2E8F0; padding-bottom: 8px;">Top 10 Breed Matches:</h4> | |
# <div class="breed-list"> | |
# """ | |
# if results: | |
# for i, result in enumerate(results[:10], 1): | |
# breed_name = result.get('breed', 'Unknown breed').replace('_', ' ') | |
# score = result.get('overall_score', result.get('final_score', 0)) | |
# html += f""" | |
# <div class="breed-item" style="margin-bottom: 8px;"> | |
# <div class="breed-info" style="display: flex; align-items: center; justify-content: space-between; padding: 8px; background: #f8fafc; border-radius: 6px;"> | |
# <span class="breed-rank" style="background: linear-gradient(135deg, #4299e1, #48bb78); color: white; padding: 4px 10px; border-radius: 6px; font-weight: 600; min-width: 40px; text-align: center;">#{i}</span> | |
# <span class="breed-name" style="font-weight: 500; color: #2D3748; font-size: 1.05em; margin: 0 12px;">{breed_name}</span> | |
# <span class="breed-score" style="background: #F0FFF4; color: #48BB78; padding: 4px 8px; border-radius: 4px; font-weight: 600;">{score*100:.1f}%</span> | |
# </div> | |
# </div> | |
# """ | |
# html += """ | |
# </div> | |
# </div> | |
# </div> | |
# """ | |
# html += "</div>" | |
# return html | |
# except Exception as e: | |
# print(f"Error formatting history: {str(e)}") | |
# print(traceback.format_exc()) | |
# return f""" | |
# <div style='text-align: center; padding: 20px; color: #dc2626;'> | |
# Error formatting history. Please try refreshing the page. | |
# <br>Error details: {str(e)} | |
# </div> | |
# """ | |
def format_history_html(self, history_data: Optional[List[Dict]] = None) -> str: | |
try: | |
if history_data is None: | |
history_data = self.history_manager.get_history() | |
if not history_data: | |
return """ | |
<div style='text-align: center; padding: 40px 20px;'> | |
<p>No search history yet. Try making some breed recommendations!</p> | |
</div> | |
""" | |
html = "<div class='history-container'>" | |
# 對歷史記錄進行反轉,最新的顯示在前面 | |
for entry in reversed(history_data): | |
timestamp = entry.get('timestamp', 'Unknown time') | |
search_type = entry.get('search_type', 'criteria') | |
results = entry.get('results', []) # 確保我們有結果資料 | |
# 顯示時間戳記和搜尋類型 | |
html += f""" | |
<div class="history-entry"> | |
<div class="history-header" style="border-left: 4px solid #4299e1; padding-left: 10px;"> | |
<span class="timestamp">🕒 {timestamp}</span> | |
<span class="search-type" style="color: #4299e1; font-weight: bold; margin-left: 10px;"> | |
Search History | |
</span> | |
</div> | |
""" | |
# 顯示搜尋參數 | |
if search_type == "criteria": | |
prefs = entry.get('preferences', {}) | |
html += f""" | |
<div class="params-list" style="background: #f8fafc; padding: 16px; border-radius: 8px; margin-bottom: 16px;"> | |
<h4 style="margin-bottom: 12px;">Search Parameters:</h4> | |
<ul style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;"> | |
<li>Living Space: {prefs.get('living_space', 'N/A')}</li> | |
<li>Exercise Time: {prefs.get('exercise_time', 'N/A')} minutes</li> | |
<li>Grooming: {prefs.get('grooming_commitment', 'N/A')}</li> | |
<li>Experience: {prefs.get('experience_level', 'N/A')}</li> | |
<li>Children at Home: {"Yes" if prefs.get('has_children') else "No"}</li> | |
<li>Noise Tolerance: {prefs.get('noise_tolerance', 'N/A')}</li> | |
</ul> | |
</div> | |
""" | |
# 關鍵修改:確保結果部分始終顯示 | |
if results: # 只有在有結果時才顯示結果區域 | |
html += """ | |
<div class="results-list" style="margin-top: 16px;"> | |
<h4 style="margin-bottom: 12px;">Top 10 Breed Matches:</h4> | |
<div class="breed-list"> | |
""" | |
# 顯示每個推薦結果 | |
for i, result in enumerate(results[:10], 1): | |
breed = result.get('breed', 'Unknown breed') | |
score = result.get('overall_score', 0) # 改用 overall_score | |
if isinstance(score, (int, float)): # 確保分數是數字 | |
score = float(score) * 100 # 轉換為百分比 | |
html += f""" | |
<div class="breed-item" style="margin-bottom: 8px;"> | |
<div class="breed-info" style="display: flex; align-items: center; justify-content: space-between; padding: 8px; background: #f8fafc; border-radius: 6px;"> | |
<span class="breed-rank" style="background: linear-gradient(135deg, #4299e1, #48bb78); color: white; padding: 4px 10px; border-radius: 6px; font-weight: 600; min-width: 40px; text-align: center;">#{i}</span> | |
<span class="breed-name" style="font-weight: 500; color: #2D3748; margin: 0 12px;">{breed.replace('_', ' ')}</span> | |
<span class="breed-score" style="background: #F0FFF4; color: #48BB78; padding: 4px 8px; border-radius: 4px; font-weight: 600;">{score:.1f}%</span> | |
</div> | |
</div> | |
""" | |
html += """ | |
</div> | |
</div> | |
""" | |
html += "</div>" # 關閉 history-entry div | |
html += "</div>" # 關閉 history-container div | |
return html | |
except Exception as e: | |
print(f"Error formatting history: {str(e)}") | |
print(traceback.format_exc()) | |
return f""" | |
<div style='text-align: center; padding: 20px; color: #dc2626;'> | |
Error formatting history. Please try refreshing the page. | |
<br>Error details: {str(e)} | |
</div> | |
""" | |
def clear_history(self) -> str: | |
"""清除所有搜尋歷史""" | |
try: | |
success = self.history_manager.clear_all_history() | |
print(f"Clear history result: {success}") | |
return self.format_history_html() | |
except Exception as e: | |
print(f"Error in clear_history: {str(e)}") | |
print(traceback.format_exc()) | |
return "Error clearing history" | |
def refresh_history(self) -> str: | |
"""刷新歷史記錄顯示""" | |
try: | |
return self.format_history_html() | |
except Exception as e: | |
print(f"Error in refresh_history: {str(e)}") | |
return "Error refreshing history" | |
def save_search(self, user_preferences: Optional[dict] = None, | |
results: list = None, | |
search_type: str = "criteria", | |
description: str = None) -> bool: | |
"""保存搜索結果 | |
Args: | |
user_preferences: 使用者偏好設定 (僅用於criteria搜尋) | |
results: 推薦結果列表 | |
search_type: 搜尋類型 ("criteria" 或 "description") | |
description: 使用者輸入的描述 (僅用於description搜尋) | |
""" | |
return self.history_manager.save_history( | |
user_preferences=user_preferences, | |
results=results, | |
search_type='criteria', | |
) | |
def create_history_component(): | |
"""只創建實例""" | |
return SearchHistoryComponent() | |
def create_history_tab(history_component: SearchHistoryComponent): | |
"""創建歷史紀錄的頁面 | |
Args: | |
history_component: | |
""" | |
with gr.TabItem("Recommendation Search History"): | |
gr.HTML(""" | |
<div style='text-align: center; padding: 20px;'> | |
<h3 style=' | |
color: #2D3748; | |
margin-bottom: 10px; | |
font-size: 1.5em; | |
background: linear-gradient(90deg, #4299e1, #48bb78); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
font-weight: 600; | |
'>Search History</h3> | |
<div style=' | |
text-align: center; | |
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; | |
'> | |
<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; | |
'> | |
View your previous breed recommendations and search preferences | |
</p> | |
</div> | |
</div> | |
""") | |
with gr.Row(): | |
with gr.Column(scale=4): | |
history_display = gr.HTML() | |
with gr.Row(): | |
with gr.Column(scale=1): | |
clear_history_btn = gr.Button( | |
"🗑️ Clear History", | |
variant="secondary", | |
size="sm" | |
) | |
with gr.Column(scale=1): | |
refresh_btn = gr.Button( | |
"🔄 Refresh", | |
variant="secondary", | |
size="sm" | |
) | |
history_display.value = history_component.format_history_html() | |
clear_history_btn.click( | |
fn=history_component.clear_history, | |
outputs=[history_display], | |
api_name="clear_history" | |
) | |
refresh_btn.click( | |
fn=history_component.refresh_history, | |
outputs=[history_display], | |
api_name="refresh_history" | |
) |