HiPet / app.py
potatomoon's picture
Upload app.py
83fc807 verified
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()