Spaces:
Sleeping
Sleeping
File size: 7,703 Bytes
9d3281a 7894107 9d3281a 7894107 9d3281a 93b9aec 9d3281a 93b9aec 9d3281a 7894107 9d3281a 7894107 9d3281a 7894107 9d3281a 7894107 9d3281a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
import streamlit as st
import time
import os
from supabase import create_client
from datetime import datetime
from zoneinfo import ZoneInfo
from supabase_auth.errors import AuthApiError
from transformers import pipeline
@st.cache_resource
def load_sentiment_model():
return pipeline(
"sentiment-analysis",
model="wonrax/phobert-base-vietnamese-sentiment"
)
sentiment_model = load_sentiment_model()
# ========== CONFIG ==========
st.set_page_config(
page_title="Personal Diary",
page_icon="📔",
layout="wide"
)
SUPABASE_URL = os.environ.get("SUPABASE_URL")
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
supabase = create_client(
SUPABASE_URL,
SUPABASE_KEY
)
# ========== STYLE ==========
st.markdown("""
<style>
.diary-card {
padding: 1rem;
border-radius: 12px;
background-color: #f8f9fa;
margin-bottom: 1rem;
}
.small-text {
font-size: 0.8rem;
color: #6c757d;
}
</style>
""", unsafe_allow_html=True)
def signup(email, password):
return supabase.auth.sign_up({
"email": email,
"password": password
})
def analyze_sentiment(text: str) -> str:
result = sentiment_model(text)[0]
return result["label"] # POS / NEG / NEU
# ========== HEADER ==========
st.title("📔 Personal Diary")
st.caption("Ghi chép suy nghĩ mỗi ngày – phiên bản demo")
# ========== LOGIN ==========
if "user" not in st.session_state:
st.subheader("🔐 Tài khoản")
tab_login, tab_signup = st.tabs(["Đăng nhập", "Đăng ký"])
# ===== LOGIN =====
with tab_login:
email = st.text_input("Email", key="login_email")
password = st.text_input("Mật khẩu", type="password", key="login_pw")
if st.button("➡️ Đăng nhập", use_container_width=True):
res = supabase.auth.sign_in_with_password({
"email": email,
"password": password
})
if res.user:
st.session_state.user = res.user
st.success("Đăng nhập thành công")
st.rerun()
else:
st.error("Sai email hoặc mật khẩu")
# ===== SIGNUP =====
with tab_signup:
email = st.text_input("Email đăng ký", key="signup_email")
password = st.text_input(
"Mật khẩu (tối thiểu 6 ký tự)",
type="password",
key="signup_pw"
)
# init cooldown
if "signup_cooldown_until" not in st.session_state:
st.session_state.signup_cooldown_until = 0
now = time.time()
cooldown_left = int(st.session_state.signup_cooldown_until - now)
if cooldown_left > 0:
st.warning(f"⏳ Vui lòng thử lại sau {cooldown_left} giây")
signup_disabled = cooldown_left > 0
if st.button(
"🆕 Tạo tài khoản",
use_container_width=True,
disabled=signup_disabled
):
try:
res = signup(email, password)
if res.user:
st.session_state.user = res.user
st.success("Đăng ký thành công 🎉")
st.rerun()
else:
st.error("Không thể đăng ký")
except AuthApiError as e:
msg = str(e)
# Bắt lỗi rate limit 60s
if "only request this after" in msg:
# Mặc định 60s nếu parse không được
wait_seconds = 60
# cố gắng parse số giây từ message
import re
match = re.search(r"after (\d+) seconds", msg)
if match:
wait_seconds = int(match.group(1))
st.session_state.signup_cooldown_until = time.time() + wait_seconds
st.error(f"🚫 Bạn đã thử quá nhiều lần. Vui lòng đợi {wait_seconds} giây rồi thử lại.")
st.rerun()
else:
st.error("Lỗi đăng ký: " + msg)
# ========== MAIN APP ==========
else:
user = st.session_state.user
# Top bar
top_left, top_right = st.columns([4, 1])
with top_left:
st.markdown(f"👋 Xin chào **{user.email}**")
with top_right:
if st.button("🚪 Đăng xuất", use_container_width=True):
st.session_state.clear()
st.rerun()
st.divider()
# Main layout
left, right = st.columns([2, 3])
# ===== LEFT: WRITE =====
with left:
st.subheader("✍️ Viết nhật ký")
content = st.text_area(
"Hôm nay bạn nghĩ gì?",
height=250,
placeholder="Viết suy nghĩ của bạn ở đây..."
)
if st.button("💾 Lưu nhật ký", use_container_width=True):
if content.strip():
sentiment = analyze_sentiment(content)
supabase.table("journals").insert({
"user_id": user.id,
"content": content,
"sentiment": sentiment,
"created_at": datetime.utcnow().isoformat()
}).execute()
st.success("Đã lưu nhật ký ✨")
st.rerun()
else:
st.warning("Nội dung đang trống")
# ===== RIGHT: LIST =====
with right:
st.subheader("📜 Nhật ký của bạn")
data = supabase.table("journals") \
.select("*") \
.eq("user_id", user.id) \
.order("created_at", desc=True) \
.execute()
if not data.data:
st.info("Chưa có nhật ký nào")
else:
for row in data.data:
# Format ngày cho gọn
created_dt = datetime.fromisoformat(
row["created_at"].replace("Z", "+00:00")
).astimezone(ZoneInfo("Asia/Ho_Chi_Minh"))
date_label = created_dt.strftime("%d/%m/%Y – %H:%M")
# Dropdown theo ngày
with st.expander(f"📅 {date_label}", expanded=False):
new_content = st.text_area(
"Nội dung",
row["content"],
key=row["id"],
height=120
)
sentiment = row.get("sentiment", "N/A")
emoji_map = {
"Vui vẻ": "😊",
"Buồn": "😔",
"Trung tính": "😐"
}
st.caption(f"Cảm xúc: {emoji_map.get(sentiment, '❓')} {sentiment}")
col_u, col_d = st.columns(2)
if col_u.button("✏️ Cập nhật", key=f"u{row['id']}"):
supabase.table("journals").update({
"content": new_content,
"updated_at": datetime.utcnow().isoformat()
}).eq("id", row["id"]).execute()
st.rerun()
if col_d.button("🗑️ Xoá", key=f"d{row['id']}"):
supabase.table("journals").delete() \
.eq("id", row["id"]).execute()
st.rerun()
|