Spaces:
Sleeping
Sleeping
File size: 8,805 Bytes
9597933 | 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 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | import streamlit as st
from supabase import create_client
from datetime import datetime
from zoneinfo import ZoneInfo
from supabase_auth.errors import AuthApiError
import time
import os
mood_options = ["😡 Giận dữ", "😢 Buồn bã", "😐 Bình thường", "😊 Vui vẻ", "🤩 Hào hứng"]
# ========== 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
})
# ========== 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ý")
# 1. Chọn Thời gian
col_date, col_time = st.columns(2)
with col_date:
d = st.date_input("Chọn ngày", datetime.now())
with col_time:
t = st.time_input("Chọn giờ", datetime.now().time())
selected_dt = combine(d, t)
# 2. Nhập Cảm xúc và Cấp độ
col_mood, col_level = st.columns([2, 3]) # Chia tỷ lệ cột cho cân đối
with col_mood:
mood = st.selectbox(
"Cảm xúc",
mood_options,
index=2 # Mặc định chọn "Bình thường"
)
with col_level:
level = st.select_slider(
"Cấp độ",
options=[1, 2, 3, 4, 5],
value=3 # Mặc định cấp độ trung bình
)
# 3. Nội dung 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():
supabase.table("journals").insert({
"user_id": user.id,
"content": content,
"mood": mood,
"mood_level": level,
"created_at": selected_dt.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):
# Tạo 2 cột để sửa Mood và Level
col_m, col_l = st.columns(2)
try:
old_mood_idx = mood_options.index(row["mood"])
except:
old_mood_idx = 2 # Mặc định là Bình thường nếu có lỗi
with col_m:
new_mood = st.selectbox(
"Cảm xúc",
mood_options,
index=old_mood_idx,
key=f"mood_{row['id']}"
)
with col_l:
new_level = st.select_slider(
"Cấp độ",
options=[1, 2, 3, 4, 5],
value=row.get("mood_level", 3),
key=f"level_{row['id']}"
)
new_content = st.text_area(
"Nội dung",
row["content"],
key=row["id"],
height=120
)
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,
"mood": new_mood,
"mood_level": new_level,
"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()
|