Files
vpd-permission-poc/ai-web-agent-console/ai_web_agent_console/presentation.py

125 lines
7.7 KiB
Python

"""Shared, intentionally small Streamlit presentation primitives."""
from __future__ import annotations
from html import escape
from typing import Any
from .profile import AppProfile
def apply_console_theme(st: Any, profile: AppProfile) -> None:
"""Apply one predictable light theme from the product profile."""
st.markdown(
f"""
<style>
:root {{ color-scheme: light !important; --console-primary: {escape(profile.primary_color)};
--console-text: {escape(profile.text_color)}; --console-muted: {escape(profile.muted_color)};
--console-border: {escape(profile.border_color)}; }}
html, body, [data-testid="stAppViewContainer"], .stApp {{ background:#fff !important;
color:var(--console-text) !important; color-scheme:light !important;
font-family:"Noto Sans KR","Malgun Gothic",sans-serif; }}
header[data-testid="stHeader"] {{ display:none !important; }}
.block-container {{ max-width:1180px; padding:2rem 3rem 4rem; }}
section[data-testid="stSidebar"], section[data-testid="stSidebar"] > div {{ background:#fff !important; }}
section[data-testid="stSidebar"] {{ border-right:1px solid var(--console-border); }}
[data-testid="stAppViewContainer"] p, [data-testid="stAppViewContainer"] span,
[data-testid="stAppViewContainer"] label, [data-testid="stAppViewContainer"] h1,
[data-testid="stAppViewContainer"] h2, [data-testid="stAppViewContainer"] h3,
[data-testid="stAppViewContainer"] li, [data-testid="stAppViewContainer"] dt,
[data-testid="stAppViewContainer"] dd, [data-testid="stAppViewContainer"] blockquote,
[data-testid="stAppViewContainer"] input, [data-testid="stAppViewContainer"] textarea,
section[data-testid="stSidebar"] * {{ color:var(--console-text) !important;
-webkit-text-fill-color:var(--console-text) !important; }}
input, textarea, [data-baseweb="select"] > div, [data-testid="stSidebar"] button {{
background:#fff !important; border:1px solid var(--console-border) !important;
border-radius:4px !important; box-shadow:none !important; }}
div[data-testid="stButton"] > button, div[data-testid="stFormSubmitButton"] > button {{
background:#fff !important; color:var(--console-text) !important;
border:1px solid var(--console-border) !important;
border-radius:4px !important; box-shadow:none !important; }}
div[data-testid="stButton"] > button *,
div[data-testid="stFormSubmitButton"] > button * {{
color:var(--console-text) !important; -webkit-text-fill-color:var(--console-text) !important; }}
div[data-testid="stButton"] > button[kind="primary"],
div[data-testid="stFormSubmitButton"] > button[data-testid="stBaseButton-primaryFormSubmit"] {{
background:var(--console-primary) !important; border-color:var(--console-primary) !important; color:#fff !important; }}
div[data-testid="stButton"] > button[kind="primary"] *,
div[data-testid="stFormSubmitButton"] > button[data-testid="stBaseButton-primaryFormSubmit"] * {{
color:#fff !important; -webkit-text-fill-color:#fff !important; }}
/* Streamlit JSON/code components otherwise retain a dark-surface theme while
the console text is forced dark, making MCP details unreadable. */
[data-testid="stJson"], [data-testid="stJson"] > div,
[data-testid="stCodeBlock"], [data-testid="stCodeBlock"] > div,
[data-testid="stCode"], [data-testid="stCode"] > div {{
background:#f6f8fa !important; color:var(--console-text) !important;
border-color:var(--console-border) !important; }}
[data-testid="stJson"] *, [data-testid="stCodeBlock"] *, [data-testid="stCode"] *,
[data-testid="stJson"] pre, [data-testid="stCodeBlock"] pre, [data-testid="stCode"] pre {{
color:var(--console-text) !important;
-webkit-text-fill-color:var(--console-text) !important; }}
[data-testid="stJson"] button, [data-testid="stCodeBlock"] button, [data-testid="stCode"] button {{
background:#fff !important; border-color:var(--console-border) !important; }}
[data-testid="stExpander"] summary {{
background:#f6f8fa !important; color:var(--console-text) !important;
border-color:var(--console-border) !important; }}
[data-testid="stExpander"] summary * {{
color:var(--console-text) !important;
-webkit-text-fill-color:var(--console-text) !important; }}
[data-baseweb="tab-list"], [data-testid="stTabs"] [role="tablist"] {{
border-bottom:1px solid var(--console-border) !important; }}
[data-baseweb="tab-list"] [role="tab"], [data-testid="stTab"] {{
background:#fff !important; color:var(--console-text) !important;
-webkit-text-fill-color:var(--console-text) !important;
border-radius:4px 4px 0 0 !important; }}
[data-baseweb="tab-list"] [role="tab"] *, [data-testid="stTab"] * {{
color:var(--console-text) !important;
-webkit-text-fill-color:var(--console-text) !important; }}
[data-baseweb="tab-list"] [role="tab"]:hover, [data-testid="stTab"]:hover {{
background:#f6f8fa !important; }}
[data-baseweb="tab-list"] [role="tab"][aria-selected="true"],
[data-testid="stTab"][aria-selected="true"] {{
color:var(--console-primary) !important;
-webkit-text-fill-color:var(--console-primary) !important;
border-bottom:3px solid var(--console-primary) !important; }}
[data-baseweb="tab-list"] [role="tab"][aria-selected="true"] *,
[data-testid="stTab"][aria-selected="true"] * {{
color:var(--console-primary) !important;
-webkit-text-fill-color:var(--console-primary) !important; }}
.console-header {{ margin:0 0 28px; padding:0 0 22px; border-bottom:1px solid var(--console-border); }}
.console-wordmark {{ color:var(--console-primary); font-size:1.35rem; font-weight:800; letter-spacing:.08em; }}
.console-header h1 {{ margin:10px 0 8px; font-size:1.7rem; }}
.console-muted {{ color:var(--console-muted) !important; }}
.st-key-console_login_container {{ max-width:440px; margin:12vh auto 0; }}
.console-login {{ text-align:left; }}
.console-login h1 {{ margin:12px 0 8px; font-size:1.7rem; }}
a.console-logout-button {{ display:block; width:100%; padding:.55rem .8rem;
margin:.25rem 0 .75rem; background:#fff; color:var(--console-text) !important;
-webkit-text-fill-color:var(--console-text) !important;
border:1px solid var(--console-border); border-radius:4px;
text-align:center; text-decoration:none; font-weight:700; }}
a.console-logout-button:hover {{ background:#f6f8fa; }}
@media (max-width:760px) {{ .block-container {{ padding:1.25rem 1.25rem 3rem; }} .st-key-console_login_container {{ margin-top:8vh; }} }}
</style>
""",
unsafe_allow_html=True,
)
def render_console_header(st: Any, profile: AppProfile) -> None:
st.markdown(
f"""<section class="console-header"><div class="console-wordmark">{escape(profile.short_name)}</div>
<h1>{escape(profile.header_title)}</h1><p class="console-muted">{escape(profile.header_description)}</p></section>""",
unsafe_allow_html=True,
)
def render_login_brand(st: Any, profile: AppProfile) -> None:
st.markdown(
f"""<section class="console-login"><div class="console-wordmark">{escape(profile.short_name)}</div>
<p class="console-muted">{escape(profile.login_kicker)}</p><h1>{escape(profile.login_title)}</h1>
<p class="console-muted">{escape(profile.login_description)}</p></section>""",
unsafe_allow_html=True,
)