Spaces:
Running
Running
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'<span style="background-color: #00B0F0; color: white; padding: 2px 8px; margin: 0 4px; border-radius: 12px;">{feature} {count}</span>' | |
for feature, count in positive_features.items()]) | |
st.markdown(f"<div style='margin-bottom: 10px;'>{positive_html}</div>", unsafe_allow_html=True) | |
if negative_features: | |
negative_html = ' '.join([f'<span style="background-color: #FFC000; color: white; padding: 2px 8px; margin: 0 4px; border-radius: 12px;">{feature} {count}</span>' | |
for feature, count in negative_features.items()]) | |
st.markdown(f"<div style='margin-bottom: 10px;'>{negative_html}</div>", 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() |