| import base64 | |
| import os | |
| from urllib import request | |
| from pathlib import Path | |
| import streamlit as st | |
| def _icon_data_uri(filename: str) -> str: | |
| icon_path = Path(__file__).resolve().parent.parent / "icons" / filename | |
| if not icon_path.exists(): | |
| return "" | |
| try: | |
| encoded = base64.b64encode(icon_path.read_bytes()).decode("ascii") | |
| except Exception: | |
| return "" | |
| return f"data:image/png;base64,{encoded}" | |
| def _build_sidebar_icon_css() -> str: | |
| """Use named icons for sidebar items: home/prob/batch/molecule/manual/ai.""" | |
| fallback = { | |
| 1: "π ", | |
| 2: "π", | |
| 3: "π¦", | |
| 4: "π§¬", | |
| 5: "βοΈ", | |
| 6: "π§ ", | |
| 7: "β¨", | |
| 8: "π¬", | |
| } | |
| icon_name = { | |
| 1: "home1.png", | |
| 2: "probe1.png", | |
| 3: "batch1.png", | |
| 4: "molecule1.png", | |
| 5: "manual1.png", | |
| 6: "ai1.png", | |
| 7: "rnn1.png", | |
| 8: "feedback.png", | |
| } | |
| rules = [ | |
| '[data-testid="stSidebarNav"] ul li a { position: relative; padding-left: 3.9rem !important; }', | |
| '[data-testid="stSidebarNav"] ul li a::before { content: ""; position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 46px; height: 46px; background-size: contain; background-repeat: no-repeat; background-position: center; }', | |
| ] | |
| for idx in range(1, 9): | |
| uri = _icon_data_uri(icon_name[idx]) | |
| if uri: | |
| rules.append( | |
| '[data-testid="stSidebarNav"] ul li:nth-of-type(%d) a::before { content: ""; background-image: url("%s"); }' | |
| % (idx, uri) | |
| ) | |
| else: | |
| emoji = fallback[idx] | |
| rules.append( | |
| '[data-testid="stSidebarNav"] ul li:nth-of-type(%d) a::before { content: "%s "; background-image: none; width: auto; height: auto; }' | |
| % (idx, emoji) | |
| ) | |
| return "\n".join(rules) | |
| def _log_visit_once_per_session() -> None: | |
| if st.session_state.get("_visit_logged"): | |
| return | |
| webhook_url = os.getenv("FEEDBACK_WEBHOOK_URL", "").strip() | |
| webhook_token = os.getenv("FEEDBACK_WEBHOOK_TOKEN", "").strip() | |
| if not webhook_url: | |
| return | |
| endpoint = webhook_url | |
| sep = "&" if "?" in webhook_url else "?" | |
| endpoint = f"{webhook_url}{sep}event=visit" | |
| if webhook_token: | |
| endpoint = f"{endpoint}&token={webhook_token}" | |
| try: | |
| with request.urlopen(endpoint, timeout=10): | |
| pass | |
| except Exception: | |
| pass | |
| st.session_state["_visit_logged"] = True | |
| def apply_global_style() -> None: | |
| _log_visit_once_per_session() | |
| icon_css = _build_sidebar_icon_css() | |
| css = """ | |
| <style> | |
| [data-testid="stSidebarNav"] ul li a { | |
| font-size: 1.2rem !important; | |
| font-weight: 600 !important; | |
| border-radius: 12px !important; | |
| padding: 0.42rem 0.65rem !important; | |
| margin-bottom: 0.38rem !important; | |
| } | |
| [data-testid="stSidebarNav"] ul li a span { | |
| font-size: 1.2rem !important; | |
| font-weight: 600 !important; | |
| } | |
| [data-testid="stSidebarNav"] ul li a[aria-current="page"] { | |
| background-color: rgba(44, 123, 29, 0.24) !important; | |
| } | |
| [data-testid="stSidebarNav"] ul li a[aria-current="page"] span { | |
| color: #1f3f7a !important; | |
| font-weight: 700 !important; | |
| } | |
| __ICON_CSS__ | |
| </style> | |
| """ | |
| st.markdown(css.replace("__ICON_CSS__", icon_css), unsafe_allow_html=True) | |