refs #701: modularize HMM console profile and scenarios
This commit is contained in:
@@ -51,12 +51,13 @@ from src.oci_genai_sdk import (
|
||||
temperature_for_model_profile,
|
||||
)
|
||||
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,
|
||||
from src.agent_console.presentation import (
|
||||
apply_console_theme,
|
||||
render_console_header,
|
||||
render_login_brand,
|
||||
)
|
||||
from src.agent_console.profile import AppProfile, AppProfileError, load_app_profile
|
||||
from src.poc4.scenarios import ScenarioConfigError, load_demo_scenarios
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -76,6 +77,8 @@ DEFAULT_QUERY_MODEL_PROFILE = "gpt54_mini_oci"
|
||||
ENV_FILE = ROOT / ".env"
|
||||
MCP_SERVERS_FILE = ROOT / "config" / "mcp_servers.json"
|
||||
VPD_TOKEN_PRESETS_FILE = ROOT / "config" / "vpd_token_presets.json"
|
||||
DEMO_SCENARIOS_FILE = ROOT / "config" / "hmm_demo_scenarios.json"
|
||||
APP_PROFILE_FILE = ROOT / "config" / "app_profile.json"
|
||||
CHAT_DB_FILE = ROOT / "data" / "poc4_mcp_chat.sqlite3"
|
||||
DEFAULT_VPD_USER_ID = "FC00789"
|
||||
VPD_OPERATIONS_URL = "https://kb.cloud-handson.com/"
|
||||
@@ -1884,12 +1887,12 @@ def new_conversation_id() -> str:
|
||||
def _question_label(question: object) -> str:
|
||||
question_id = str(getattr(question, "question_id"))
|
||||
category = str(getattr(question, "category"))
|
||||
text = str(getattr(question, "text"))
|
||||
return f"{question_id} · {category} · {text}"
|
||||
title = str(getattr(question, "title", getattr(question, "text")))
|
||||
return f"{question_id} · {category} · {title}"
|
||||
|
||||
|
||||
def _apply_hmm_theme() -> None:
|
||||
apply_hmm_theme(st)
|
||||
def _apply_console_theme(profile: AppProfile) -> None:
|
||||
apply_console_theme(st, profile)
|
||||
|
||||
|
||||
def _portal_auth_value(name: str) -> str:
|
||||
@@ -1987,9 +1990,9 @@ def _clear_portal_remembered_session() -> None:
|
||||
del st.query_params[PORTAL_REMEMBER_TOKEN_PARAM]
|
||||
|
||||
|
||||
def _render_portal_login() -> None:
|
||||
with st.container(key="kb_login_container"):
|
||||
render_hmm_login_brand(st)
|
||||
def _render_portal_login(profile: AppProfile) -> None:
|
||||
with st.container(key="console_login_container"):
|
||||
render_login_brand(st, profile)
|
||||
if not _portal_auth_configured():
|
||||
st.info("데모 계정 설정 중입니다. 운영 담당자에게 계정 발급을 요청해 주세요.")
|
||||
return
|
||||
@@ -2027,9 +2030,7 @@ def _render_portal_login() -> None:
|
||||
if st.session_state.get(PORTAL_LOGIN_FAILURE_KEY, False):
|
||||
st.error("사용자 ID 또는 비밀번호를 확인해 주세요.")
|
||||
st.markdown(
|
||||
'<div class="kb-login-note">'
|
||||
'인증된 DEMO 사용자만 접근할 수 있습니다.'
|
||||
'</div>',
|
||||
f'<p class="console-muted">{html.escape(profile.login_footer)}</p>',
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
@@ -2042,8 +2043,8 @@ def _logout_portal() -> None:
|
||||
st.rerun()
|
||||
|
||||
|
||||
def _render_hmm_header() -> None:
|
||||
render_hmm_header(st)
|
||||
def _render_app_header(profile: AppProfile) -> None:
|
||||
render_console_header(st, profile)
|
||||
|
||||
|
||||
def _render_vpd_user_card(preset: VpdTokenPreset) -> None:
|
||||
@@ -3994,11 +3995,12 @@ def discover_enabled_server_tools(
|
||||
|
||||
def _mcp_server_cache_rows(
|
||||
servers: list[McpServer],
|
||||
) -> tuple[tuple[str, str, str, tuple[str, ...], str, str], ...]:
|
||||
) -> tuple[tuple[str, str, str, str, tuple[str, ...], str, str], ...]:
|
||||
return tuple(
|
||||
(
|
||||
server.server_id,
|
||||
server.endpoint_url,
|
||||
server.auth_token_env,
|
||||
server.default_tool,
|
||||
server.tool_allowlist,
|
||||
server.router_model_profile,
|
||||
@@ -4009,16 +4011,17 @@ def _mcp_server_cache_rows(
|
||||
|
||||
|
||||
def _mcp_servers_from_cache_rows(
|
||||
rows: tuple[tuple[str, str, str, tuple[str, ...], str, str], ...],
|
||||
rows: tuple[tuple[str, str, str, str, tuple[str, ...], str, str], ...],
|
||||
) -> list[McpServer]:
|
||||
return [
|
||||
McpServer(
|
||||
server_id=row[0],
|
||||
endpoint_url=row[1],
|
||||
default_tool=row[2],
|
||||
tool_allowlist=tuple(row[3]),
|
||||
router_model_profile=row[4],
|
||||
description=row[5],
|
||||
auth_token_env=row[2],
|
||||
default_tool=row[3],
|
||||
tool_allowlist=tuple(row[4]),
|
||||
router_model_profile=row[5],
|
||||
description=row[6],
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
@@ -4026,7 +4029,7 @@ def _mcp_servers_from_cache_rows(
|
||||
|
||||
@st.cache_data(show_spinner=False)
|
||||
def cached_discover_enabled_server_tools(
|
||||
server_rows: tuple[tuple[str, str, str, tuple[str, ...], str, str], ...],
|
||||
server_rows: tuple[tuple[str, str, str, str, tuple[str, ...], str, str], ...],
|
||||
token_fingerprint: str,
|
||||
cache_generation: int,
|
||||
_bearer_token: str,
|
||||
@@ -6917,19 +6920,25 @@ def _process_submitted_question(
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
profile = load_app_profile(APP_PROFILE_FILE)
|
||||
except AppProfileError as exc:
|
||||
st.set_page_config(page_title="AI 업무 에이전트", layout="wide")
|
||||
st.error(str(exc))
|
||||
return
|
||||
st.set_page_config(
|
||||
page_title="스마일게이트 게임 데이터 AI 콘솔", page_icon="🎮", layout="wide"
|
||||
page_title=profile.page_title, page_icon=profile.page_icon, layout="wide"
|
||||
)
|
||||
_apply_hmm_theme()
|
||||
_apply_console_theme(profile)
|
||||
_restore_portal_remembered_session()
|
||||
if not st.session_state.get(PORTAL_AUTHENTICATED_KEY, False):
|
||||
_render_portal_login()
|
||||
_render_portal_login(profile)
|
||||
return
|
||||
try:
|
||||
questions = load_demo_scenarios(DEMO_SCENARIOS_FILE)
|
||||
except ScenarioConfigError as exc:
|
||||
st.error(str(exc))
|
||||
return
|
||||
questions = tuple(
|
||||
question
|
||||
for question in COMMON_DEMO_QUESTIONS
|
||||
if str(question.question_id).strip().upper() != "S5"
|
||||
)
|
||||
scenario_key = "poc4_mcp_discovery_scenario"
|
||||
question_text_key = "poc4_mcp_discovery_question_text"
|
||||
loaded_scenario_key = "poc4_mcp_discovery_loaded_scenario_id"
|
||||
@@ -6938,9 +6947,10 @@ def main() -> None:
|
||||
query_progress_notice_key = "poc4_query_progress_notice"
|
||||
mcp_cache_generation_key = "poc4_mcp_tools_cache_generation"
|
||||
selected_scenario_state = st.session_state.get(scenario_key)
|
||||
if (
|
||||
str(getattr(selected_scenario_state, "question_id", "")).strip().upper()
|
||||
== "S5"
|
||||
scenario_ids = {question.question_id for question in questions}
|
||||
if str(getattr(selected_scenario_state, "question_id", "")).strip().upper() not in (
|
||||
"",
|
||||
*scenario_ids,
|
||||
):
|
||||
st.session_state.pop(scenario_key, None)
|
||||
st.session_state.pop(loaded_scenario_key, None)
|
||||
@@ -6987,7 +6997,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_hmm_header()
|
||||
_render_app_header(profile)
|
||||
with st.sidebar:
|
||||
st.caption(
|
||||
f"포털 사용자 · {st.session_state.get(PORTAL_AUTH_USER_KEY, '')}"
|
||||
@@ -7233,6 +7243,7 @@ def main() -> None:
|
||||
except (OSError, UnicodeError, ValueError):
|
||||
st.warning("MCP 설정 JSON을 읽지 못했습니다.")
|
||||
st.caption(f"config: {MCP_SERVERS_FILE}")
|
||||
st.caption(f"scenario config: {DEMO_SCENARIOS_FILE}")
|
||||
st.caption(f"token presets: {VPD_TOKEN_PRESETS_FILE}")
|
||||
st.caption(f"selected LLM model: {selected_query_model_profile}")
|
||||
st.caption(f"default model: {default_query_model_profile}")
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
"""Cross-browser light theme primitives shared by the PoC_4 Streamlit UIs.
|
||||
|
||||
This module is presentation-only. It does not import or call runtime adapters,
|
||||
MCP clients, databases, retrieval code, or model providers.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
POC4_LIGHT_THEME_CSS = """
|
||||
<style id="poc4-cross-browser-light-theme">
|
||||
:root,
|
||||
html,
|
||||
body,
|
||||
#root,
|
||||
.stApp,
|
||||
[data-testid="stAppViewContainer"],
|
||||
[data-testid="stMain"] {
|
||||
color-scheme: light !important;
|
||||
background-color: #f7f8fa !important;
|
||||
color: #172033 !important;
|
||||
}
|
||||
|
||||
/* Keep native form controls independent of the browser/OS dark-mode palette. */
|
||||
input,
|
||||
textarea,
|
||||
select,
|
||||
[data-baseweb="input"] input,
|
||||
[data-baseweb="textarea"] textarea {
|
||||
color-scheme: light !important;
|
||||
background-color: #ffffff !important;
|
||||
color: #172033 !important;
|
||||
-webkit-text-fill-color: #172033 !important;
|
||||
caret-color: #172033 !important;
|
||||
border-color: #aab3c2 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder,
|
||||
[data-baseweb="input"] input::placeholder,
|
||||
[data-baseweb="textarea"] textarea::placeholder {
|
||||
color: #687386 !important;
|
||||
-webkit-text-fill-color: #687386 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
textarea:-webkit-autofill,
|
||||
textarea:-webkit-autofill:hover,
|
||||
textarea:-webkit-autofill:focus {
|
||||
-webkit-text-fill-color: #172033 !important;
|
||||
-webkit-box-shadow: 0 0 0 1000px #ffffff inset !important;
|
||||
caret-color: #172033 !important;
|
||||
}
|
||||
|
||||
select,
|
||||
select option,
|
||||
option {
|
||||
background-color: #ffffff !important;
|
||||
color: #172033 !important;
|
||||
-webkit-text-fill-color: #172033 !important;
|
||||
}
|
||||
|
||||
/* Streamlit selectbox is rendered by BaseWeb; its menu is portaled to body. */
|
||||
[data-baseweb="select"],
|
||||
[data-baseweb="select"] > div {
|
||||
color-scheme: light !important;
|
||||
background-color: #ffffff !important;
|
||||
color: #172033 !important;
|
||||
border-color: #aab3c2 !important;
|
||||
}
|
||||
|
||||
[data-baseweb="select"] input,
|
||||
[data-baseweb="select"] span,
|
||||
[data-baseweb="select"] [aria-selected] {
|
||||
color: #172033 !important;
|
||||
-webkit-text-fill-color: #172033 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
[data-baseweb="select"] input::placeholder,
|
||||
[data-baseweb="select"] [aria-placeholder="true"],
|
||||
[data-baseweb="select"] [data-baseweb="placeholder"] {
|
||||
color: #687386 !important;
|
||||
-webkit-text-fill-color: #687386 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
[data-baseweb="select"] svg {
|
||||
fill: #526075 !important;
|
||||
color: #526075 !important;
|
||||
}
|
||||
|
||||
[data-baseweb="popover"],
|
||||
[data-baseweb="menu"],
|
||||
[role="listbox"] {
|
||||
color-scheme: light !important;
|
||||
background-color: #ffffff !important;
|
||||
color: #172033 !important;
|
||||
border-color: #aab3c2 !important;
|
||||
}
|
||||
|
||||
[role="option"],
|
||||
[role="option"] *,
|
||||
[data-baseweb="menu"] li,
|
||||
[data-baseweb="menu"] li * {
|
||||
background-color: #ffffff !important;
|
||||
color: #172033 !important;
|
||||
-webkit-text-fill-color: #172033 !important;
|
||||
}
|
||||
|
||||
[role="option"]:hover,
|
||||
[role="option"][aria-selected="true"],
|
||||
[role="option"]:hover *,
|
||||
[role="option"][aria-selected="true"] * {
|
||||
background-color: #ffffff !important;
|
||||
color: #172033 !important;
|
||||
-webkit-text-fill-color: #172033 !important;
|
||||
}
|
||||
|
||||
input:disabled,
|
||||
textarea:disabled,
|
||||
select:disabled,
|
||||
[data-baseweb="select"][aria-disabled="true"],
|
||||
[data-baseweb="select"] > div[aria-disabled="true"],
|
||||
[data-baseweb="select"] [aria-disabled="true"] {
|
||||
background-color: #f1f3f6 !important;
|
||||
color: #5f6b7a !important;
|
||||
-webkit-text-fill-color: #5f6b7a !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
div[data-testid="stButton"] > button,
|
||||
button[data-testid="stBaseButton-primary"],
|
||||
button[kind="primary"] {
|
||||
color-scheme: light !important;
|
||||
background-color: #2256c7 !important;
|
||||
border-color: #2256c7 !important;
|
||||
color: #ffffff !important;
|
||||
-webkit-text-fill-color: #ffffff !important;
|
||||
}
|
||||
|
||||
div[data-testid="stButton"] > button *,
|
||||
button[data-testid="stBaseButton-primary"] *,
|
||||
button[kind="primary"] * {
|
||||
color: #ffffff !important;
|
||||
-webkit-text-fill-color: #ffffff !important;
|
||||
}
|
||||
|
||||
div[data-testid="stButton"] > button:hover,
|
||||
button[data-testid="stBaseButton-primary"]:hover,
|
||||
button[kind="primary"]:hover {
|
||||
background-color: #1746a2 !important;
|
||||
border-color: #1746a2 !important;
|
||||
}
|
||||
|
||||
div[data-testid="stButton"] > button:disabled,
|
||||
button[data-testid="stBaseButton-primary"]:disabled,
|
||||
button[kind="primary"]:disabled {
|
||||
background-color: #e4e8ee !important;
|
||||
border-color: #c3cad4 !important;
|
||||
color: #5f6b7a !important;
|
||||
-webkit-text-fill-color: #5f6b7a !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
div[data-testid="stButton"] > button:disabled *,
|
||||
button[data-testid="stBaseButton-primary"]:disabled *,
|
||||
button[kind="primary"]:disabled * {
|
||||
color: #5f6b7a !important;
|
||||
-webkit-text-fill-color: #5f6b7a !important;
|
||||
}
|
||||
|
||||
/* Outrank the legacy primary-label selector for non-WebKit engines too. */
|
||||
div[data-testid="stButton"]
|
||||
> button[data-testid="stBaseButton-primary"]:disabled
|
||||
[data-testid="stMarkdownContainer"],
|
||||
div[data-testid="stButton"]
|
||||
> button[data-testid="stBaseButton-primary"]:disabled
|
||||
[data-testid="stMarkdownContainer"] p,
|
||||
div[data-testid="stButton"]
|
||||
> button[data-testid="stBaseButton-primary"]:disabled
|
||||
[data-testid="stMarkdownContainer"] span {
|
||||
color: #5f6b7a !important;
|
||||
-webkit-text-fill-color: #5f6b7a !important;
|
||||
}
|
||||
|
||||
label,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
p,
|
||||
[data-testid="stMarkdownContainer"],
|
||||
[data-testid="stWidgetLabel"],
|
||||
[data-testid="stExpander"] summary,
|
||||
[data-testid="stExpander"] summary *,
|
||||
[data-testid="stExpanderDetails"],
|
||||
[data-testid="stExpanderDetails"] * {
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
[data-testid="stExpander"],
|
||||
[data-testid="stVerticalBlockBorderWrapper"] {
|
||||
color-scheme: light !important;
|
||||
background-color: #ffffff !important;
|
||||
color: #172033 !important;
|
||||
border-color: #dce1e8 !important;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
|
||||
def apply_poc4_light_theme(st: object, *, additional_css: str = "") -> None:
|
||||
"""Inject optional layout CSS followed by the authoritative light theme."""
|
||||
|
||||
st.markdown(
|
||||
additional_css + POC4_LIGHT_THEME_CSS,
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["POC4_LIGHT_THEME_CSS", "apply_poc4_light_theme"]
|
||||
Reference in New Issue
Block a user