Update pages/5_ai_coach.py
Browse files- pages/5_ai_coach.py +282 -306
pages/5_ai_coach.py
CHANGED
|
@@ -16,115 +16,110 @@ data = st.session_state.user_data
|
|
| 16 |
|
| 17 |
st.markdown("""
|
| 18 |
<style>
|
| 19 |
-
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Syne:wght@700;800&family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600&display=swap');
|
| 20 |
|
| 21 |
-
/* ββ BACKGROUND ββ */
|
| 22 |
[data-testid="stAppViewContainer"]::before{
|
| 23 |
content:'';position:fixed;inset:0;z-index:0;
|
| 24 |
-
background:url('https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?fm=jpg&w=
|
| 25 |
-
filter:blur(
|
| 26 |
[data-testid="stAppViewContainer"]{
|
| 27 |
-
background:
|
|
|
|
|
|
|
| 28 |
position:relative;}
|
| 29 |
[data-testid="stAppViewContainer"]>section>div.block-container{
|
| 30 |
-
position:relative;z-index:2;max-width:
|
| 31 |
-
margin:0 auto!important;padding:0 20px
|
| 32 |
|
| 33 |
#MainMenu,footer,header,[data-testid="stToolbar"],[data-testid="stDecoration"],
|
| 34 |
[data-testid="stSidebarNav"],[data-testid="collapsedControl"],
|
| 35 |
section[data-testid="stSidebar"],button[kind="header"]{display:none!important;}
|
| 36 |
-
html,body,.stApp{background:#
|
| 37 |
|
| 38 |
-
/* ββ NAV BUTTONS ββ */
|
| 39 |
div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button{
|
| 40 |
-
background:rgba(
|
| 41 |
-
color:rgba(255,255,255,0.
|
| 42 |
-
font-size:0.
|
| 43 |
-
min-height:
|
| 44 |
div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button:hover{
|
| 45 |
-
background:rgba(229,9,20,0.
|
| 46 |
div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]:last-child>button{
|
| 47 |
-
background:rgba(229,9,20,0.
|
| 48 |
|
| 49 |
-
/* ββ MAIN SEND BUTTON ββ */
|
| 50 |
.stButton>button{
|
| 51 |
-
background:linear-gradient(135deg,#E50914,#
|
| 52 |
border:none!important;color:#fff!important;border-radius:12px!important;
|
| 53 |
font-family:'DM Sans',sans-serif!important;font-size:1rem!important;font-weight:700!important;
|
| 54 |
-
box-shadow:0 4px 20px rgba(229,9,20,0.
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
/* ββ FORM & INPUT ββ */
|
| 58 |
[data-testid="stForm"]{background:transparent!important;border:none!important;}
|
| 59 |
[data-testid="stFormSubmitButton"] button,.stFormSubmitButton>button{
|
| 60 |
-
background:linear-gradient(135deg,#E50914,#
|
| 61 |
-
color:#fff!important;border-radius:
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
input,
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
font-size:1rem!important;
|
| 74 |
-
box-shadow:inset 0 0 0 9999px #0e0c1a!important;
|
| 75 |
-
-webkit-box-shadow:inset 0 0 0 9999px #0e0c1a!important;
|
| 76 |
-
caret-color:#E50914!important;transition:border-color 0.2s!important;}
|
| 77 |
input:focus,[data-baseweb="input"] input:focus{
|
| 78 |
-
border-color:rgba(229,9,20,0.
|
| 79 |
-
box-shadow:inset 0 0 0 9999px #
|
| 80 |
-
-webkit-box-shadow:inset 0 0 0 9999px #
|
| 81 |
-
[data-baseweb="input"]{
|
| 82 |
-
|
| 83 |
-
border:1.5px solid rgba(229,9,20,0.35)!important;border-radius:14px!important;}
|
| 84 |
input:-webkit-autofill,input:-webkit-autofill:focus{
|
| 85 |
-
-webkit-box-shadow:0 0 0 9999px #
|
| 86 |
-
-webkit-text-fill-color:#fff!important;}
|
| 87 |
|
| 88 |
-
/* ββ QUICK BUTTONS ββ */
|
| 89 |
.quick-btn .stButton>button{
|
| 90 |
-
background:rgba(255,255,255,0.
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
box-shadow:none!important;letter-spacing:0!important;
|
| 97 |
-
font-style:italic!important;backdrop-filter:blur(12px)!important;
|
| 98 |
-
animation:none!important;transition:all 0.15s!important;}
|
| 99 |
.quick-btn .stButton>button:hover{
|
| 100 |
-
background:rgba(229,9,20,0.
|
| 101 |
-
color:#fff!important;transform:translateY(-
|
|
|
|
| 102 |
|
| 103 |
-
/* ββ GLOBAL TEXT ββ */
|
| 104 |
html,body,.stApp,.stMarkdown,p,div,span,label{text-shadow:0 1px 4px rgba(0,0,0,0.95)!important;}
|
| 105 |
[data-testid="stWidgetLabel"],[data-testid="stWidgetLabel"] p{color:#fff!important;font-weight:600!important;}
|
| 106 |
.stMarkdown p,.stMarkdown li{color:#fff!important;line-height:1.75!important;}
|
| 107 |
|
| 108 |
-
|
| 109 |
-
@keyframes
|
| 110 |
-
@keyframes
|
| 111 |
-
@keyframes
|
| 112 |
-
@keyframes
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
</style>
|
| 114 |
""", unsafe_allow_html=True)
|
| 115 |
|
| 116 |
apply_bg(
|
| 117 |
-
"https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?fm=jpg&w=
|
| 118 |
-
overlay="rgba(
|
| 119 |
)
|
| 120 |
|
| 121 |
# ββ NAV βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 122 |
-
st.markdown("<div style='padding:6px 0;margin-bottom:
|
| 123 |
_n = st.columns([1.6,1,1,1,1,1,1,1.2])
|
| 124 |
with _n[0]:
|
| 125 |
st.markdown(
|
| 126 |
"<div style='font-family:Bebas Neue,sans-serif;font-size:1.4rem;letter-spacing:5px;"
|
| 127 |
-
"color:#E50914;text-shadow:0 0
|
| 128 |
"⚡ FITPLAN PRO</div>", unsafe_allow_html=True)
|
| 129 |
nav_pages = [("Home","pages/2_Dashboard.py","ac_db"),("Workout","pages/3_Workout_Plan.py","ac_wp"),
|
| 130 |
("Diet","pages/4_Diet_Plan.py","ac_dp"),("AI Coach","pages/5_ai_coach.py","ac_ai"),
|
|
@@ -145,73 +140,82 @@ with _n[7]:
|
|
| 145 |
st.switch_page("app.py")
|
| 146 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 147 |
|
| 148 |
-
# ββ
|
| 149 |
-
sdays
|
| 150 |
-
diet_lbl = "Vegetarian" if st.session_state.get("dietary_type")=="veg"
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
st.markdown(
|
| 154 |
-
"<div style='background:linear-gradient(135deg,rgba(229,9,20,0.12),rgba(10,6,20,0.80));"
|
| 155 |
-
"border:1.5px solid rgba(229,9,20,0.30);border-radius:20px;padding:24px 32px;"
|
| 156 |
-
"margin-bottom:20px;position:relative;overflow:hidden'>"
|
| 157 |
-
"<div style='position:absolute;top:0;left:0;right:0;height:2px;"
|
| 158 |
-
"background:linear-gradient(90deg,transparent,#E50914,rgba(229,9,20,0.30),transparent)'></div>"
|
| 159 |
-
"<div style='position:absolute;top:-40px;right:-20px;font-size:8rem;opacity:0.04;line-height:1'>π€</div>"
|
| 160 |
-
"<div style='display:flex;align-items:center;gap:16px'>"
|
| 161 |
-
# Avatar with pulse
|
| 162 |
-
"<div style='position:relative;flex-shrink:0'>"
|
| 163 |
-
"<div style='width:56px;height:56px;border-radius:50%;"
|
| 164 |
-
"background:linear-gradient(135deg,#E50914,#7c000a);"
|
| 165 |
-
"display:flex;align-items:center;justify-content:center;font-size:1.6rem;"
|
| 166 |
-
"box-shadow:0 0 0 3px rgba(229,9,20,0.25),0 0 20px rgba(229,9,20,0.40)'>"
|
| 167 |
-
"π€</div>"
|
| 168 |
-
"<div style='position:absolute;inset:-4px;border-radius:50%;"
|
| 169 |
-
"border:2px solid rgba(229,9,20,0.40);animation:pulse-ring 2s ease-out infinite'></div>"
|
| 170 |
-
"</div>"
|
| 171 |
-
"<div>"
|
| 172 |
-
"<div style='font-family:Syne,sans-serif;font-size:1.5rem;font-weight:800;"
|
| 173 |
-
"color:#fff;letter-spacing:1px;line-height:1;margin-bottom:5px'>"
|
| 174 |
-
"ATLAS β Your AI Fitness Coach</div>"
|
| 175 |
-
"<div style='font-size:0.85rem;color:rgba(255,255,255,0.55);line-height:1.5'>"
|
| 176 |
-
"Trained on your profile Β· "
|
| 177 |
-
+ str(len(sdays)) + "-day plan Β· "
|
| 178 |
-
+ diet_lbl + " diet Β· "
|
| 179 |
-
+ data.get("goal","Fitness") + "</div>"
|
| 180 |
-
"</div>"
|
| 181 |
-
"<div style='margin-left:auto;display:flex;align-items:center;gap:6px'>"
|
| 182 |
-
"<div style='width:8px;height:8px;border-radius:50%;background:#22c55e;"
|
| 183 |
-
"box-shadow:0 0 8px #22c55e;animation:blink 2s ease-in-out infinite'></div>"
|
| 184 |
-
"<span style='font-size:0.75rem;color:rgba(34,197,94,0.90);font-weight:600;letter-spacing:1px'>ONLINE</span>"
|
| 185 |
-
"</div>"
|
| 186 |
-
"</div></div>",
|
| 187 |
-
unsafe_allow_html=True
|
| 188 |
-
)
|
| 189 |
-
|
| 190 |
-
# ββ SYSTEM PROMPT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 191 |
-
plan_ctx = ""
|
| 192 |
-
if sdays:
|
| 193 |
-
plan_ctx = str(len(sdays)) + "-day fitness plan. Diet: " + diet_lbl + ". "
|
| 194 |
|
| 195 |
system_prompt = (
|
| 196 |
-
"You are ATLAS, a professional AI fitness coach inside FitPlan Pro.
|
| 197 |
-
"
|
| 198 |
-
"User
|
| 199 |
-
", Age=" + str(data.get("age","?")) +
|
| 200 |
", Weight=" + str(data.get("weight","?")) + "kg"
|
| 201 |
", Height=" + str(data.get("height","?")) + "cm"
|
| 202 |
", Goal=" + data.get("goal","Fitness") +
|
| 203 |
", Level=" + data.get("level","Beginner") +
|
| 204 |
-
", Equipment=" + (", ".join(data.get("equipment",[])) or "
|
| 205 |
-
|
| 206 |
-
"
|
| 207 |
-
"
|
| 208 |
-
"
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
"
|
| 214 |
-
"8.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
)
|
| 216 |
|
| 217 |
# ββ CHAT HISTORY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -224,154 +228,169 @@ if "chat_messages" not in st.session_state:
|
|
| 224 |
st.session_state.chat_messages = []
|
| 225 |
|
| 226 |
# ββ QUICK QUESTIONS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 227 |
-
quick_qs = [
|
| 228 |
-
"πͺ Best post-workout meal?",
|
| 229 |
-
"π₯ How to lose fat fast?",
|
| 230 |
-
"π΄ How much sleep do I need?",
|
| 231 |
-
"π₯© Daily protein target?",
|
| 232 |
-
"β‘ How to boost energy?",
|
| 233 |
-
"π§ Recovery tips for sore muscles?",
|
| 234 |
-
]
|
| 235 |
-
|
| 236 |
st.markdown(
|
| 237 |
-
"<div style='font-size:0.
|
| 238 |
-
"color:rgba(229,9,20,0.
|
| 239 |
-
"<span style='width:
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
)
|
|
|
|
| 243 |
qc = st.columns(3)
|
| 244 |
-
for i,
|
| 245 |
-
with qc[i
|
| 246 |
st.markdown("<div class='quick-btn'>", unsafe_allow_html=True)
|
| 247 |
-
if st.button(q, key="qq_
|
| 248 |
-
|
| 249 |
-
st.session_state.chat_messages.append({"role":"user","content":clean_q})
|
| 250 |
st.rerun()
|
| 251 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 252 |
|
| 253 |
st.markdown("<div style='height:8px'></div>", unsafe_allow_html=True)
|
| 254 |
|
| 255 |
-
# ββ CHAT
|
| 256 |
-
st.markdown("<div style='height:4px'></div>", unsafe_allow_html=True)
|
| 257 |
-
|
| 258 |
if not st.session_state.chat_messages:
|
| 259 |
st.markdown(
|
| 260 |
-
"<div style='text-align:center;padding:
|
| 261 |
-
"<div style='font-size:
|
| 262 |
-
"
|
| 263 |
-
"
|
| 264 |
-
"
|
| 265 |
-
"
|
| 266 |
-
"
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
else:
|
| 270 |
for mi, msg in enumerate(st.session_state.chat_messages):
|
|
|
|
| 271 |
if msg["role"] == "user":
|
| 272 |
st.markdown(
|
| 273 |
-
"<div style='display:flex;justify-content:flex-end;margin:
|
| 274 |
-
"animation:
|
| 275 |
-
"<div style='max-width:
|
| 276 |
-
"
|
| 277 |
-
"
|
| 278 |
-
"<div style='
|
| 279 |
-
"
|
| 280 |
-
"
|
| 281 |
-
"</div>"
|
| 282 |
-
"<div
|
| 283 |
-
"
|
| 284 |
-
"
|
| 285 |
-
"
|
| 286 |
-
|
| 287 |
-
|
|
|
|
| 288 |
else:
|
| 289 |
-
# Format AI message
|
| 290 |
_lines = []
|
| 291 |
for _ln in msg["content"].splitlines():
|
| 292 |
_s = _ln.strip()
|
| 293 |
if _s.startswith("- ") or _s.startswith("β’ "):
|
| 294 |
_lines.append(
|
| 295 |
-
"<div style='display:flex;gap:
|
| 296 |
-
"<span style='color:#E50914;font-size:0.
|
| 297 |
-
"
|
|
|
|
| 298 |
elif _s:
|
| 299 |
_lines.append(
|
| 300 |
-
"<div style='font-size:0.95rem;color:rgba(255,255,255,0.
|
| 301 |
-
"line-height:1.
|
| 302 |
else:
|
| 303 |
-
_lines.append("<div style='height:
|
| 304 |
|
| 305 |
st.markdown(
|
| 306 |
-
"<div style='display:flex;margin:
|
| 307 |
-
"
|
| 308 |
-
"
|
| 309 |
-
"
|
| 310 |
-
"
|
| 311 |
-
"
|
| 312 |
-
"
|
| 313 |
-
"
|
| 314 |
-
"
|
| 315 |
-
"
|
| 316 |
-
"
|
| 317 |
-
"
|
| 318 |
-
"
|
|
|
|
| 319 |
+ "".join(_lines) +
|
| 320 |
-
"</div></div>",
|
| 321 |
-
unsafe_allow_html=True
|
| 322 |
-
)
|
| 323 |
|
| 324 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
# ββ PROCESS AI REPLY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 327 |
if st.session_state.chat_messages and st.session_state.chat_messages[-1]["role"] == "user":
|
| 328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
try:
|
| 330 |
-
from
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
# Clean unwanted prefixes
|
| 339 |
-
for _pfx in ["Assistant:","ATLAS:","AI Coach:","Coach:"]:
|
| 340 |
-
if reply.startswith(_pfx): reply = reply[len(_pfx):].strip()
|
| 341 |
-
import re as _re
|
| 342 |
-
# Remove any JSON-like blobs
|
| 343 |
-
_clean = [ln for ln in reply.splitlines()
|
| 344 |
-
if not (ln.strip().startswith("{") or ln.strip().startswith("[")
|
| 345 |
-
or (ln.strip().startswith('"') and ln.strip().endswith('"},'))) ]
|
| 346 |
-
reply = _re.sub(r"\{[^}]*\}", "", "\n".join(_clean))
|
| 347 |
-
reply = _re.sub(r"\[[^\]]*\]", "", reply)
|
| 348 |
-
reply = _re.sub(r"\n{3,}", "\n\n", reply).strip()
|
| 349 |
-
if not reply: reply = "Great question! Ask me again and I'll give you the full breakdown."
|
| 350 |
-
st.session_state.chat_messages.append({"role":"assistant","content":reply})
|
| 351 |
-
try:
|
| 352 |
-
from utils.db import save_chat_history
|
| 353 |
-
save_chat_history(uname, st.session_state.chat_messages[-30:])
|
| 354 |
-
except Exception: pass
|
| 355 |
-
st.rerun()
|
| 356 |
-
except Exception as e:
|
| 357 |
-
st.error("ATLAS error: " + str(e))
|
| 358 |
|
| 359 |
# ββ INPUT BAR βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 360 |
st.markdown(
|
| 361 |
-
"<div style='background:rgba(
|
| 362 |
-
"border
|
| 363 |
-
"
|
| 364 |
-
"
|
| 365 |
-
|
| 366 |
-
unsafe_allow_html=True
|
| 367 |
-
)
|
| 368 |
with st.form("chat_form", clear_on_submit=True):
|
| 369 |
-
inp_col, btn_col = st.columns([5,
|
| 370 |
with inp_col:
|
| 371 |
user_input = st.text_input(
|
| 372 |
-
"", placeholder="Ask ATLAS about workouts, nutrition, recovery...",
|
| 373 |
-
key="chat_input_field", label_visibility="collapsed"
|
| 374 |
-
)
|
| 375 |
with btn_col:
|
| 376 |
send = st.form_submit_button("Send β€", use_container_width=True)
|
| 377 |
if send and user_input.strip():
|
|
@@ -379,84 +398,41 @@ with st.form("chat_form", clear_on_submit=True):
|
|
| 379 |
st.rerun()
|
| 380 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 381 |
|
| 382 |
-
# ββ BOTTOM
|
| 383 |
-
st.markdown("<div style='height:
|
| 384 |
-
|
| 385 |
-
ba1, ba2, ba3 = st.columns([2, 2, 6])
|
| 386 |
with ba1:
|
| 387 |
if st.session_state.chat_messages:
|
| 388 |
if st.button("ποΈ Clear Chat", key="clear_chat", use_container_width=True):
|
| 389 |
st.session_state.chat_messages = []
|
| 390 |
st.rerun()
|
| 391 |
with ba2:
|
| 392 |
-
|
| 393 |
-
if
|
| 394 |
-
|
| 395 |
st.markdown(
|
| 396 |
-
f"<div style='background:rgba(229,9,20,0.
|
| 397 |
-
f"border-radius:10px;padding:8px 14px;text-align:center;font-size:0.
|
| 398 |
-
f"color:rgba(255,255,255,0.
|
| 399 |
-
|
| 400 |
-
unsafe_allow_html=True
|
| 401 |
-
)
|
| 402 |
|
| 403 |
-
# ββ
|
| 404 |
if not st.session_state.chat_messages:
|
| 405 |
-
st.markdown("<div style='height:
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
"What ATLAS Knows About You</div>",
|
| 411 |
-
unsafe_allow_html=True
|
| 412 |
-
)
|
| 413 |
-
caps = [
|
| 414 |
-
("ποΈ", "Your Workout Plan", str(len(sdays)) + "-day personalised plan"),
|
| 415 |
-
("π₯", "Your Diet", diet_lbl + " Β· " + data.get("goal","Fitness")),
|
| 416 |
-
("π", "Your Body Stats", str(data.get("weight","?")) + "kg Β· " + str(data.get("height","?")) + "cm Β· " + data.get("level","Beginner")),
|
| 417 |
-
("βοΈ", "Your Equipment", ", ".join(data.get("equipment",[])) if data.get("equipment") else "Bodyweight"),
|
| 418 |
-
]
|
| 419 |
cc = st.columns(4)
|
| 420 |
for i,(icon,title,sub) in enumerate(caps):
|
| 421 |
with cc[i]:
|
| 422 |
st.markdown(
|
| 423 |
-
f"<div style='background:rgba(10,8,20,0.
|
| 424 |
-
f"border-radius:14px;padding:14px
|
| 425 |
-
f"
|
| 426 |
-
f"<div style='font-size:
|
| 427 |
-
f"
|
| 428 |
-
f"
|
|
|
|
| 429 |
f"</div>",
|
| 430 |
-
unsafe_allow_html=True
|
| 431 |
-
)
|
| 432 |
-
|
| 433 |
-
st.markdown("<div style='height:14px'></div>", unsafe_allow_html=True)
|
| 434 |
-
st.markdown(
|
| 435 |
-
"<div style='font-size:0.70rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
|
| 436 |
-
"color:rgba(229,9,20,0.65);margin-bottom:12px;display:flex;align-items:center;gap:8px'>"
|
| 437 |
-
"<span style='width:14px;height:1.5px;background:#E50914;display:block'></span>"
|
| 438 |
-
"Topics ATLAS Can Help With</div>",
|
| 439 |
-
unsafe_allow_html=True
|
| 440 |
-
)
|
| 441 |
-
topics = [
|
| 442 |
-
("πͺ","Workout Form & Technique","Exercise breakdowns, form cues, safer alternatives"),
|
| 443 |
-
("π₯¦","Nutrition & Diet","Meal timing, macros, supplements, foods to eat"),
|
| 444 |
-
("π΄","Recovery & Sleep","Rest days, sleep quality, muscle soreness relief"),
|
| 445 |
-
("π₯","Fat Loss & Muscle Gain","Cutting, bulking, body recomposition strategies"),
|
| 446 |
-
("π§ ","Motivation & Mindset","Staying consistent, beating plateaus, mental game"),
|
| 447 |
-
("π","Progress & Goals","Tracking progress, adjusting your plan, setting targets"),
|
| 448 |
-
]
|
| 449 |
-
tc = st.columns(3)
|
| 450 |
-
for i,(icon,title,sub) in enumerate(topics):
|
| 451 |
-
with tc[i % 3]:
|
| 452 |
-
st.markdown(
|
| 453 |
-
f"<div style='background:rgba(10,8,20,0.65);border:1px solid rgba(255,255,255,0.07);"
|
| 454 |
-
f"border-radius:12px;padding:12px 14px;margin-bottom:10px;"
|
| 455 |
-
f"display:flex;align-items:flex-start;gap:10px'>"
|
| 456 |
-
f"<div style='font-size:1.3rem;flex-shrink:0'>{icon}</div>"
|
| 457 |
-
f"<div>"
|
| 458 |
-
f"<div style='font-size:0.82rem;font-weight:700;color:#fff;margin-bottom:3px'>{title}</div>"
|
| 459 |
-
f"<div style='font-size:0.73rem;color:rgba(255,255,255,0.42);line-height:1.4'>{sub}</div>"
|
| 460 |
-
f"</div></div>",
|
| 461 |
-
unsafe_allow_html=True
|
| 462 |
-
)
|
|
|
|
| 16 |
|
| 17 |
st.markdown("""
|
| 18 |
<style>
|
| 19 |
+
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Syne:wght@600;700;800&family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600&display=swap');
|
| 20 |
|
|
|
|
| 21 |
[data-testid="stAppViewContainer"]::before{
|
| 22 |
content:'';position:fixed;inset:0;z-index:0;
|
| 23 |
+
background:url('https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?fm=jpg&w=1920&q=80&fit=crop') center/cover no-repeat;
|
| 24 |
+
filter:blur(12px) brightness(0.15) saturate(0.35);transform:scale(1.10);}
|
| 25 |
[data-testid="stAppViewContainer"]{
|
| 26 |
+
background:radial-gradient(ellipse at 20% 50%,rgba(229,9,20,0.08) 0%,transparent 60%),
|
| 27 |
+
radial-gradient(ellipse at 80% 20%,rgba(99,60,220,0.06) 0%,transparent 50%),
|
| 28 |
+
linear-gradient(160deg,rgba(2,2,8,0.97) 0%,rgba(5,3,12,0.95) 100%)!important;
|
| 29 |
position:relative;}
|
| 30 |
[data-testid="stAppViewContainer"]>section>div.block-container{
|
| 31 |
+
position:relative;z-index:2;max-width:940px!important;
|
| 32 |
+
margin:0 auto!important;padding:0 20px 120px!important;}
|
| 33 |
|
| 34 |
#MainMenu,footer,header,[data-testid="stToolbar"],[data-testid="stDecoration"],
|
| 35 |
[data-testid="stSidebarNav"],[data-testid="collapsedControl"],
|
| 36 |
section[data-testid="stSidebar"],button[kind="header"]{display:none!important;}
|
| 37 |
+
html,body,.stApp{background:#020208!important;color:#fff!important;font-family:'DM Sans',sans-serif!important;}
|
| 38 |
|
|
|
|
| 39 |
div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button{
|
| 40 |
+
background:rgba(8,6,18,0.80)!important;border:1px solid rgba(229,9,20,0.28)!important;
|
| 41 |
+
color:rgba(255,255,255,0.70)!important;border-radius:8px!important;
|
| 42 |
+
font-size:0.80rem!important;font-weight:600!important;height:30px!important;
|
| 43 |
+
min-height:30px!important;white-space:nowrap!important;transition:all 0.16s!important;}
|
| 44 |
div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button:hover{
|
| 45 |
+
background:rgba(229,9,20,0.18)!important;border-color:rgba(229,9,20,0.60)!important;color:#fff!important;}
|
| 46 |
div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]:last-child>button{
|
| 47 |
+
background:rgba(229,9,20,0.22)!important;border-color:rgba(229,9,20,0.50)!important;}
|
| 48 |
|
|
|
|
| 49 |
.stButton>button{
|
| 50 |
+
background:linear-gradient(135deg,#E50914 0%,#b0000a 100%)!important;
|
| 51 |
border:none!important;color:#fff!important;border-radius:12px!important;
|
| 52 |
font-family:'DM Sans',sans-serif!important;font-size:1rem!important;font-weight:700!important;
|
| 53 |
+
box-shadow:0 4px 20px rgba(229,9,20,0.48),0 0 0 1px rgba(229,9,20,0.18)!important;
|
| 54 |
+
transition:all 0.22s cubic-bezier(0.34,1.56,0.64,1)!important;}
|
| 55 |
+
.stButton>button:hover{
|
| 56 |
+
transform:translateY(-3px) scale(1.03)!important;
|
| 57 |
+
box-shadow:0 10px 32px rgba(229,9,20,0.70),0 0 50px rgba(229,9,20,0.22)!important;}
|
| 58 |
|
|
|
|
| 59 |
[data-testid="stForm"]{background:transparent!important;border:none!important;}
|
| 60 |
[data-testid="stFormSubmitButton"] button,.stFormSubmitButton>button{
|
| 61 |
+
background:linear-gradient(135deg,#E50914,#8b0000)!important;border:none!important;
|
| 62 |
+
color:#fff!important;border-radius:14px!important;font-weight:800!important;
|
| 63 |
+
font-size:1rem!important;letter-spacing:1px!important;height:56px!important;
|
| 64 |
+
box-shadow:0 6px 24px rgba(229,9,20,0.55),0 0 50px rgba(229,9,20,0.12)!important;}
|
| 65 |
+
|
| 66 |
+
input,textarea,.stTextInput>div>div>input,
|
| 67 |
+
[data-baseweb="input"] input,[data-testid="stTextInput"] input{
|
| 68 |
+
background:#0a0814!important;background-color:#0a0814!important;
|
| 69 |
+
border:1.5px solid rgba(229,9,20,0.28)!important;color:#fff!important;
|
| 70 |
+
border-radius:14px!important;font-family:'DM Sans',sans-serif!important;font-size:1rem!important;
|
| 71 |
+
box-shadow:inset 0 0 0 9999px #0a0814!important;
|
| 72 |
+
-webkit-box-shadow:inset 0 0 0 9999px #0a0814!important;
|
| 73 |
+
caret-color:#E50914!important;transition:border-color 0.25s!important;}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
input:focus,[data-baseweb="input"] input:focus{
|
| 75 |
+
border-color:rgba(229,9,20,0.72)!important;
|
| 76 |
+
box-shadow:inset 0 0 0 9999px #0a0814,0 0 0 3px rgba(229,9,20,0.14)!important;
|
| 77 |
+
-webkit-box-shadow:inset 0 0 0 9999px #0a0814!important;outline:none!important;}
|
| 78 |
+
[data-baseweb="input"]{background:#0a0814!important;background-color:#0a0814!important;
|
| 79 |
+
border:1.5px solid rgba(229,9,20,0.28)!important;border-radius:14px!important;}
|
|
|
|
| 80 |
input:-webkit-autofill,input:-webkit-autofill:focus{
|
| 81 |
+
-webkit-box-shadow:0 0 0 9999px #0a0814 inset!important;-webkit-text-fill-color:#fff!important;}
|
|
|
|
| 82 |
|
|
|
|
| 83 |
.quick-btn .stButton>button{
|
| 84 |
+
background:rgba(255,255,255,0.03)!important;border:1px solid rgba(255,255,255,0.09)!important;
|
| 85 |
+
color:rgba(255,255,255,0.62)!important;font-size:0.80rem!important;font-weight:500!important;
|
| 86 |
+
text-transform:none!important;border-radius:22px!important;padding:6px 14px!important;
|
| 87 |
+
height:auto!important;min-height:auto!important;box-shadow:none!important;
|
| 88 |
+
letter-spacing:0!important;font-style:italic!important;animation:none!important;
|
| 89 |
+
transition:all 0.18s ease!important;}
|
|
|
|
|
|
|
|
|
|
| 90 |
.quick-btn .stButton>button:hover{
|
| 91 |
+
background:rgba(229,9,20,0.12)!important;border-color:rgba(229,9,20,0.42)!important;
|
| 92 |
+
color:#fff!important;transform:translateY(-2px)!important;
|
| 93 |
+
box-shadow:0 4px 14px rgba(229,9,20,0.18)!important;}
|
| 94 |
|
|
|
|
| 95 |
html,body,.stApp,.stMarkdown,p,div,span,label{text-shadow:0 1px 4px rgba(0,0,0,0.95)!important;}
|
| 96 |
[data-testid="stWidgetLabel"],[data-testid="stWidgetLabel"] p{color:#fff!important;font-weight:600!important;}
|
| 97 |
.stMarkdown p,.stMarkdown li{color:#fff!important;line-height:1.75!important;}
|
| 98 |
|
| 99 |
+
@keyframes fadeSlideUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
|
| 100 |
+
@keyframes fadeSlideRight{from{opacity:0;transform:translateX(-14px)}to{opacity:1;transform:translateX(0)}}
|
| 101 |
+
@keyframes fadeSlideLeft{from{opacity:0;transform:translateX(14px)}to{opacity:1;transform:translateX(0)}}
|
| 102 |
+
@keyframes pulseRing{0%{transform:scale(1);opacity:0.70}100%{transform:scale(1.80);opacity:0}}
|
| 103 |
+
@keyframes onlineBlink{0%,100%{opacity:1;box-shadow:0 0 8px #22c55e}50%{opacity:0.35;box-shadow:0 0 3px #22c55e}}
|
| 104 |
+
@keyframes typing{0%,100%{transform:translateY(0);opacity:0.90}50%{transform:translateY(-5px);opacity:0.40}}
|
| 105 |
+
@keyframes borderPulse{0%,100%{box-shadow:0 0 0 1px rgba(229,9,20,0.18),0 8px 32px rgba(0,0,0,0.60)}
|
| 106 |
+
50%{box-shadow:0 0 0 1px rgba(229,9,20,0.42),0 8px 40px rgba(229,9,20,0.10)}}
|
| 107 |
+
@keyframes scanLine{0%{top:-2px;opacity:0}10%{opacity:1}90%{opacity:1}100%{top:102%;opacity:0}}
|
| 108 |
</style>
|
| 109 |
""", unsafe_allow_html=True)
|
| 110 |
|
| 111 |
apply_bg(
|
| 112 |
+
"https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?fm=jpg&w=1920&q=80&fit=crop",
|
| 113 |
+
overlay="rgba(2,2,10,0.85)"
|
| 114 |
)
|
| 115 |
|
| 116 |
# ββ NAV βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 117 |
+
st.markdown("<div style='padding:6px 0;margin-bottom:12px'>", unsafe_allow_html=True)
|
| 118 |
_n = st.columns([1.6,1,1,1,1,1,1,1.2])
|
| 119 |
with _n[0]:
|
| 120 |
st.markdown(
|
| 121 |
"<div style='font-family:Bebas Neue,sans-serif;font-size:1.4rem;letter-spacing:5px;"
|
| 122 |
+
"color:#E50914;text-shadow:0 0 24px rgba(229,9,20,0.60);line-height:1;padding-top:4px'>"
|
| 123 |
"⚡ FITPLAN PRO</div>", unsafe_allow_html=True)
|
| 124 |
nav_pages = [("Home","pages/2_Dashboard.py","ac_db"),("Workout","pages/3_Workout_Plan.py","ac_wp"),
|
| 125 |
("Diet","pages/4_Diet_Plan.py","ac_dp"),("AI Coach","pages/5_ai_coach.py","ac_ai"),
|
|
|
|
| 140 |
st.switch_page("app.py")
|
| 141 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 142 |
|
| 143 |
+
# ββ CONTEXT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 144 |
+
sdays = st.session_state.get("structured_days", [])
|
| 145 |
+
diet_lbl = ("Vegetarian" if st.session_state.get("dietary_type")=="veg"
|
| 146 |
+
else ("Non-Veg" if st.session_state.get("dietary_type")=="nonveg" else "Flexible"))
|
| 147 |
+
plan_ctx = (str(len(sdays)) + "-day fitness plan. Diet: " + diet_lbl + ". ") if sdays else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
system_prompt = (
|
| 150 |
+
"You are ATLAS, a professional AI fitness coach inside FitPlan Pro. "
|
| 151 |
+
"Warm, motivating, direct. Speak like a real coach. "
|
| 152 |
+
"User: " + uname + ", Age=" + str(data.get("age","?")) +
|
|
|
|
| 153 |
", Weight=" + str(data.get("weight","?")) + "kg"
|
| 154 |
", Height=" + str(data.get("height","?")) + "cm"
|
| 155 |
", Goal=" + data.get("goal","Fitness") +
|
| 156 |
", Level=" + data.get("level","Beginner") +
|
| 157 |
+
", Equipment=" + (", ".join(data.get("equipment",[])) or "Bodyweight") + ". " + plan_ctx +
|
| 158 |
+
"RULES: Only answer fitness, gym, nutrition, body, health, lifestyle, motivation, sleep, recovery. "
|
| 159 |
+
"Redirect off-topic warmly. NO JSON/code blocks. Max 130 words. "
|
| 160 |
+
"Dash (-) for lists, max 4 items. Don't start with 'Assistant:' or 'ATLAS:'. "
|
| 161 |
+
"Reference earlier conversation. End with short motivational line when fitting."
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
# ββ HERO ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 165 |
+
st.markdown(
|
| 166 |
+
"<div style='position:relative;overflow:hidden;border-radius:22px;margin-bottom:16px;"
|
| 167 |
+
"background:linear-gradient(135deg,rgba(18,6,6,0.94) 0%,rgba(8,4,20,0.90) 60%,rgba(18,6,6,0.94) 100%);"
|
| 168 |
+
"border:1px solid rgba(229,9,20,0.22);animation:borderPulse 4s ease-in-out infinite;"
|
| 169 |
+
"padding:20px 26px'>"
|
| 170 |
+
# Scan line
|
| 171 |
+
"<div style='position:absolute;left:0;right:0;height:1px;"
|
| 172 |
+
"background:linear-gradient(90deg,transparent,rgba(229,9,20,0.35),transparent);"
|
| 173 |
+
"animation:scanLine 4s linear infinite;pointer-events:none;z-index:1'></div>"
|
| 174 |
+
# Top line
|
| 175 |
+
"<div style='position:absolute;top:0;left:0;right:0;height:2px;"
|
| 176 |
+
"background:linear-gradient(90deg,transparent,#E50914 35%,rgba(229,9,20,0.35) 65%,transparent)'></div>"
|
| 177 |
+
# Glow orb
|
| 178 |
+
"<div style='position:absolute;top:-50px;right:-50px;width:200px;height:200px;border-radius:50%;"
|
| 179 |
+
"background:radial-gradient(circle,rgba(229,9,20,0.16) 0%,transparent 70%);pointer-events:none'></div>"
|
| 180 |
+
"<div style='display:flex;align-items:center;gap:18px;position:relative;z-index:2'>"
|
| 181 |
+
# Avatar
|
| 182 |
+
"<div style='position:relative;flex-shrink:0;width:60px;height:60px'>"
|
| 183 |
+
"<div style='width:60px;height:60px;border-radius:50%;"
|
| 184 |
+
"background:linear-gradient(135deg,#E50914 0%,#5c000a 100%);"
|
| 185 |
+
"display:flex;align-items:center;justify-content:center;font-size:1.7rem;"
|
| 186 |
+
"box-shadow:0 0 0 3px rgba(229,9,20,0.28),0 0 28px rgba(229,9,20,0.42)'>π€</div>"
|
| 187 |
+
"<div style='position:absolute;inset:-6px;border-radius:50%;"
|
| 188 |
+
"border:2px solid rgba(229,9,20,0.45);animation:pulseRing 2s ease-out infinite'></div>"
|
| 189 |
+
"<div style='position:absolute;inset:-12px;border-radius:50%;"
|
| 190 |
+
"border:1px solid rgba(229,9,20,0.20);animation:pulseRing 2s ease-out infinite 0.6s'></div>"
|
| 191 |
+
"</div>"
|
| 192 |
+
# Title
|
| 193 |
+
"<div style='flex:1'>"
|
| 194 |
+
"<div style='font-family:Syne,sans-serif;font-size:1.55rem;font-weight:800;"
|
| 195 |
+
"background:linear-gradient(90deg,#ffffff 55%,rgba(229,9,20,0.85));background-size:200% auto;"
|
| 196 |
+
"-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;"
|
| 197 |
+
"line-height:1.1;margin-bottom:6px'>ATLAS — Your AI Fitness Coach</div>"
|
| 198 |
+
"<div style='display:flex;flex-wrap:wrap;gap:6px;align-items:center'>"
|
| 199 |
+
+ " ".join([
|
| 200 |
+
f"<span style='font-size:0.76rem;color:rgba(255,255,255,0.40)'>{t}</span>"
|
| 201 |
+
+ (f"<span style='color:rgba(229,9,20,0.40);font-size:0.76rem'>·</span>" if i<3 else "")
|
| 202 |
+
for i,t in enumerate([
|
| 203 |
+
f"Trained on your profile",
|
| 204 |
+
f"{len(sdays)}-day plan",
|
| 205 |
+
f"{diet_lbl} diet",
|
| 206 |
+
data.get("goal","Fitness")
|
| 207 |
+
])
|
| 208 |
+
]) +
|
| 209 |
+
"</div></div>"
|
| 210 |
+
# Online
|
| 211 |
+
"<div style='display:flex;align-items:center;gap:6px;background:rgba(34,197,94,0.09);"
|
| 212 |
+
"border:1px solid rgba(34,197,94,0.25);border-radius:20px;padding:5px 12px;flex-shrink:0'>"
|
| 213 |
+
"<div style='width:7px;height:7px;border-radius:50%;background:#22c55e;"
|
| 214 |
+
"animation:onlineBlink 2s ease-in-out infinite'></div>"
|
| 215 |
+
"<span style='font-size:0.70rem;font-weight:700;letter-spacing:2px;"
|
| 216 |
+
"color:rgba(34,197,94,0.88);text-transform:uppercase'>ONLINE</span>"
|
| 217 |
+
"</div></div></div>",
|
| 218 |
+
unsafe_allow_html=True
|
| 219 |
)
|
| 220 |
|
| 221 |
# ββ CHAT HISTORY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 228 |
st.session_state.chat_messages = []
|
| 229 |
|
| 230 |
# ββ QUICK QUESTIONS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
st.markdown(
|
| 232 |
+
"<div style='font-size:0.66rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
|
| 233 |
+
"color:rgba(229,9,20,0.58);margin-bottom:7px;display:flex;align-items:center;gap:8px'>"
|
| 234 |
+
"<span style='width:12px;height:1px;background:#E50914;display:block'></span>Quick Ask</div>",
|
| 235 |
+
unsafe_allow_html=True)
|
| 236 |
+
quick_qs = [("πͺ","Best post-workout meal?"),("π₯","How to lose fat faster?"),
|
| 237 |
+
("π΄","How much sleep do I need?"),("π₯©","Daily protein target?"),
|
| 238 |
+
("β‘","How to boost energy?"),("π§","Recovery tips for soreness?")]
|
| 239 |
qc = st.columns(3)
|
| 240 |
+
for i,(emoji,q) in enumerate(quick_qs):
|
| 241 |
+
with qc[i%3]:
|
| 242 |
st.markdown("<div class='quick-btn'>", unsafe_allow_html=True)
|
| 243 |
+
if st.button(f"{emoji} {q}", key=f"qq_{i}", use_container_width=True):
|
| 244 |
+
st.session_state.chat_messages.append({"role":"user","content":q})
|
|
|
|
| 245 |
st.rerun()
|
| 246 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 247 |
|
| 248 |
st.markdown("<div style='height:8px'></div>", unsafe_allow_html=True)
|
| 249 |
|
| 250 |
+
# ββ CHAT MESSAGES βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
| 251 |
if not st.session_state.chat_messages:
|
| 252 |
st.markdown(
|
| 253 |
+
f"<div style='text-align:center;padding:52px 20px;animation:fadeSlideUp 0.6s ease both'>"
|
| 254 |
+
f"<div style='font-size:3.8rem;margin-bottom:14px;"
|
| 255 |
+
f"filter:drop-shadow(0 0 24px rgba(229,9,20,0.65))'>π€</div>"
|
| 256 |
+
f"<div style='font-family:Syne,sans-serif;font-size:1.5rem;font-weight:800;"
|
| 257 |
+
f"background:linear-gradient(135deg,#fff 40%,#E50914);"
|
| 258 |
+
f"-webkit-background-clip:text;-webkit-text-fill-color:transparent;"
|
| 259 |
+
f"background-clip:text;letter-spacing:0.5px;margin-bottom:10px'>"
|
| 260 |
+
f"Hey {uname}, I'm ATLAS</div>"
|
| 261 |
+
f"<div style='font-size:0.92rem;color:rgba(255,255,255,0.42);max-width:420px;"
|
| 262 |
+
f"margin:0 auto;line-height:1.75'>"
|
| 263 |
+
f"Your personal AI fitness coach. Ask me anything about workouts, "
|
| 264 |
+
f"nutrition, recovery β I know your full {len(sdays)}-day plan.</div>"
|
| 265 |
+
f"<div style='margin-top:22px;display:flex;justify-content:center;gap:10px;flex-wrap:wrap'>"
|
| 266 |
+
f"<div style='background:rgba(229,9,20,0.09);border:1px solid rgba(229,9,20,0.22);"
|
| 267 |
+
f"border-radius:20px;padding:5px 14px;font-size:0.76rem;color:rgba(255,255,255,0.50)'>π¬ Ongoing conversations</div>"
|
| 268 |
+
f"<div style='background:rgba(229,9,20,0.09);border:1px solid rgba(229,9,20,0.22);"
|
| 269 |
+
f"border-radius:20px;padding:5px 14px;font-size:0.76rem;color:rgba(255,255,255,0.50)'>π§ Knows your full plan</div>"
|
| 270 |
+
f"<div style='background:rgba(229,9,20,0.09);border:1px solid rgba(229,9,20,0.22);"
|
| 271 |
+
f"border-radius:20px;padding:5px 14px;font-size:0.76rem;color:rgba(255,255,255,0.50)'>β‘ Instant answers</div>"
|
| 272 |
+
f"</div></div>",
|
| 273 |
+
unsafe_allow_html=True)
|
| 274 |
else:
|
| 275 |
for mi, msg in enumerate(st.session_state.chat_messages):
|
| 276 |
+
delay = f"{min(mi*0.04,0.30):.2f}s"
|
| 277 |
if msg["role"] == "user":
|
| 278 |
st.markdown(
|
| 279 |
+
f"<div style='display:flex;justify-content:flex-end;margin:8px 0;"
|
| 280 |
+
f"animation:fadeSlideLeft 0.32s ease {delay} both'>"
|
| 281 |
+
f"<div style='max-width:76%'>"
|
| 282 |
+
f"<div style='text-align:right;font-size:0.60rem;font-weight:700;letter-spacing:2px;"
|
| 283 |
+
f"text-transform:uppercase;color:rgba(229,9,20,0.60);margin-bottom:4px;padding-right:6px'>YOU</div>"
|
| 284 |
+
f"<div style='background:linear-gradient(135deg,rgba(229,9,20,0.22),rgba(140,0,8,0.16));"
|
| 285 |
+
f"border:1.5px solid rgba(229,9,20,0.35);border-radius:18px 18px 4px 18px;"
|
| 286 |
+
f"padding:13px 17px;box-shadow:0 4px 20px rgba(229,9,20,0.16)'>"
|
| 287 |
+
f"<div style='font-size:0.95rem;color:#fff;line-height:1.65'>{msg['content']}</div>"
|
| 288 |
+
f"</div></div>"
|
| 289 |
+
f"<div style='width:34px;height:34px;border-radius:50%;"
|
| 290 |
+
f"background:rgba(229,9,20,0.14);border:1.5px solid rgba(229,9,20,0.32);"
|
| 291 |
+
f"display:flex;align-items:center;justify-content:center;"
|
| 292 |
+
f"margin-left:9px;flex-shrink:0;align-self:flex-end;font-size:1rem'>π€</div>"
|
| 293 |
+
f"</div>",
|
| 294 |
+
unsafe_allow_html=True)
|
| 295 |
else:
|
|
|
|
| 296 |
_lines = []
|
| 297 |
for _ln in msg["content"].splitlines():
|
| 298 |
_s = _ln.strip()
|
| 299 |
if _s.startswith("- ") or _s.startswith("β’ "):
|
| 300 |
_lines.append(
|
| 301 |
+
f"<div style='display:flex;gap:9px;margin:5px 0;align-items:flex-start'>"
|
| 302 |
+
f"<span style='color:#E50914;font-size:0.68rem;margin-top:4px;flex-shrink:0;"
|
| 303 |
+
f"text-shadow:0 0 8px rgba(229,9,20,0.55)'>βΆ</span>"
|
| 304 |
+
f"<span style='font-size:0.95rem;color:rgba(255,255,255,0.88);line-height:1.62'>{_s[2:]}</span></div>")
|
| 305 |
elif _s:
|
| 306 |
_lines.append(
|
| 307 |
+
f"<div style='font-size:0.95rem;color:rgba(255,255,255,0.88);"
|
| 308 |
+
f"line-height:1.68;margin-bottom:3px'>{_s}</div>")
|
| 309 |
else:
|
| 310 |
+
_lines.append("<div style='height:4px'></div>")
|
| 311 |
|
| 312 |
st.markdown(
|
| 313 |
+
f"<div style='display:flex;margin:8px 0;"
|
| 314 |
+
f"animation:fadeSlideRight 0.32s ease {delay} both'>"
|
| 315 |
+
f"<div style='width:34px;height:34px;border-radius:50%;"
|
| 316 |
+
f"background:linear-gradient(135deg,#E50914,#7c000a);"
|
| 317 |
+
f"display:flex;align-items:center;justify-content:center;font-size:1rem;"
|
| 318 |
+
f"flex-shrink:0;align-self:flex-end;margin-right:9px;"
|
| 319 |
+
f"box-shadow:0 0 16px rgba(229,9,20,0.40)'>π€</div>"
|
| 320 |
+
f"<div style='max-width:80%'>"
|
| 321 |
+
f"<div style='font-size:0.60rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
|
| 322 |
+
f"color:rgba(229,9,20,0.60);margin-bottom:4px;padding-left:4px'>ATLAS</div>"
|
| 323 |
+
f"<div style='background:rgba(12,10,24,0.90);"
|
| 324 |
+
f"border:1px solid rgba(255,255,255,0.08);border-radius:18px 18px 18px 4px;"
|
| 325 |
+
f"padding:14px 18px;backdrop-filter:blur(24px);"
|
| 326 |
+
f"box-shadow:0 4px 24px rgba(0,0,0,0.45),inset 0 1px 0 rgba(255,255,255,0.04)'>"
|
| 327 |
+ "".join(_lines) +
|
| 328 |
+
f"</div></div></div>",
|
| 329 |
+
unsafe_allow_html=True)
|
|
|
|
| 330 |
|
| 331 |
+
# ββ TYPING DOTS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 332 |
+
if st.session_state.chat_messages and st.session_state.chat_messages[-1]["role"] == "user":
|
| 333 |
+
st.markdown(
|
| 334 |
+
"<div style='display:flex;align-items:center;gap:10px;margin:8px 0;padding-left:43px'>"
|
| 335 |
+
"<div style='display:flex;gap:5px;align-items:center;"
|
| 336 |
+
"background:rgba(12,10,24,0.85);border:1px solid rgba(255,255,255,0.07);"
|
| 337 |
+
"border-radius:20px;padding:9px 16px'>"
|
| 338 |
+
"<div style='width:7px;height:7px;border-radius:50%;background:#E50914;"
|
| 339 |
+
"animation:typing 1.1s ease-in-out infinite 0s'></div>"
|
| 340 |
+
"<div style='width:7px;height:7px;border-radius:50%;background:#E50914;"
|
| 341 |
+
"animation:typing 1.1s ease-in-out infinite 0.22s'></div>"
|
| 342 |
+
"<div style='width:7px;height:7px;border-radius:50%;background:#E50914;"
|
| 343 |
+
"animation:typing 1.1s ease-in-out infinite 0.44s'></div>"
|
| 344 |
+
"<span style='font-size:0.76rem;color:rgba(255,255,255,0.38);margin-left:8px;"
|
| 345 |
+
"font-style:italic'>ATLAS is thinking...</span>"
|
| 346 |
+
"</div></div>",
|
| 347 |
+
unsafe_allow_html=True)
|
| 348 |
|
| 349 |
# ββ PROCESS AI REPLY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 350 |
if st.session_state.chat_messages and st.session_state.chat_messages[-1]["role"] == "user":
|
| 351 |
+
try:
|
| 352 |
+
from model_api import query_model
|
| 353 |
+
history = st.session_state.chat_messages[-10:]
|
| 354 |
+
conv = "".join(
|
| 355 |
+
("User" if m["role"]=="user" else "Coach") + ": " + m["content"] + "\n"
|
| 356 |
+
for m in history[:-1])
|
| 357 |
+
full_prompt = (system_prompt + "\n\nConversation:\n" + conv +
|
| 358 |
+
"\nUser: " + history[-1]["content"] + "\nCoach:")
|
| 359 |
+
reply = query_model(full_prompt, max_tokens=320).strip()
|
| 360 |
+
for _pfx in ["Assistant:","ATLAS:","AI Coach:","Coach:"]:
|
| 361 |
+
if reply.startswith(_pfx): reply = reply[len(_pfx):].strip()
|
| 362 |
+
import re as _re
|
| 363 |
+
_clean = [ln for ln in reply.splitlines()
|
| 364 |
+
if not (ln.strip().startswith("{") or ln.strip().startswith("[")
|
| 365 |
+
or (ln.strip().startswith('"') and ln.strip().endswith('",')))]
|
| 366 |
+
reply = _re.sub(r"\{[^}]*\}","","\n".join(_clean))
|
| 367 |
+
reply = _re.sub(r"\[[^\]]*\]","",reply)
|
| 368 |
+
reply = _re.sub(r"\n{3,}","\n\n",reply).strip()
|
| 369 |
+
if not reply: reply = "Great question! Try asking me again."
|
| 370 |
+
st.session_state.chat_messages.append({"role":"assistant","content":reply})
|
| 371 |
try:
|
| 372 |
+
from utils.db import save_chat_history
|
| 373 |
+
save_chat_history(uname, st.session_state.chat_messages[-30:])
|
| 374 |
+
except Exception: pass
|
| 375 |
+
st.rerun()
|
| 376 |
+
except Exception as e:
|
| 377 |
+
st.error("ATLAS error: " + str(e))
|
| 378 |
+
|
| 379 |
+
st.markdown("<div style='height:10px'></div>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
|
| 381 |
# ββ INPUT BAR βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 382 |
st.markdown(
|
| 383 |
+
"<div style='background:linear-gradient(135deg,rgba(10,8,22,0.94),rgba(14,6,8,0.90));"
|
| 384 |
+
"border:1.5px solid rgba(229,9,20,0.20);border-radius:18px;"
|
| 385 |
+
"padding:13px 16px;backdrop-filter:blur(28px);"
|
| 386 |
+
"box-shadow:0 8px 36px rgba(0,0,0,0.55),inset 0 1px 0 rgba(255,255,255,0.03)'>",
|
| 387 |
+
unsafe_allow_html=True)
|
|
|
|
|
|
|
| 388 |
with st.form("chat_form", clear_on_submit=True):
|
| 389 |
+
inp_col, btn_col = st.columns([5,1])
|
| 390 |
with inp_col:
|
| 391 |
user_input = st.text_input(
|
| 392 |
+
"", placeholder="Ask ATLAS about workouts, nutrition, recovery, goals...",
|
| 393 |
+
key="chat_input_field", label_visibility="collapsed")
|
|
|
|
| 394 |
with btn_col:
|
| 395 |
send = st.form_submit_button("Send β€", use_container_width=True)
|
| 396 |
if send and user_input.strip():
|
|
|
|
| 398 |
st.rerun()
|
| 399 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 400 |
|
| 401 |
+
# ββ BOTTOM BAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 402 |
+
st.markdown("<div style='height:8px'></div>", unsafe_allow_html=True)
|
| 403 |
+
ba1, ba2, ba3 = st.columns([2,3,5])
|
|
|
|
| 404 |
with ba1:
|
| 405 |
if st.session_state.chat_messages:
|
| 406 |
if st.button("ποΈ Clear Chat", key="clear_chat", use_container_width=True):
|
| 407 |
st.session_state.chat_messages = []
|
| 408 |
st.rerun()
|
| 409 |
with ba2:
|
| 410 |
+
mc = len(st.session_state.chat_messages)
|
| 411 |
+
if mc > 0:
|
| 412 |
+
ai_c = sum(1 for m in st.session_state.chat_messages if m["role"]=="assistant")
|
| 413 |
st.markdown(
|
| 414 |
+
f"<div style='background:rgba(229,9,20,0.06);border:1px solid rgba(229,9,20,0.15);"
|
| 415 |
+
f"border-radius:10px;padding:8px 14px;text-align:center;font-size:0.76rem;"
|
| 416 |
+
f"color:rgba(255,255,255,0.40)'>π¬ {mc} messages Β· π€ {ai_c} replies</div>",
|
| 417 |
+
unsafe_allow_html=True)
|
|
|
|
|
|
|
| 418 |
|
| 419 |
+
# ββ WHAT ATLAS KNOWS (empty state only) βββββββββββββββββββββββββββββββββββββββ
|
| 420 |
if not st.session_state.chat_messages:
|
| 421 |
+
st.markdown("<div style='height:18px'></div>", unsafe_allow_html=True)
|
| 422 |
+
caps = [("ποΈ","Workout Plan",f"{len(sdays)}-day personalised"),
|
| 423 |
+
("π₯","Your Diet",f"{diet_lbl} Β· {data.get('goal','Fitness')}"),
|
| 424 |
+
("π","Body Stats",f"{data.get('weight','?')}kg Β· {data.get('height','?')}cm"),
|
| 425 |
+
("βοΈ","Equipment",", ".join(data.get("equipment",[])) if data.get("equipment") else "Bodyweight")]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
cc = st.columns(4)
|
| 427 |
for i,(icon,title,sub) in enumerate(caps):
|
| 428 |
with cc[i]:
|
| 429 |
st.markdown(
|
| 430 |
+
f"<div style='background:rgba(10,8,20,0.68);border:1px solid rgba(229,9,20,0.12);"
|
| 431 |
+
f"border-radius:14px;padding:14px 14px;text-align:center;"
|
| 432 |
+
f"animation:fadeSlideUp 0.5s ease {0.08*i:.2f}s both'>"
|
| 433 |
+
f"<div style='font-size:1.5rem;margin-bottom:6px'>{icon}</div>"
|
| 434 |
+
f"<div style='font-size:0.66rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
|
| 435 |
+
f"color:rgba(229,9,20,0.60);margin-bottom:4px'>{title}</div>"
|
| 436 |
+
f"<div style='font-size:0.74rem;color:rgba(255,255,255,0.38);line-height:1.4'>{sub}</div>"
|
| 437 |
f"</div>",
|
| 438 |
+
unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|