ST_AI_ShowCase / app.py
UCS2014's picture
Update app.py
16bde8f verified
raw
history blame
11.3 kB
# -*- coding: utf-8 -*-
import base64, mimetypes
from html import escape
from pathlib import Path
import streamlit as st
# ========= PATHS =========
BASE_DIR = Path(__file__).parent
ASSETS = BASE_DIR / "assets"
# ========= META =========
st.set_page_config(page_title="ST_LOG SUITE — Apps", page_icon="🧭", layout="wide")
# ========= TEXT =========
SUITE_NAME = "ST_LOG SUITE"
SUITE_TAGLINE = "Generating AI-Based Well Logging Profiles While Drilling"
# ========= COLOR PALETTE =========
NAVY_900 = "#0B1220"
NAVY_700 = "#1F2937"
SLATE_600 = "#344054"
PAPER_TOP = "#FFFFFF"
PAPER_BOT = "#FBFCFE"
GOLD = "#EAB308"
# ========= SIZE & SPACING CONTROLS (EDIT THESE) =========
TOP_PADDING_PX = 50 # top padding of the whole page
STRIP_GAP_PX = 10 # space between pill and tagline
STRIP_PILL_PAD_V_PX = 8
STRIP_PILL_PAD_H_PX = 14
STRIP_PILL_FONT_PX = 16
TAGLINE_FONT_PX = 15
STRIP_BELOW_GAP_PX = 30 # space between the strip row and the hero logo
HERO_LOGO_WIDTH_PX = 400 # width of the hero logo
HERO_MARGIN_BOTTOM_PX= 30 # space under the hero logo
GRID_GAP_PX = 50 # space between cards
CARD_WIDTH_PX = 340 # card width
CARD_RADIUS_PX = 22
CARD_BORDER_PX = 2
CARD_PAD_V_PX = 24
CARD_PAD_H_PX = 20
ICON_DIAM_PX = 118 # outer circle
ICON_IMG_PX = 106 # image inside the circle
TITLE_FONT_PX = 25 # app title size
BLURB_FONT_PX = 16
BUTTON_FONT_PX = 16
BUTTON_PAD_V_PX = 12
BUTTON_PAD_H_PX = 20
BUTTON_RADIUS_PX = 14
# ========= BEHAVIOR / OPTIONS =========
SHOW_CARD_CHIP = False # show small suite chip inside each card?
USE_TINTED_CARD_BG = True # very light background tint per card (3–5%)
# ========= ASSETS =========
HERO_LOGO = ASSETS / "AI_Suite_Log_logo.png"
ICON_GR = ASSETS / "GR_logo.png"
ICON_TS = ASSETS / "Ts_logo.png"
ICON_TC = ASSETS / "Tc_logo.png"
APPS = [
{
"title": "ST_Log_GR",
"url": "https://smart-thinking-gr.hf.space/",
"blurb": "Real-time gamma-ray log prediction.",
"icon": ICON_GR,
"tint": "linear-gradient(180deg, rgba(14,116,144,0.05) 0%, rgba(14,116,144,0.02) 100%)",
},
{
"title": "ST_Log_Sonic (Ts)",
"url": "https://smart-thinking-sonic-ts.hf.space",
"blurb": "Predict shear slowness (DtS) in real time.",
"icon": ICON_TS,
"tint": "linear-gradient(180deg, rgba(2,132,199,0.05) 0%, rgba(2,132,199,0.02) 100%)",
},
{
"title": "ST_Log_Sonic (Tc)",
"url": "https://smart-thinking-sonic-tc.hf.space",
"blurb": "Predict compressional slowness (DtC) in real time.",
"icon": ICON_TC,
"tint": "linear-gradient(180deg, rgba(99,102,241,0.05) 0%, rgba(99,102,241,0.02) 100%)",
},
]
# ========= HELPERS =========
def data_uri(path: Path) -> str | None:
if not path or not path.exists(): return None
mime, _ = mimetypes.guess_type(path.name)
if not mime: mime = "image/png"
b64 = base64.b64encode(path.read_bytes()).decode("utf-8")
return f"data:{mime};base64,{b64}"
def img_tag(path: Path, alt: str, cls: str = "", style: str = "") -> str:
uri = data_uri(path)
if not uri: return ""
cls_attr = f' class="{cls}"' if cls else ""
style_attr = f' style="{style}"' if style else ""
return f'<img{cls_attr}{style_attr} src="{uri}" alt="{escape(alt)}" />'
# ========= CSS =========
st.markdown(f"""
<style>
:root {{
/* sizes */
--top-pad: {TOP_PADDING_PX}px;
--strip-gap: {STRIP_GAP_PX}px;
--strip-pill-pv: {STRIP_PILL_PAD_V_PX}px;
--strip-pill-ph: {STRIP_PILL_PAD_H_PX}px;
--strip-pill-fs: {STRIP_PILL_FONT_PX}px;
--tagline-fs: {TAGLINE_FONT_PX}px;
--strip-row-mb: {STRIP_BELOW_GAP_PX}px;
--hero-w: {HERO_LOGO_WIDTH_PX}px;
--hero-mb: {HERO_MARGIN_BOTTOM_PX}px;
--grid-gap: {GRID_GAP_PX}px;
--card-w: {CARD_WIDTH_PX}px;
--card-r: {CARD_RADIUS_PX}px;
--card-bw: {CARD_BORDER_PX}px;
--card-pv: {CARD_PAD_V_PX}px;
--card-ph: {CARD_PAD_H_PX}px;
--icon-d: {ICON_DIAM_PX}px;
--icon-img: {ICON_IMG_PX}px;
--title-fs: {TITLE_FONT_PX}px;
--blurb-fs: {BLURB_FONT_PX}px;
--btn-fs: {BUTTON_FONT_PX}px;
--btn-pv: {BUTTON_PAD_V_PX}px;
--btn-ph: {BUTTON_PAD_H_PX}px;
--btn-r: {BUTTON_RADIUS_PX}px;
/* colors */
--navy1: {NAVY_900};
--navy2: {NAVY_700};
--gold: {GOLD};
--slate6:{SLATE_600};
/* outlines / 3D */
--card-stroke: {NAVY_900};
--card-stroke-hover: #1E293B;
}}
html, body, [data-testid="stAppViewContainer"] {{ height: 100%; }}
[data-testid="stAppViewContainer"] > .main {{
padding-top: 0 !important; padding-bottom: 0 !important;
}}
.block-container {{
max-width: 1120px;
min-height: 100vh;
display: flex; flex-direction: column;
gap: 14px;
padding: var(--top-pad) 0 28px !important;
background:
radial-gradient(980px 460px at 50% -140px,
rgba(2,12,30,0.06) 0%,
rgba(2,12,30,0.04) 28%,
rgba(255,255,255,0.98) 60%,
rgba(255,255,255,1) 100%);
}}
/* ===== TOP-LEFT STRIP ROW ===== */
.suite-row {{
display:flex; align-items:center; gap: var(--strip-gap);
justify-content:flex-start; flex-wrap: wrap;
margin: 0 0 var(--strip-row-mb) 0; /* was 0 0 8px 0 */
}}
.suite-pill {{
background: linear-gradient(90deg, var(--navy1) 0%, var(--navy2) 100%);
color: var(--gold);
padding: var(--strip-pill-pv) var(--strip-pill-ph);
border-radius: 999px;
font-weight: 800; letter-spacing: .25px;
font-size: var(--strip-pill-fs);
box-shadow: 0 6px 14px rgba(2,12,30,.18);
white-space: nowrap;
}}
.suite-tagline {{
color: var(--slate6); font-weight: 600; opacity: .95;
font-size: var(--tagline-fs);
white-space: nowrap;
}}
/* HERO LOGO (below the strip) */
.hero {{ text-align:center; margin: 0 0 var(--hero-mb); }}
.hero img {{
width: var(--hero-w); max-width: 92vw; height: auto;
display:block; margin: 0 auto var(--hero-mb);
filter: drop-shadow(0 6px 16px rgba(0,0,0,.10));
}}
/* GRID */
.grid {{
display: grid;
grid-template-columns: repeat(3, var(--card-w));
gap: var(--grid-gap);
justify-content: center;
align-items: stretch;
margin-top: 10px;
}}
@media (max-width: 1120px) {{
.grid {{ grid-template-columns: repeat(2, var(--card-w)); gap: calc(var(--grid-gap) - 12px); }}
}}
@media (max-width: 720px) {{
.grid {{ grid-template-columns: 1fr; }}
}}
/* CARD — dark outline + premium 3D look */
.card {{
position: relative;
width: var(--card-w);
border-radius: var(--card-r);
padding: var(--card-pv) var(--card-ph);
background: var(--card-bg, linear-gradient(180deg, {PAPER_TOP} 0%, {PAPER_BOT} 100%));
border: var(--card-bw) solid var(--card-stroke);
box-shadow:
0 14px 32px rgba(2,20,35,.12),
0 1px 0 rgba(255,255,255,0.90) inset,
0 -1px 10px rgba(255,255,255,0.40) inset;
transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, filter .18s ease;
text-align:center; display:flex; flex-direction:column; gap:16px;
align-items: center;
}}
.card:hover {{
transform: translateY(-6px) scale(1.01);
border-color: var(--card-stroke-hover);
box-shadow:
0 22px 56px rgba(2,20,35,.18),
0 1px 0 rgba(255,255,255,0.92) inset,
0 -1px 12px rgba(255,255,255,0.45) inset;
filter: saturate(1.03);
}}
.suite-chip {{
position: absolute; top: 12px; right: 12px;
background: linear-gradient(90deg, var(--navy1) 0%, var(--navy2) 100%);
color: var(--gold); padding: 6px 10px; border-radius: 999px;
font-weight: 700; font-size: 13px; letter-spacing: .2px;
box-shadow: 0 6px 14px rgba(2,12,30,.18);
}}
.icon-wrap {{
width: var(--icon-d); height: var(--icon-d); border-radius: 9999px;
background: #F1F5F9;
display:grid; place-items:center;
border: 1px solid rgba(12,18,32,0.10);
box-shadow: inset 0 1px 0 rgba(255,255,255,.95), 0 10px 22px rgba(2,20,35,.07);
}}
.icon-wrap img {{
width: var(--icon-img); height: var(--icon-img); border-radius:9999px; display:block;
}}
.card h3 {{
margin: 0; font-size: var(--title-fs); font-weight: 900; color:#0b1220;
letter-spacing: .15px;
}}
.card p {{
color:#566275; min-height: 40px; margin: 0 10px; font-size: var(--blurb-fs);
}}
.btn {{
display:inline-block; padding: var(--btn-pv) var(--btn-ph); border-radius: var(--btn-r);
border: 1px solid rgba(11,18,32,.45);
background: linear-gradient(180deg, var(--navy1) 0%, var(--navy2) 100%);
font-weight: 800; letter-spacing:.3px; font-size: var(--btn-fs);
margin: 6px auto 0;
box-shadow: 0 12px 28px rgba(11,18,32,.28), inset 0 1px 0 rgba(255,255,255,.10);
}}
a.btn, a.btn:link, a.btn:visited, a.btn:hover, a.btn:active, a.btn:focus {{
color: #ffffff !important; text-decoration: none !important;
}}
.btn:hover {{
filter: brightness(1.03) saturate(1.04);
transform: translateY(-1px);
box-shadow: 0 16px 38px rgba(11,18,32,.36);
}}
.btn:focus {{
outline: none;
box-shadow: 0 0 0 4px rgba(30,41,59,.28), 0 16px 38px rgba(11,18,32,.36);
}}
.footer {{ text-align:center; color:#3f4a5a; font-size:0.96em; margin-top: 26px; }}
.footer hr {{ margin: 12px 0; border-color: rgba(0,0,0,.08); }}
</style>
""", unsafe_allow_html=True)
# ===== TOP-LEFT STRIP (then hero below) =====
st.markdown(
f"""
<div class="suite-row">
<span class="suite-pill">{escape(SUITE_NAME)}</span>
<span class="suite-tagline">{escape(SUITE_TAGLINE)}</span>
</div>
""",
unsafe_allow_html=True,
)
# ===== HERO (logo below the strip) =====
hero_html = img_tag(HERO_LOGO, "ST_LOG SUITE")
st.markdown(f"<div class='hero'>{hero_html}</div>", unsafe_allow_html=True)
# ===== CARDS =====
def app_card(app: dict) -> str:
tint = app.get("tint") if USE_TINTED_CARD_BG else f"linear-gradient(180deg, {PAPER_TOP} 0%, {PAPER_BOT} 100%)"
icon_html = img_tag(app.get("icon"), "icon",
style=f"width:{ICON_IMG_PX}px;height:{ICON_IMG_PX}px;border-radius:9999px;") \
if app.get("icon") and app["icon"].exists() else ""
target = "_self"
chip = f"<div class='suite-chip'>{escape(SUITE_NAME)}</div>" if SHOW_CARD_CHIP else ""
style_vars = f"--card-bg:{tint};"
return (
f"<div class='card' style='{style_vars}'>"
+ chip
+ f"<div class='icon-wrap'>{icon_html}</div>"
+ f"<h3>{escape(app['title'])}</h3>"
+ f"<p>{escape(app['blurb'])}</p>"
+ f"<a class='btn' href='{escape(app['url'])}' target='{target}' rel='noopener'>Run App</a>"
+ "</div>"
)
cards_html = "".join(app_card(a) for a in APPS)
st.markdown(f"<div class='grid'>{cards_html}</div>", unsafe_allow_html=True)
# ===== FOOTER =====
st.markdown(
"""
<hr>
<div class='footer'>
© 2025 Smart Thinking AI-Solutions Team. All rights reserved.<br>
Website: <a href="https://smartthinking.com.sa" target="_blank" rel="noopener noreferrer">smartthinking.com.sa</a>
</div>
""",
unsafe_allow_html=True,
)