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(""" """, 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()