from __future__ import annotations import base64 from datetime import datetime, timezone from pathlib import Path import streamlit as st try: from streamlit_extras.metric_cards import style_metric_cards except ImportError: style_metric_cards = None from app.config import PROJECT_ROOT MASCOT_DIR = PROJECT_ROOT / "assets" / "mascots" def mascot_path(name: str) -> Path: path = MASCOT_DIR / f"{name}.png" return path if path.exists() else MASCOT_DIR / "chatvns.png" def mascot_data_uri(name: str) -> str: path = mascot_path(name) if not path.exists(): return "" encoded = base64.b64encode(path.read_bytes()).decode("ascii") return f"data:image/png;base64,{encoded}" def inject_app_css() -> None: st.markdown( """ """, unsafe_allow_html=True, ) def render_hero(ticker_count: int, top_k: int) -> None: image_uri = mascot_data_uri("chatvns") st.markdown( f"""
ChatVNS mascot
Trợ lý chứng khoán Việt Nam

ChatVNS

Trợ lý RAG cho dữ liệu chứng khoán Việt Nam — trả lời ngắn gọn, ưu tiên dữ liệu mới nhất và luôn chỉ rõ nguồn.

{ticker_count} mã có dữ liệu Truy xuất kết hợp · Top {top_k} Ưu tiên trích nguồn
""", unsafe_allow_html=True, ) def render_welcome() -> None: image_uri = mascot_data_uri("welcome") st.markdown( f"""
ChatVNS welcome mascot

Chào bạn, mình là ChatVNS

Hỏi về giá gần nhất, báo cáo doanh nghiệp, tin tức hoặc chỉ báo kỹ thuật. Chọn một gợi ý bên dưới để bắt đầu.

""", unsafe_allow_html=True, ) def render_trust_note() -> None: image_uri = mascot_data_uri("trust") st.markdown( f"""
Source trust mascot Nguồn được lấy từ dữ liệu đã thu thập và đưa trực tiếp vào ngữ cảnh của câu trả lời.
""", unsafe_allow_html=True, ) def render_disclaimer() -> None: st.markdown( """
Lưu ý: Nội dung do AI tổng hợp nhằm mục đích tham khảo, không phải khuyến nghị mua/bán hay tư vấn đầu tư cá nhân. Dữ liệu thị trường có thể có độ trễ; hãy kiểm tra lại với nguồn giao dịch chính thức trước khi ra quyết định.
""", unsafe_allow_html=True, ) def freshness_label(crawled_at: str | None) -> tuple[str, str]: if not crawled_at: return "Chưa xác định thời điểm cập nhật", "chatvns-stale" try: parsed = datetime.fromisoformat(str(crawled_at).replace("Z", "+00:00")) if parsed.tzinfo is None: parsed = parsed.replace(tzinfo=timezone.utc) age = datetime.now(timezone.utc) - parsed.astimezone(timezone.utc) minutes = max(0, int(age.total_seconds() // 60)) if minutes < 60: return f"Cập nhật {minutes} phút trước", "chatvns-fresh" hours = minutes // 60 if hours < 24: return f"Cập nhật {hours} giờ trước", "chatvns-fresh" if hours < 4 else "chatvns-stale" return f"Cập nhật {hours // 24} ngày trước", "chatvns-stale" except (TypeError, ValueError): return "Không đọc được thời điểm cập nhật", "chatvns-stale" def apply_metric_card_style() -> None: if style_metric_cards is not None: style_metric_cards( background_color="#ffffff", border_left_color="#0877e8", border_color="#d8eaff", box_shadow=True, ) def render_section_header(label: str, description: str = "") -> None: st.subheader(label, divider="blue") if description: st.caption(description)