refactor(poc4): modularize HMM interface
This commit is contained in:
@@ -6,3 +6,4 @@
|
||||
- 외부 KB 로고·전용 글꼴 의존성을 제거하고, 애플리케이션 내부 SVG 워드마크와 해양 청색 계열로 표시한다.
|
||||
- 기존 MCP, VPD, 데이터베이스 스키마 및 도구 계약은 변경하지 않는다.
|
||||
- 로그인 유지 기능은 서버 비밀키로 서명한 7일 만료 토큰을 사용하며, 로그아웃 시 즉시 폐기한다.
|
||||
- 화면 수정의 단일 진입점은 `src/poc4/hmm_ui.py`다. 색상·레이아웃·로그인/헤더 브랜드는 이 모듈에서만 관리한다.
|
||||
|
||||
@@ -52,6 +52,11 @@ from src.oci_genai_sdk import (
|
||||
)
|
||||
from src.poc3.model_registry import load_model_registry, resolve_model_profile
|
||||
from src.poc3.questions import COMMON_DEMO_QUESTIONS
|
||||
from src.poc4.hmm_ui import (
|
||||
apply_hmm_theme,
|
||||
render_hmm_header,
|
||||
render_hmm_login_brand,
|
||||
)
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -1359,6 +1364,89 @@ html, body, [data-testid="stAppViewContainer"], .stApp {
|
||||
</style>
|
||||
"""
|
||||
|
||||
HMM_CLEAN_THEME_CSS = """
|
||||
<style>
|
||||
:root {
|
||||
--hmm-navy: #003b70;
|
||||
--hmm-ink: #172b3a;
|
||||
--hmm-muted: #667785;
|
||||
--hmm-line: #dfe7ed;
|
||||
--hmm-surface: #ffffff;
|
||||
}
|
||||
|
||||
html, body, [data-testid="stAppViewContainer"], .stApp {
|
||||
background: var(--hmm-surface) !important;
|
||||
color: var(--hmm-ink) !important;
|
||||
font-family: "Noto Sans KR", "Malgun Gothic", sans-serif;
|
||||
}
|
||||
|
||||
#MainMenu, header [data-testid="stToolbar"] { display: none !important; }
|
||||
.block-container { max-width: 1180px; padding: 2rem 3rem 4rem; }
|
||||
|
||||
section[data-testid="stSidebar"] {
|
||||
background: #ffffff !important;
|
||||
border-right: 1px solid var(--hmm-line);
|
||||
}
|
||||
section[data-testid="stSidebar"] > div { background: #ffffff !important; }
|
||||
section[data-testid="stSidebar"] [data-testid="stMarkdownContainer"] p { color: var(--hmm-ink); }
|
||||
|
||||
input, textarea, [data-baseweb="select"] > div {
|
||||
border-radius: 4px !important;
|
||||
border-color: #cfdbe4 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
div[data-testid="stButton"] > button,
|
||||
div[data-testid="stFormSubmitButton"] > button {
|
||||
border-radius: 4px !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
div[data-testid="stButton"] > button[kind="primary"],
|
||||
div[data-testid="stFormSubmitButton"] > button[data-testid="stBaseButton-primaryFormSubmit"] {
|
||||
background: var(--hmm-navy) !important;
|
||||
border-color: var(--hmm-navy) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.hmm-app-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 18px;
|
||||
margin: 0 0 28px;
|
||||
padding: 0 0 22px;
|
||||
border-bottom: 1px solid var(--hmm-line);
|
||||
}
|
||||
.hmm-app-wordmark, .hmm-login-wordmark {
|
||||
color: var(--hmm-navy);
|
||||
font-size: 1.35rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.hmm-app-header h1 { margin: 0; color: var(--hmm-ink); font-size: 1.7rem; }
|
||||
.hmm-app-header p { margin: 0; color: var(--hmm-muted); font-size: 0.92rem; }
|
||||
|
||||
.st-key-kb_login_container { max-width: 440px; margin: 12vh auto 0; }
|
||||
.kb-login-brand { text-align: left; }
|
||||
.kb-login-kicker { margin-top: 12px; color: var(--hmm-muted); font-size: 0.76rem; font-weight: 700; }
|
||||
.kb-login-brand h1 { margin: 12px 0 8px; color: var(--hmm-ink); font-size: 1.7rem; }
|
||||
.kb-login-brand p { margin: 0 0 22px; color: var(--hmm-muted); }
|
||||
.st-key-kb_login_container [data-testid="stForm"] {
|
||||
padding: 0; border: 0; background: transparent; box-shadow: none;
|
||||
}
|
||||
.st-key-kb_login_container [data-testid="stFormSubmitButton"] button {
|
||||
min-height: 44px; background: var(--hmm-navy); border-color: var(--hmm-navy);
|
||||
color: #ffffff; box-shadow: none;
|
||||
}
|
||||
.kb-login-note { color: var(--hmm-muted); }
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.block-container { padding: 1.25rem 1.25rem 3rem; }
|
||||
.hmm-app-header { display: block; }
|
||||
.hmm-app-header h1 { margin-top: 10px; }
|
||||
.st-key-kb_login_container { margin-top: 8vh; }
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class McpServer:
|
||||
@@ -1799,8 +1887,8 @@ def _question_label(question: object) -> str:
|
||||
return f"{question_id} · {category} · {text}"
|
||||
|
||||
|
||||
def _apply_kb_theme() -> None:
|
||||
st.markdown(HMM_MINIMAL_THEME_CSS, unsafe_allow_html=True)
|
||||
def _apply_hmm_theme() -> None:
|
||||
apply_hmm_theme(st)
|
||||
|
||||
|
||||
def _portal_auth_value(name: str) -> str:
|
||||
@@ -1900,17 +1988,7 @@ def _clear_portal_remembered_session() -> None:
|
||||
|
||||
def _render_portal_login() -> None:
|
||||
with st.container(key="kb_login_container"):
|
||||
st.markdown(
|
||||
f"""
|
||||
<section class="kb-login-brand">
|
||||
<div class="hmm-login-wordmark" aria-label="HMM">HMM</div>
|
||||
<div class="kb-login-kicker">HMM SHIPPING & LOGISTICS DEMO</div>
|
||||
<h1>HMM AI 업무 에이전트</h1>
|
||||
<p>사용자 인증 후 해운·물류 AI 질의와 보안 관리 기능을 이용할 수 있습니다.</p>
|
||||
</section>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
render_hmm_login_brand(st)
|
||||
if not _portal_auth_configured():
|
||||
st.error("포털 로그인 설정을 확인해 주세요.")
|
||||
return
|
||||
@@ -1963,27 +2041,8 @@ def _logout_portal() -> None:
|
||||
st.rerun()
|
||||
|
||||
|
||||
def _render_kb_header() -> None:
|
||||
st.markdown(
|
||||
f"""
|
||||
<section class="kb-hero">
|
||||
<div class="kb-hero-main">
|
||||
<div class="kb-brand-row">
|
||||
<div>
|
||||
<div class="kb-brand-name">HMM Digital Operations Demo</div>
|
||||
<div class="kb-brand-sub">Connect Values · Navigate Growth · AI 업무 질의</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1>HMM AI 업무 에이전트</h1>
|
||||
<p>
|
||||
사용자 권한을 기준으로 해운·물류 업무의 AI 질의 응답과 보안 관리를 수행하는
|
||||
DEMO 화면입니다.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
def _render_hmm_header() -> None:
|
||||
render_hmm_header(st)
|
||||
|
||||
|
||||
def _render_vpd_user_card(preset: VpdTokenPreset) -> None:
|
||||
@@ -6859,7 +6918,7 @@ def main() -> None:
|
||||
st.set_page_config(
|
||||
page_title="HMM AI Operations Console", page_icon="⛴️", layout="wide"
|
||||
)
|
||||
_apply_kb_theme()
|
||||
_apply_hmm_theme()
|
||||
_restore_portal_remembered_session()
|
||||
if not st.session_state.get(PORTAL_AUTHENTICATED_KEY, False):
|
||||
_render_portal_login()
|
||||
@@ -6923,7 +6982,7 @@ def main() -> None:
|
||||
)
|
||||
if st.session_state.get(query_model_profile_key) not in model_profile_keys:
|
||||
st.session_state[query_model_profile_key] = default_query_model_profile
|
||||
_render_kb_header()
|
||||
_render_hmm_header()
|
||||
with st.sidebar:
|
||||
st.caption(
|
||||
f"포털 사용자 · {st.session_state.get(PORTAL_AUTH_USER_KEY, '')}"
|
||||
|
||||
1
poc4_active_source_20260714/src/poc4/__init__.py
Normal file
1
poc4_active_source_20260714/src/poc4/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""PoC4 UI support modules."""
|
||||
76
poc4_active_source_20260714/src/poc4/hmm_ui.py
Normal file
76
poc4_active_source_20260714/src/poc4/hmm_ui.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""Small, self-contained HMM presentation layer for the PoC4 Streamlit app."""
|
||||
|
||||
HMM_CLEAN_THEME_CSS = """
|
||||
<style>
|
||||
:root { --hmm-navy:#003b70; --hmm-ink:#172b3a; --hmm-muted:#667785; --hmm-line:#dfe7ed; }
|
||||
html, body, [data-testid="stAppViewContainer"], .stApp {
|
||||
background:#fff !important; color:var(--hmm-ink) !important;
|
||||
font-family:"Noto Sans KR","Malgun Gothic",sans-serif;
|
||||
}
|
||||
#MainMenu, header [data-testid="stToolbar"] { 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(--hmm-line); }
|
||||
input, textarea, [data-baseweb="select"] > div {
|
||||
border-radius:4px !important; border-color:#cfdbe4 !important; box-shadow:none !important;
|
||||
}
|
||||
div[data-testid="stButton"] > button, div[data-testid="stFormSubmitButton"] > button {
|
||||
border-radius:4px !important; box-shadow:none !important;
|
||||
}
|
||||
div[data-testid="stButton"] > button[kind="primary"],
|
||||
div[data-testid="stFormSubmitButton"] > button[data-testid="stBaseButton-primaryFormSubmit"] {
|
||||
background:var(--hmm-navy) !important; border-color:var(--hmm-navy) !important; color:#fff !important;
|
||||
}
|
||||
.hmm-app-header { margin:0 0 28px; padding:0 0 22px; border-bottom:1px solid var(--hmm-line); }
|
||||
.hmm-app-wordmark, .hmm-login-wordmark { color:var(--hmm-navy); font-size:1.35rem; font-weight:800; letter-spacing:.08em; }
|
||||
.hmm-app-header h1 { margin:10px 0 8px; color:var(--hmm-ink); font-size:1.7rem; }
|
||||
.hmm-app-header p { margin:0; color:var(--hmm-muted); font-size:.92rem; }
|
||||
.st-key-kb_login_container { max-width:440px; margin:12vh auto 0; }
|
||||
.kb-login-brand { text-align:left; }
|
||||
.kb-login-kicker { margin-top:12px; color:var(--hmm-muted); font-size:.76rem; font-weight:700; }
|
||||
.kb-login-brand h1 { margin:12px 0 8px; color:var(--hmm-ink); font-size:1.7rem; }
|
||||
.kb-login-brand p { margin:0 0 22px; color:var(--hmm-muted); }
|
||||
.st-key-kb_login_container [data-testid="stForm"] { padding:0; border:0; background:transparent; box-shadow:none; }
|
||||
.st-key-kb_login_container [data-testid="stFormSubmitButton"] button {
|
||||
min-height:44px; background:var(--hmm-navy); border-color:var(--hmm-navy); color:#fff; box-shadow:none;
|
||||
}
|
||||
.kb-login-note { color:var(--hmm-muted); }
|
||||
@media (max-width:760px) {
|
||||
.block-container { padding:1.25rem 1.25rem 3rem; }
|
||||
.st-key-kb_login_container { margin-top:8vh; }
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
|
||||
def apply_hmm_theme(st: object) -> None:
|
||||
st.markdown(HMM_CLEAN_THEME_CSS, unsafe_allow_html=True)
|
||||
|
||||
|
||||
def render_hmm_login_brand(st: object) -> None:
|
||||
st.markdown(
|
||||
"""
|
||||
<section class="kb-login-brand">
|
||||
<div class="hmm-login-wordmark" aria-label="HMM">HMM</div>
|
||||
<div class="kb-login-kicker">HMM SHIPPING & LOGISTICS DEMO</div>
|
||||
<h1>HMM AI 업무 에이전트</h1>
|
||||
<p>사용자 인증 후 해운·물류 AI 질의와 보안 관리 기능을 이용할 수 있습니다.</p>
|
||||
</section>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
|
||||
def render_hmm_header(st: object) -> None:
|
||||
st.markdown(
|
||||
"""
|
||||
<section class="hmm-app-header">
|
||||
<div class="hmm-app-wordmark">HMM</div>
|
||||
<h1>AI 업무 에이전트</h1>
|
||||
<p>사용자 권한에 맞는 업무 질의와 보안 관리 기능을 제공합니다.</p>
|
||||
</section>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
Reference in New Issue
Block a user