CareerPilot-AI / app.py
AtharAbbas993's picture
Final version of project
65ab06a
Raw
History Blame Contribute Delete
29.5 kB
# app.py
# Pakistan AI Career Roadmap Bot
# Interactive Streamlit Application — Dark Blue/Red Theme
import streamlit as st
from groq import Groq
from dotenv import load_dotenv
import os
from prompts import (
SYSTEM_PROMPT,
get_career_prompt,
is_safe_input,
is_safe_output
)
from domains import (
get_all_domains,
get_domain_info
)
# =====================================
# LOAD API KEY
# =====================================
load_dotenv()
try:
api_key = st.secrets["GROQ_API_KEY"]
except:
api_key = os.getenv("GROQ_API_KEY")
client = Groq(api_key=api_key)
# =====================================
# PAGE CONFIG
# =====================================
st.set_page_config(
page_title="Pakistan AI Career Bot",
page_icon="🤖",
layout="wide"
)
# =====================================
# GLOBAL CSS
# =====================================
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,300&family=JetBrains+Mono:wght@400;500;600&display=swap');
/* ─── CSS VARIABLES ─── */
:root {
--bg-base: #060912;
--bg-surface: #0c1220;
--bg-elevated: #111827;
--bg-card: #0f1729;
--accent-blue: #1d6fce;
--accent-red: #c0392b;
--accent-red-br: #e74c3c;
--accent-blue-br:#3b8eea;
--glow-blue: rgba(29, 111, 206, 0.25);
--glow-red: rgba(192, 57, 43, 0.25);
--text-primary: #eef2ff;
--text-secondary:#8b9cc8;
--text-muted: #4a5578;
--border: #1e2d4a;
--border-bright: #2a3f66;
}
/* ─── BASE ─── */
html, body, [class*="css"] {
font-family: 'DM Sans', sans-serif !important;
background-color: var(--bg-base) !important;
color: var(--text-primary) !important;
}
.stApp {
background:
radial-gradient(ellipse 80% 50% at 10% 0%, rgba(29,111,206,0.12) 0%, transparent 60%),
radial-gradient(ellipse 60% 40% at 90% 100%, rgba(192,57,43,0.10) 0%, transparent 60%),
var(--bg-base) !important;
}
/* Hide streamlit branding */
#MainMenu, footer, header { visibility: hidden; }
.block-container { padding-top: 1.5rem !important; max-width: 1200px; }
/* ─── HERO ─── */
.hero-wrap {
text-align: center;
padding: 2.5rem 1rem 1.5rem;
animation: fadeDown 0.7s cubic-bezier(.16,1,.3,1) both;
}
.hero-eyebrow {
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--accent-red-br);
margin-bottom: 0.7rem;
}
.hero-title {
font-family: 'DM Serif Display', serif;
font-size: 2.8rem;
font-weight: 400;
line-height: 1.15;
color: var(--text-primary);
margin-bottom: 0.6rem;
}
.hero-title span {
background: linear-gradient(110deg, var(--accent-blue-br) 0%, var(--accent-red-br) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-sub {
font-family: 'DM Sans', sans-serif;
font-size: 0.9rem;
color: var(--text-secondary);
font-weight: 300;
letter-spacing: 0.3px;
}
/* ─── STEP TRACK ─── */
.step-track {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
margin: 1.8rem auto 0.5rem;
max-width: 520px;
}
.st-node {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35rem;
}
.st-circle {
width: 34px; height: 34px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
font-weight: 600;
transition: all 0.4s ease;
}
.st-circle.done { background: var(--accent-blue); color: #fff; box-shadow: 0 0 14px var(--glow-blue); }
.st-circle.active { background: linear-gradient(135deg, var(--accent-blue), var(--accent-red)); color: #fff; box-shadow: 0 0 20px var(--glow-red); }
.st-circle.idle { background: var(--bg-elevated); color: var(--text-muted); border: 1px solid var(--border); }
.st-label {
font-size: 0.65rem;
font-family: 'JetBrains Mono', monospace;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
}
.st-label.active-label { color: var(--accent-blue-br); }
.st-line {
flex: 1;
height: 1px;
max-width: 80px;
margin-bottom: 1.2rem;
background: var(--border);
transition: background 0.4s;
}
.st-line.done-line { background: linear-gradient(90deg, var(--accent-blue), var(--accent-red)); }
/* ─── SECTION HEADING ─── */
.sec-heading {
font-family: 'DM Serif Display', serif;
font-size: 1.6rem;
color: var(--text-primary);
margin-bottom: 0.3rem;
}
.sec-sub {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 1.8rem;
font-weight: 300;
}
/* ─── DOMAIN CARDS ─── */
.d-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 1.4rem 1.2rem;
height: 100%;
transition: all 0.3s cubic-bezier(.4,0,.2,1);
position: relative;
overflow: hidden;
animation: fadeUp 0.5s ease both;
}
.d-card::after {
content: '';
position: absolute;
inset: 0;
border-radius: 14px;
background: linear-gradient(135deg, var(--glow-blue), var(--glow-red));
opacity: 0;
transition: opacity 0.3s ease;
}
.d-card:hover {
border-color: var(--accent-blue);
transform: translateY(-3px);
box-shadow: 0 12px 35px var(--glow-blue);
}
.d-card:hover::after { opacity: 1; }
.d-icon { font-size: 1.9rem; margin-bottom: 0.7rem; position: relative; z-index: 1; }
.d-name {
font-family: 'DM Serif Display', serif;
font-size: 1rem;
color: var(--text-primary);
margin-bottom: 0.35rem;
position: relative; z-index: 1;
}
.d-desc {
font-size: 0.78rem;
color: var(--text-secondary);
line-height: 1.55;
margin-bottom: 0.8rem;
font-weight: 300;
position: relative; z-index: 1;
}
.d-chip {
display: inline-block;
background: rgba(29,111,206,0.12);
border: 1px solid rgba(29,111,206,0.3);
color: var(--accent-blue-br);
font-size: 0.66rem;
font-family: 'JetBrains Mono', monospace;
padding: 0.18rem 0.55rem;
border-radius: 20px;
margin: 0.15rem 0.15rem 0 0;
position: relative; z-index: 1;
}
/* ─── SELECTED DOMAIN BANNER ─── */
.sel-banner {
display: flex;
align-items: center;
gap: 1.2rem;
background: linear-gradient(110deg, rgba(29,111,206,0.1) 0%, rgba(192,57,43,0.08) 100%);
border: 1px solid var(--accent-blue);
border-radius: 14px;
padding: 1.2rem 1.5rem;
margin-bottom: 1.8rem;
animation: fadeUp 0.4s ease both;
}
.sel-icon { font-size: 2.2rem; }
.sel-name {
font-family: 'DM Serif Display', serif;
font-size: 1.25rem;
color: var(--text-primary);
}
.sel-desc {
font-size: 0.8rem;
color: var(--text-secondary);
font-weight: 300;
margin-top: 0.1rem;
}
/* ─── STATS ROW ─── */
.stats-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 2rem;
}
.s-card {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1rem 1.2rem;
text-align: center;
}
.s-val {
font-family: 'DM Serif Display', serif;
font-size: 1.6rem;
background: linear-gradient(110deg, var(--accent-blue-br), var(--accent-red-br));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.s-lbl {
font-size: 0.68rem;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 0.15rem;
}
/* ─── FORM ─── */
.form-card {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 2rem;
animation: fadeUp 0.5s ease 0.1s both;
}
.form-heading {
font-family: 'DM Serif Display', serif;
font-size: 1.3rem;
color: var(--text-primary);
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.6rem;
}
/* ─── INPUTS ─── */
.stTextInput input,
.stSelectbox > div > div,
.stMultiSelect > div > div {
background: var(--bg-card) !important;
border: 1px solid var(--border-bright) !important;
border-radius: 10px !important;
color: var(--text-primary) !important;
font-family: 'DM Sans', sans-serif !important;
font-size: 0.88rem !important;
}
.stTextInput input:focus {
border-color: var(--accent-blue) !important;
box-shadow: 0 0 0 2px var(--glow-blue) !important;
}
label, .stSelectbox label, .stMultiSelect label, .stTextInput label, .stRadio label {
color: var(--text-secondary) !important;
font-size: 0.82rem !important;
font-weight: 500 !important;
letter-spacing: 0.3px;
}
/* ─── BUTTONS ─── */
.stButton > button {
background: linear-gradient(110deg, var(--accent-blue) 0%, #1a5bb0 100%) !important;
color: #fff !important;
border: none !important;
border-radius: 10px !important;
font-family: 'DM Sans', sans-serif !important;
font-weight: 600 !important;
font-size: 0.88rem !important;
letter-spacing: 0.3px !important;
padding: 0.55rem 1.4rem !important;
transition: all 0.25s ease !important;
}
.stButton > button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 8px 24px var(--glow-blue) !important;
background: linear-gradient(110deg, var(--accent-blue-br), var(--accent-blue)) !important;
}
/* ─── GENERATING SCREEN ─── */
.gen-wrap {
text-align: center;
padding: 4rem 1rem;
animation: fadeUp 0.5s ease both;
}
.gen-title {
font-family: 'DM Serif Display', serif;
font-size: 1.8rem;
color: var(--text-primary);
margin: 1rem 0 0.5rem;
}
.gen-sub {
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 300;
font-family: 'JetBrains Mono', monospace;
}
.pulse-dot {
display: inline-block;
width: 10px; height: 10px;
background: var(--accent-red-br);
border-radius: 50%;
margin: 0 4px;
animation: pulse 1.2s ease-in-out infinite;
}
.pulse-dot:nth-child(2) { animation-delay: 0.2s; background: var(--accent-blue-br); }
.pulse-dot:nth-child(3) { animation-delay: 0.4s; }
/* ─── ROADMAP DISPLAY ─── */
.roadmap-wrap {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 18px;
padding: 2.2rem;
margin: 1rem 0;
animation: fadeUp 0.5s ease both;
line-height: 1.8;
}
/* Claude-style typography for roadmap content */
.roadmap-wrap h1, .roadmap-wrap h2, .roadmap-wrap h3 {
font-family: 'DM Serif Display', serif !important;
color: var(--text-primary) !important;
}
.roadmap-wrap p, .roadmap-wrap li {
font-family: 'DM Sans', sans-serif !important;
font-size: 0.92rem !important;
color: var(--text-secondary) !important;
line-height: 1.8 !important;
font-weight: 300 !important;
}
.roadmap-wrap strong {
color: var(--text-primary) !important;
font-weight: 600 !important;
}
.roadmap-wrap code {
font-family: 'JetBrains Mono', monospace !important;
background: var(--bg-elevated) !important;
border: 1px solid var(--border) !important;
padding: 0.15rem 0.4rem !important;
border-radius: 4px !important;
font-size: 0.82rem !important;
color: var(--accent-blue-br) !important;
}
/* Markdown rendered inside st.markdown for roadmap */
.element-container .stMarkdown p {
font-family: 'DM Sans', sans-serif !important;
font-size: 0.92rem !important;
color: var(--text-secondary) !important;
line-height: 1.8 !important;
font-weight: 300 !important;
}
.element-container .stMarkdown h1,
.element-container .stMarkdown h2,
.element-container .stMarkdown h3 {
font-family: 'DM Serif Display', serif !important;
color: var(--text-primary) !important;
}
.element-container .stMarkdown li {
font-family: 'DM Sans', sans-serif !important;
font-size: 0.9rem !important;
color: var(--text-secondary) !important;
line-height: 1.75 !important;
}
/* ─── ROADMAP HEADER ─── */
.roadmap-hdr {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1.8rem;
padding-bottom: 1.2rem;
border-bottom: 1px solid var(--border);
}
.roadmap-hdr-icon { font-size: 2.4rem; }
.roadmap-hdr-title {
font-family: 'DM Serif Display', serif;
font-size: 1.4rem;
color: var(--text-primary);
}
.roadmap-hdr-sub {
font-size: 0.78rem;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
margin-top: 0.1rem;
}
/* ─── CHAT ─── */
[data-testid="stChatMessage"] {
background: var(--bg-elevated) !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
margin-bottom: 0.8rem !important;
}
[data-testid="stChatMessage"] p {
font-family: 'DM Sans', sans-serif !important;
font-size: 0.9rem !important;
color: var(--text-secondary) !important;
line-height: 1.7 !important;
}
.stChatInput > div {
background: var(--bg-elevated) !important;
border: 1px solid var(--border-bright) !important;
border-radius: 12px !important;
}
/* ─── DOWNLOAD BUTTON ─── */
.stDownloadButton > button {
background: linear-gradient(110deg, var(--accent-red) 0%, #a93226 100%) !important;
color: #fff !important;
border: none !important;
border-radius: 10px !important;
font-family: 'DM Sans', sans-serif !important;
font-weight: 600 !important;
font-size: 0.85rem !important;
transition: all 0.25s ease !important;
}
.stDownloadButton > button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 8px 24px var(--glow-red) !important;
}
/* ─── PROGRESS BAR ─── */
.stProgress > div > div > div {
background: linear-gradient(90deg, var(--accent-blue), var(--accent-red-br)) !important;
border-radius: 4px !important;
}
/* ─── DIVIDER ─── */
hr { border-color: var(--border) !important; }
/* ─── SIDEBAR ─── */
[data-testid="stSidebar"] {
background: var(--bg-surface) !important;
border-right: 1px solid var(--border) !important;
}
/* ─── SCROLLBAR ─── */
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: var(--bg-base); }
::-webkit-scrollbar-thumb { background: var(--border-bright); border-radius: 3px; }
/* ─── FOOTER ─── */
.footer {
text-align: center;
padding: 2rem 0 1rem;
border-top: 1px solid var(--border);
margin-top: 3rem;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
font-size: 0.72rem;
letter-spacing: 0.5px;
}
/* ─── ANIMATIONS ─── */
@keyframes fadeDown {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.75); }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* stagger domain cards */
.d-card:nth-child(1){animation-delay:.05s}
.d-card:nth-child(2){animation-delay:.10s}
.d-card:nth-child(3){animation-delay:.15s}
.d-card:nth-child(4){animation-delay:.20s}
.d-card:nth-child(5){animation-delay:.25s}
.d-card:nth-child(6){animation-delay:.30s}
.d-card:nth-child(7){animation-delay:.35s}
.d-card:nth-child(8){animation-delay:.40s}
/* warning / error */
.stAlert { border-radius: 10px !important; }
</style>
""", unsafe_allow_html=True)
# =====================================
# DOMAIN ICONS
# =====================================
DOMAIN_ICONS = {
"Machine Learning Engineer": "🧠",
"Deep Learning Engineer": "⚡",
"Computer Vision Engineer": "👁️",
"NLP Engineer": "💬",
"MLOps Engineer": "⚙️",
"Data Scientist": "📊",
"Generative AI Engineer": "✨",
"AI Research Engineer": "🔬"
}
# =====================================
# SESSION STATE
# =====================================
defaults = {
"messages": [{"role": "system", "content": SYSTEM_PROMPT}],
"roadmap_generated": False,
"selected_domain": None,
"roadmap_text": "",
"current_step": 1,
"student_name": "",
"form_data": {}
}
for k, v in defaults.items():
if k not in st.session_state:
st.session_state[k] = v
# =====================================
# HERO
# =====================================
st.markdown("""
<div class="hero-wrap">
<div class="hero-eyebrow">● Pakistan AI Career Roadmap Bot</div>
<div class="hero-title">Find Your Path in <span>Artificial Intelligence</span></div>
<div class="hero-sub">AI-powered personalized learning roadmaps for students — by Athar Abbas</div>
</div>
""", unsafe_allow_html=True)
# =====================================
# STEP TRACKER
# =====================================
step = st.session_state.current_step
labels = ["Domain", "Profile", "Generating", "Roadmap"]
def sc(n): # circle class
if n < step: return "done"
if n == step: return "active"
return "idle"
def lc(n): # line class
return "done-line" if n < step else ""
nodes_html = ""
for i, lbl in enumerate(labels, 1):
nodes_html += f'<div class="st-node"><div class="st-circle {sc(i)}">{i}</div><div class="st-label {"active-label" if i==step else ""}">{lbl}</div></div>'
if i < len(labels):
nodes_html += f'<div class="st-line {lc(i)}"></div>'
st.markdown(f'<div class="step-track">{nodes_html}</div>', unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
# ======================================================
# STEP 1 — CHOOSE DOMAIN
# ======================================================
if step == 1:
st.markdown('<div class="sec-heading">Choose Your AI Domain</div>', unsafe_allow_html=True)
st.markdown('<div class="sec-sub">Select the specialisation that excites you most — we\'ll build a roadmap around it.</div>', unsafe_allow_html=True)
all_domains = get_all_domains()
cols = st.columns(4)
for i, dname in enumerate(all_domains):
dinfo = get_domain_info(dname)
icon = DOMAIN_ICONS.get(dname, "🤖")
chips = "".join([f'<span class="d-chip">{s}</span>'
for s in dinfo["skills_required"][:3]])
with cols[i % 4]:
st.markdown(f"""
<div class="d-card">
<div class="d-icon">{icon}</div>
<div class="d-name">{dname}</div>
<div class="d-desc">{dinfo['description']}</div>
<div>{chips}</div>
</div>
""", unsafe_allow_html=True)
if st.button(f"Select {icon}", key=f"d{i}", use_container_width=True):
st.session_state.selected_domain = dname
st.session_state.current_step = 2
st.rerun()
# ======================================================
# STEP 2 — STUDENT PROFILE
# ======================================================
elif step == 2:
dinfo = get_domain_info(st.session_state.selected_domain)
icon = DOMAIN_ICONS.get(st.session_state.selected_domain, "🤖")
# Banner
st.markdown(f"""
<div class="sel-banner">
<span class="sel-icon">{icon}</span>
<div>
<div class="sel-name">{st.session_state.selected_domain}</div>
<div class="sel-desc">{dinfo['description']}</div>
</div>
</div>
""", unsafe_allow_html=True)
# Stats (no salary, no duration)
st.markdown(f"""
<div class="stats-row">
<div class="s-card">
<div class="s-val">{len(dinfo['skills_required'])}</div>
<div class="s-lbl">Core Skills</div>
</div>
<div class="s-card">
<div class="s-val">{len(dinfo['free_resources'])}</div>
<div class="s-lbl">Free Resources</div>
</div>
</div>
""", unsafe_allow_html=True)
# Form
st.markdown('<div class="form-card">', unsafe_allow_html=True)
st.markdown('<div class="form-heading">👤 Tell Us About Yourself</div>', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
name = st.text_input("Your Good Name", placeholder="e.g. Athar Abbas")
education = st.selectbox("Current Education", [
"Matric / O-Levels",
"FSc / A-Levels",
"BS CS/IT/AI — 1st Semester",
"BS CS/IT/AI — 2nd Semester",
"BS CS/IT/AI — 3rd Semester",
"BS CS/IT/AI — 4th Semester",
"BS CS/IT/AI — 5th Semester",
"BS CS/IT/AI — 6th Semester",
"BS CS/IT/AI — 7th Semester",
"BS CS/IT/AI — 8th Semester",
"Fresh Graduate",
"Working Professional",
"Other"
])
with col2:
experience = st.selectbox("Coding Experience Level", [
"Complete Beginner — No experience",
"Beginner — Less than 6 months",
"Intermediate — 6 months to 1 year",
"Solid — 1 to 2 years",
"Experienced — 2+ years"
])
skills = st.multiselect("Your Current Skills", [
"Python Basics",
"Python Advanced",
"Mathematics",
"Statistics",
"Machine Learning Basics",
"Deep Learning",
"Data Analysis",
"SQL",
"Web Development",
"No Skills Yet"
])
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
c1, c2 = st.columns([1, 3])
with c1:
if st.button("← Back", use_container_width=True):
st.session_state.current_step = 1
st.rerun()
with c2:
if st.button("🚀 Generate My Roadmap", use_container_width=True):
if not name.strip():
st.warning("⚠️ Please enter your name to continue.")
else:
st.session_state.student_name = name.strip()
st.session_state.form_data = {
"name": name.strip(),
"education": education,
"skills": skills,
"experience": experience
}
st.session_state.current_step = 3
st.rerun()
# ======================================================
# STEP 3 — GENERATING
# ======================================================
elif step == 3:
st.markdown("""
<div class="gen-wrap">
<div style="font-size:3.5rem;animation:fadeUp 0.4s ease both">⚡</div>
<div class="gen-title">Building Your Roadmap</div>
<div class="gen-sub">Analysing profile · cross-checking recommendations · writing plan</div>
<div style="margin-top:1.2rem">
<span class="pulse-dot"></span>
<span class="pulse-dot"></span>
<span class="pulse-dot"></span>
</div>
</div>
""", unsafe_allow_html=True)
progress_bar = st.progress(0, text="Initialising…")
form = st.session_state.form_data
career_prompt = get_career_prompt(
name = form["name"],
education = form["education"],
skills = ", ".join(form["skills"]) if form["skills"] else "No skills yet",
domain = st.session_state.selected_domain,
experience = form["experience"]
)
if not is_safe_input(career_prompt):
st.error("⚠️ Invalid input detected.")
st.session_state.current_step = 2
st.rerun()
st.session_state.messages.append({"role": "user", "content": career_prompt})
responses = []
for i in range(3):
pct = (i + 1) * 30
msgs = [f"Generating roadmap — pass {i+1}/3…",
"Cross-checking domain knowledge…",
"Selecting best response…"]
progress_bar.progress(pct, text=msgs[i])
resp = client.chat.completions.create(
model = "llama-3.1-8b-instant",
messages = st.session_state.messages,
temperature= 0.3,
max_tokens = 2000
)
responses.append(resp.choices[0].message.content)
progress_bar.progress(100, text="✅ Done!")
best = max(responses, key=len)
if not is_safe_output(best):
st.error("⚠️ Something went wrong. Please try again.")
st.session_state.current_step = 2
else:
st.session_state.messages.append({"role": "assistant", "content": best})
st.session_state.roadmap_text = best
st.session_state.roadmap_generated= True
st.session_state.current_step = 4
st.rerun()
# ======================================================
# STEP 4 — ROADMAP DISPLAY
# ======================================================
elif step == 4:
icon = DOMAIN_ICONS.get(st.session_state.selected_domain, "🤖")
dinfo = get_domain_info(st.session_state.selected_domain)
name = st.session_state.student_name
# Top row: title + download
col_hdr, col_dl = st.columns([3, 1])
with col_hdr:
st.markdown(f"""
<div class="roadmap-hdr">
<span class="roadmap-hdr-icon">{icon}</span>
<div>
<div class="roadmap-hdr-title">{st.session_state.selected_domain}</div>
<div class="roadmap-hdr-sub">Roadmap for {name}</div>
</div>
</div>
""", unsafe_allow_html=True)
with col_dl:
download_content = (
f"AI CAREER ROADMAP\n"
f"For: {name}\n"
f"Domain: {st.session_state.selected_domain}\n"
f"{'─'*50}\n\n"
f"{st.session_state.roadmap_text}\n\n"
f"{'─'*50}\n"
f"Generated by AI Career Roadmap Bot\n"
)
st.markdown("<br>", unsafe_allow_html=True)
st.download_button(
label = "⬇️ Download Roadmap",
data = download_content,
file_name= f"AI_Roadmap_{name.replace(' ','_')}.txt",
mime = "text/plain",
use_container_width=True
)
# Stats (no salary, no duration)
st.markdown(f"""
<div class="stats-row">
<div class="s-card">
<div class="s-val">{len(dinfo['skills_required'])}</div>
<div class="s-lbl">Skills in Roadmap</div>
</div>
<div class="s-card">
<div class="s-val">{len(dinfo['pakistani_companies'])}</div>
<div class="s-lbl">Hiring Companies</div>
</div>
</div>
""", unsafe_allow_html=True)
# Roadmap content — styled like Claude output
st.markdown('<div class="roadmap-wrap">', unsafe_allow_html=True)
st.markdown(st.session_state.roadmap_text)
st.markdown("</div>", unsafe_allow_html=True)
st.divider()
# ── Follow-up chat ──
st.markdown('<div class="sec-heading" style="font-size:1.2rem">💬 Ask Follow-up Questions</div>', unsafe_allow_html=True)
st.markdown('<div class="sec-sub">Have questions about your roadmap? Ask anything below.</div>', unsafe_allow_html=True)
# show only follow-up messages (skip system + initial roadmap exchange)
for msg in st.session_state.messages[3:]:
with st.chat_message(msg["role"]):
st.write(msg["content"])
user_q = st.chat_input("Ask about resources, skills, companies…")
if user_q:
if not is_safe_input(user_q):
st.error("⚠️ Please ask career-related questions only.")
else:
st.session_state.messages.append({"role": "user", "content": user_q})
with st.spinner("Thinking…"):
resp = client.chat.completions.create(
model = "llama-3.1-8b-instant",
messages = st.session_state.messages,
temperature= 0.3,
max_tokens = 1000
)
reply = resp.choices[0].message.content
if not is_safe_output(reply):
st.error("⚠️ Something went wrong.")
else:
st.session_state.messages.append({"role": "assistant", "content": reply})
st.rerun()
st.divider()
# Action buttons
c1, c2 = st.columns(2)
with c1:
if st.button("🔄 New Roadmap", use_container_width=True):
for k, v in defaults.items():
st.session_state[k] = v
st.rerun()
with c2:
if st.button("🔀 Change Domain", use_container_width=True):
st.session_state.messages = [{"role": "system", "content": SYSTEM_PROMPT}]
st.session_state.roadmap_generated = False
st.session_state.roadmap_text = ""
st.session_state.selected_domain = None
st.session_state.current_step = 1
st.rerun()
# ── FOOTER ──
st.markdown("""
<div class="footer">
© 2026 Athar Abbas AI Engineer
</div>
""", unsafe_allow_html=True)