import streamlit as st from dotenv import load_dotenv import os from datetime import datetime from utils import db_client from utils.nlp_utils import analyze_reviews from utils.ai_assistant import ask_deepseek_ai # Load environment variables from .env file # 尝试加载 .env 文件(本地开发时) try: load_dotenv() except: pass # 在 Hugging Face Space 中忽略 # 从环境变量获取配置 api_key = os.getenv('DEEPSEEK_API_KEY') db_url = os.getenv('NOCODE_API_KEY') # --- Page Configuration --- st.set_page_config( page_title="HiPet - Find Your Pet Sitter", page_icon="🐾", layout="wide" ) # --- Helper Functions --- def detect_language(text: str) -> str: """Simple language detection. Returns 'zh' if Chinese characters are prominent, else 'en'.""" for char in text: if '\u4e00' <= char <= '\u9fff': return "zh" return "en" def format_datetime_for_display(datetime_str: str, lang: str) -> str: """格式化时间显示""" try: # 解析数据库中的时间格式 dt = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S") if lang == "zh": # 中文格式:2025年6月30日 04:22 return dt.strftime("%Y年%m月%d日 %H:%M") else: # 英文格式:Jun 30, 2025 04:22 return dt.strftime("%b %d, %Y %H:%M") except: # 如果解析失败,返回原始字符串 return datetime_str # --- Main Application --- def main(): st.title("🐾 HiPet - Find Your Perfect Pet Sitter") if 'language' not in st.session_state: st.session_state.language = "en" # Default to English lang = st.session_state.language # 语言切换按钮 cols_lang = st.columns([0.8, 0.2]) with cols_lang[1]: if st.button("Switch to Chinese (切换到中文)" if st.session_state.language == "en" else "Switch to English (切换到英文)"): st.session_state.language = "zh" if st.session_state.language == "en" else "en" if 'selected_city' in st.session_state: del st.session_state['selected_city'] if 'selected_service' in st.session_state: del st.session_state['selected_service'] st.rerun() # Fetch petsitters petsitters = db_client.get_petsitters() if not petsitters: st.error("Could not fetch pet sitters from the database." if lang == "en" else "无法从数据库获取宠物保姆信息。") return # Group petsitters by city and service type cities_data = {} service_types = set() for sitter in petsitters: city_name = sitter.get(f"Location_city_{lang}", sitter.get("Location_city_en", "Unknown City")) english_city_name = sitter.get("Location_city_en") service_type = sitter.get(f"Preferred_type_of_pet_care_{lang}") # Add to service types set service_types.add(service_type) # Add to cities data if city_name not in cities_data: cities_data[city_name] = {'sitters': [], 'city_name_en': english_city_name} cities_data[city_name]['sitters'].append(sitter) # 在侧栏添加一些说明信息 st.sidebar.header("🔍 " + ("How to Use" if lang == "en" else "使用说明")) if lang == "en": st.sidebar.markdown(""" 1. Select your Location 2. Choose service type 3. Browse pet sitters 4. Read reviews 5. Ask AI questions """) else: st.sidebar.markdown(""" 1. 选择您的地点 2. 选择服务类型 3. 浏览宠物保姆 4. 查看评价 5. 向AI提问 """) # 侧边栏:城市和服务类型选择 st.sidebar.header("🏙️ " + ("Location & Service" if lang == "en" else "地点与服务")) # City selection dropdown city_names = list(cities_data.keys()) selected_city = st.sidebar.selectbox( "Select Location" if lang == "en" else "选择地点", options=city_names, key='selected_city' ) # Service type selection dropdown service_types_list = sorted(list(service_types)) selected_service = st.sidebar.selectbox( "Select Service Type" if lang == "en" else "选择服务类型", options=service_types_list, key='selected_service' ) # AI聊天框区域移动到侧边栏 st.sidebar.header("🤖 " + ("Ask AI about pet care" if lang == "en" else "向AI咨询宠物护理问题")) # AI聊天框 if lang == "en": user_question = st.sidebar.text_area("Ask anything about pets...", placeholder="E.g., How often should I walk my dog?", height=100, key="ai_question") ask_button = st.sidebar.button("Ask AI", type="primary") else: user_question = st.sidebar.text_area("请提出与宠物相关的问题...", placeholder="例如:我一天该遛几次狗?", height=100, key="ai_question") ask_button = st.sidebar.button("提问 AI", type="primary") if ask_button: if user_question.strip(): with st.spinner("Thinking..." if lang == "en" else "AI 思考中..."): ai_response = ask_deepseek_ai(user_question, lang=lang) st.sidebar.markdown("**Answer:**" if lang == "en" else "**AI 答复:**") st.sidebar.write(ai_response) else: st.sidebar.warning("Please enter a question." if lang == "en" else "请输入您的问题。") # 主内容区域 # 删除原来的AI聊天框代码,直接开始宠物保姆信息区域 # 宠物保姆信息区域 if selected_city and selected_service: city_info = cities_data.get(selected_city) if city_info: city_sitters = city_info['sitters'] english_city_name = city_info['city_name_en'] # Filter sitters by service type filtered_sitters = [sitter for sitter in city_sitters if sitter.get(f"Preferred_type_of_pet_care_{lang}") == selected_service] if not filtered_sitters: st.info(f"No pet sitters found in {selected_city} offering {selected_service} service. please try contacting other pet sitters in nearby districts" if lang == "en" else f"在{selected_city}没有找到提供{selected_service}服务的宠物保姆。请尝试联系附近其他宠物保姆。") else: st.markdown(f"### 🐈🐕‍🦺 " + (f"Pet Sitters in {selected_city}" if lang == "en" else f"{selected_city}的宠物保姆")) # Get all reviews for the city all_city_reviews = db_client.get_reviews_for_city(english_city_name) # 合并session_state中的新评论 for sitter in filtered_sitters: sitter_id = str(sitter.get('petsitter_id')) if f"updated_reviews_{sitter_id}" in st.session_state: session_reviews = st.session_state[f"updated_reviews_{sitter_id}"] db_reviews = all_city_reviews.get(sitter_id, []) existing_review_ids = {str(review.get('review_id')) for review in db_reviews} new_reviews = [review for review in session_reviews if str(review.get('review_id')) not in existing_review_ids] if new_reviews: all_city_reviews[sitter_id] = new_reviews + db_reviews for sitter in filtered_sitters: # 宠物保姆卡片 st.markdown(f"#### {sitter.get('petsitter_name', 'Unknown')}") # 获取并显示特征标签 petsitter_id = sitter.get('petsitter_id') features = db_client.get_petsitter_features(petsitter_id) if features: positive_features = features[f'positive_{lang}'] negative_features = features[f'negative_{lang}'] if positive_features: positive_html = ' '.join([f'{feature} {count}' for feature, count in positive_features.items()]) st.markdown(f"
{positive_html}
", unsafe_allow_html=True) if negative_features: negative_html = ' '.join([f'{feature} {count}' for feature, count in negative_features.items()]) st.markdown(f"
{negative_html}
", unsafe_allow_html=True) # 基本信息和评价区域 info_col, review_col = st.columns([1, 2]) with info_col: # Basic information st.markdown("**📋 " + ("Basic Information" if lang == "en" else "基本信息") + "**") st.write(f"{'Name' if lang == 'en' else '姓名'}: {sitter.get('petsitter_name', 'Unknown')}") st.write(f"{'Real-name Verification' if lang == 'en' else '实名认证'}: {'✅ Verified' if sitter.get('Real-name_verification_' + lang) == 'Yes' else '❌ Not Verified' if lang == 'en' else '❌ 未认证'}") st.write(f"{'Student Status' if lang == 'en' else '学生身份'}: {'✅ Student' if sitter.get('Student_or_not_' + lang) == 'Yes' else '❌ Not Student' if lang == 'en' else '❌ 非学生'}") st.write(f"{'Age' if lang == 'en' else '年龄'}: {sitter.get('Age_' + lang)}") st.write(f"{'Gender' if lang == 'en' else '性别'}: {sitter.get('Gender_' + lang)}") st.write(f"{'Experience' if lang == 'en' else '经验'}: {sitter.get('Pet_Experience/year_' + lang)} {'years' if lang == 'en' else '年'}") st.write(f"{'Preferred Pets' if lang == 'en' else '擅长照顾'}: {sitter.get('Preferred_type_of_pet_' + lang)}") st.write(f"{'Rate' if lang == 'en' else '时薪'}: ¥{sitter.get('Willing_payment/per_hour_' + lang)}/{'hour' if lang == 'en' else '小时'}") # Average rating section st.write("") st.markdown("**💫 " + ("Average Rating" if lang == "en" else "综合评分") + "**") sitter_reviews = all_city_reviews.get(str(sitter.get('petsitter_id')), []) if sitter_reviews: sentiment_score, keywords_by_lang = analyze_reviews(sitter_reviews) ratings = [review.get("rating", 0) for review in sitter_reviews] avg_rating = sum(ratings) / len(ratings) full_hearts = "❤️" * int(avg_rating) half_heart = "❤️" if avg_rating % 1 >= 0.5 else "" st.write(f"{avg_rating:.1f} {full_hearts + half_heart}") # 情感分析结果 st.write("") st.markdown("**💟 " + ("Sentiment Score" if lang == "en" else "情感分析") + "**") if sentiment_score > 0: st.write("😊 " + ("积极" if lang == "zh" else "Positive") + f" ({sentiment_score:.2f})") elif sentiment_score < 0: st.write("😔 " + ("消极" if lang == "zh" else "Negative") + f" ({sentiment_score:.2f})") else: st.write("😐 " + ("中性" if lang == "zh" else "Neutral") + f" ({sentiment_score:.2f})") # 基于x条评价 st.write(f"{'Based on ' + str(len(sitter_reviews)) + ' reviews' if lang == 'en' else '基于' + str(len(sitter_reviews)) + '条评价'}") else: st.write(f"{'No ratings yet' if lang == 'en' else '暂无评分'}") st.write(f"{'Based on ' + str(len(sitter_reviews)) + ' reviews' if lang == 'en' else '基于' + str(len(sitter_reviews)) + '条评价'}") with review_col: # Reviews section st.markdown("**💬 " + ("Reviews" if lang == "en" else "评价") + "**") # 添加新评价按钮 if st.button("Add New Review" if lang == "en" else "添加新评价", key=f"add_review_{sitter.get('petsitter_id')}"): st.session_state[f"show_review_form_{sitter.get('petsitter_id')}"] = True # 显示评价表单 if st.session_state.get(f"show_review_form_{sitter.get('petsitter_id')}", False): with st.form(key=f"review_form_{sitter.get('petsitter_id')}"): st.markdown("**✨ " + ("Write a Review" if lang == "en" else "写评价") + "**") reviewer_name = st.text_input( "Your Name" if lang == "en" else "您的姓名", placeholder="Enter your name" if lang == "en" else "请输入您的姓名" ) rating = st.selectbox( "Rating" if lang == "en" else "评分", options=[1, 2, 3, 4, 5], format_func=lambda x: "⭐" * x + "☆" * (5-x) ) review_text = st.text_area( "Review" if lang == "en" else "评价内容", placeholder="Share your experience..." if lang == "en" else "分享您的体验...", height=100 ) # 使用容器而不是嵌套列 button_container = st.container() with button_container: submit_review = st.form_submit_button( "Submit Review" if lang == "en" else "提交评价", type="primary" ) cancel_review = st.form_submit_button( "Cancel" if lang == "en" else "取消" ) if submit_review: if reviewer_name and review_text: success, message, new_review_data = db_client.add_review( str(sitter.get('petsitter_id')), reviewer_name, rating, review_text ) if success: st.success(message) st.session_state[f"show_review_form_{sitter.get('petsitter_id')}"] = False keys_to_remove = [key for key in st.session_state.keys() if key.startswith(f"updated_reviews_")] for key in keys_to_remove: del st.session_state[key] st.rerun() else: st.error(message) else: st.error("Please fill in all required fields" if lang == "en" else "请填写所有必填字段") if cancel_review: st.session_state[f"show_review_form_{sitter.get('petsitter_id')}"] = False st.rerun() # 显示现有评价 sitter_id = str(sitter.get('petsitter_id')) current_sitter_reviews = all_city_reviews.get(sitter_id, []) if current_sitter_reviews: # 排序选项 st.markdown("**🔃 " + ("Sort by" if lang == "en" else "排序方式") + "**") sort_options = [ ("Latest" if lang == "en" else "最新", "latest", "🔼"), ("Earliest" if lang == "en" else "最早", "earliest", "🔽"), ("Highest" if lang == "en" else "最高分", "highest", "👍"), ("Lowest" if lang == "en" else "最低分", "lowest", "👎") ] # 使用radio按钮替代列布局 current_sort = st.radio( "选择排序方式" if lang == "zh" else "Choose sorting", options=["latest", "earliest", "highest", "lowest"], format_func=lambda x: { "latest": f"{'Latest' if lang == 'en' else '最新'} 🔼", "earliest": f"{'Earliest' if lang == 'en' else '最早'} 🔽", "highest": f"{'Highest' if lang == 'en' else '最高分'} 👍", "lowest": f"{'Lowest' if lang == 'en' else '最低分'} 👎" }[x], horizontal=True, key=f"sort_radio_{sitter_id}" ) # 排序评论 if current_sort == "latest": sorted_reviews = sorted(current_sitter_reviews, key=lambda x: x.get('created_at', ''), reverse=True) elif current_sort == "earliest": sorted_reviews = sorted(current_sitter_reviews, key=lambda x: x.get('created_at', '')) elif current_sort == "highest": sorted_reviews = sorted(current_sitter_reviews, key=lambda x: x.get('rating', 0), reverse=True) else: # lowest sorted_reviews = sorted(current_sitter_reviews, key=lambda x: x.get('rating', 0)) # 显示评价 # 显示评价 for review in sorted_reviews: # 显示所有评价 rating = review.get('rating', 0) stars = "⭐" * rating # 根据评分显示相应数量的星星 with st.expander(f"{stars} {rating}/5 - {review.get('reviewer_name', 'Anonymous')}"): # 显示评价内容 review_lang = review.get('review_language', 'en') if review_lang == lang: # 显示原始语言的评价 st.write(review.get('review_text_original', '')) else: # 显示翻译后的评价 st.write(review.get('review_text_translated', '')) st.caption(f"{'Translated from' if lang == 'en' else '翻译自'} {'Chinese' if review_lang == 'zh' else 'English'}") # 显示时间 created_at = review.get('created_at', '') if created_at: formatted_time = format_datetime_for_display(created_at, lang) st.caption(f"{'Posted on' if lang == 'en' else '发布于'}: {formatted_time}") # 删除或修改这个条件判断 # if len(current_sitter_reviews) > 5: # st.info(f"{'Showing 5 of' if lang == 'en' else '显示前5条,共'} {len(current_sitter_reviews)} {'reviews' if lang == 'en' else '条评价'}") # 可选:显示总评价数 if len(current_sitter_reviews) > 0: st.info(f"{'Total' if lang == 'en' else '共'} {len(current_sitter_reviews)} {'reviews' if lang == 'en' else '条评价'}") else: st.info("No reviews yet. Be the first to review!" if lang == "en" else "暂无评价,快来写第一条评价吧!") st.markdown("---") if __name__ == "__main__": main()