personal_diary1 / src /streamlit_app.py
GphaHoa's picture
Upload streamlit_app.py
7894107 verified
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()