Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pyrebase | |
| import os | |
| import base64 | |
| import json | |
| import requests | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| # ==== Firebase Config & Initialization ==== | |
| def initialize_firebase(): | |
| """Khởi tạo và trả về các đối tượng Firebase. Sử dụng singleton pattern.""" | |
| if "firebase_app" not in st.session_state: | |
| firebase_config = { | |
| "apiKey": os.getenv("FIREBASE_API_KEY"), | |
| "authDomain": os.getenv("FIREBASE_AUTH_DOMAIN"), | |
| "projectId": os.getenv("FIREBASE_PROJECT_ID"), | |
| "storageBucket": os.getenv("FIREBASE_STORAGE_BUCKET"), | |
| "messagingSenderId": os.getenv("FIREBASE_MESSAGING_SENDER_ID"), | |
| "appId": os.getenv("FIREBASE_APP_ID"), | |
| "databaseURL": os.getenv("FIREBASE_DATABASE_URL", ""), | |
| } | |
| st.session_state.firebase_app = pyrebase.initialize_app(firebase_config) | |
| auth_fb = st.session_state.firebase_app.auth() | |
| return auth_fb | |
| # ==== CSS Dùng chung ==== | |
| def load_css(): | |
| """Tải CSS theme Cyberpunk Neon.""" | |
| st.markdown(""" | |
| <style> | |
| /* === Hide default Streamlit elements === */ | |
| section[data-testid="stSidebar"] {display: none;} | |
| header {visibility: hidden;} | |
| /* === Main container styling === */ | |
| .stApp { | |
| background-color: #0d1117; | |
| color: #c9d1d9; | |
| } | |
| /* === Main content area styling === */ | |
| .main .block-container { | |
| max-width: 1100px; /* Rộng hơn cho layout mới */ | |
| padding: 1rem 1.5rem; | |
| } | |
| .not-logged-in .main .block-container { | |
| max-width: 450px; | |
| } | |
| /* === Custom Navigation Bar === */ | |
| .nav-container { | |
| display: flex; | |
| justify-content: center; | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| padding: 0.5rem; | |
| background-color: rgba(30, 41, 59, 0.5); | |
| border-radius: 12px; | |
| border: 1px solid rgba(100, 116, 139, 0.3); | |
| } | |
| .nav-container .stButton>button { | |
| background: transparent; | |
| border: 2px solid transparent; | |
| transition: all 0.3s ease; | |
| font-weight: 600; | |
| color: #94a3b8; | |
| } | |
| .nav-container .stButton>button:hover { | |
| color: #ffffff; | |
| border-color: rgba(48, 207, 208, 0.5); | |
| box-shadow: none; | |
| transform: none; /* FIX: Vô hiệu hóa hiệu ứng transform cho nút nav */ | |
| } | |
| /* Style for the ACTIVE button */ | |
| .nav-container .stButton>button.active-nav-button { | |
| color: #ffffff; | |
| border-color: #30cfd0; | |
| box-shadow: 0 0 15px rgba(48, 207, 208, 0.4); | |
| } | |
| /* === Card styling with "frosted glass" effect === */ | |
| div[data-testid="stTabs-panel"], .report-container { | |
| background-color: rgba(30, 41, 59, 0.5); | |
| backdrop-filter: blur(12px); | |
| border-radius: 16px; | |
| padding: 2.5rem; | |
| box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); | |
| border: 1px solid rgba(100, 116, 139, 0.3); | |
| } | |
| /* === General Button styling === */ | |
| .stButton>button { | |
| border-radius: 8px; | |
| border: 1px solid #30cfd0; | |
| padding: 12px 20px; | |
| color: white; | |
| background: linear-gradient(90deg, #30cfd0, #330867); | |
| transition: all 0.3s ease-in-out; | |
| font-weight: 600; | |
| } | |
| .stButton>button:hover { | |
| box-shadow: 0 0 20px #30cfd0; | |
| transform: translateY(-2px); | |
| } | |
| /* === Input fields styling === */ | |
| .stTextInput label, .stDateInput label { | |
| color: #c9d1d9 !important; | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| } | |
| .stTextInput>div>div>input, .stDateInput>div>div>input { | |
| background-color: rgba(15, 23, 42, 0.5); | |
| border: 1px solid #64748b; | |
| border-radius: 8px; | |
| padding: 12px; | |
| color: #ffffff; | |
| } | |
| .stTextInput>div>div>input:focus, .stDateInput>div>div>input:focus { | |
| border-color: #30cfd0; | |
| box-shadow: 0 0 10px rgba(48, 207, 208, 0.5); | |
| } | |
| /* === Header styling === */ | |
| h1, h2 { | |
| text-align: center; | |
| color: #ffffff; | |
| font-weight: 700; | |
| letter-spacing: 1px; | |
| text-shadow: 0 0 10px rgba(48, 207, 208, 0.5); | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ==== Hàm Render Avatar ==== | |
| def render_avatar(uid, container, get_avatar_blob_func): | |
| avatar_bytes = get_avatar_blob_func(uid) | |
| if avatar_bytes: | |
| img_base64 = base64.b64encode(avatar_bytes).decode() | |
| avatar_html = f'<img src="data:image/png;base64,{img_base64}" style="border-radius:50%; border:4px solid #30cfd0; width:120px; height:120px; object-fit:cover; box-shadow:0 0 20px rgba(48, 207, 208, 0.5);">' | |
| else: | |
| avatar_html = """ | |
| <div style='border-radius:50%; background:linear-gradient(135deg, #30cfd0, #330867); | |
| width:120px; height:120px; display:flex; align-items:center; justify-content:center; | |
| box-shadow:0 0 20px rgba(48, 207, 208, 0.3);'> | |
| <span style='font-size:3em; color:#fff;'>👤</span> | |
| </div> | |
| <div style='margin-top:8px; color:#94a3b8; font-size:0.9em;'>Chưa có avatar</div> | |
| """ | |
| container.html(f"<div style='display:flex; flex-direction:column; align-items:center; margin-bottom: 1rem;'>{avatar_html}</div>") | |
| # ==== Hàm gọi Gemini API ==== | |
| def call_genai_summary(report_data, stock_code, time_period): | |
| api_key = os.getenv("GEMINI_API_KEY") | |
| if not api_key: | |
| st.error("Vui lòng cung cấp GEMINI_API_KEY trong file .env") | |
| return "Lỗi: Chưa cấu hình API Key." | |
| url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}" | |
| prompt = f""" | |
| Hãy tóm tắt ngắn gọn, chuyên nghiệp về mã cổ phiếu {stock_code} trong giai đoạn {time_period[0]} đến {time_period[1]} dựa trên dữ liệu JSON sau: | |
| {json.dumps(report_data, ensure_ascii=False, indent=2)} | |
| """ | |
| payload = {"contents": [{"parts": [{"text": prompt}]}]} | |
| try: | |
| response = requests.post(url, json=payload, timeout=45) | |
| response.raise_for_status() | |
| data = response.json() | |
| return data["candidates"][0]["content"]["parts"][0]["text"] | |
| except Exception as e: | |
| return f"Lỗi khi gọi Gemini API: {e}" | |