import gradio as gr
import requests
import base64
import time
import re
# --- IMPORTANT ---
# Paste the web endpoint URL you got from deploying the Modal app here.
MODAL_WEB_ENDPOINT_URL = "https://aryanjathar0723--career-advisor-agent-gemini-web-endpoint.modal.run"
if MODAL_WEB_ENDPOINT_URL.startswith("https://your-org"):
print("="*80)
print("!!! WARNING: You have not replaced the placeholder MODAL_WEB_ENDPOINT_URL. !!!")
print("!!! Please deploy the modal_agent.py script and paste the URL in app.py. !!!")
print("="*80)
def extract_sections(markdown_text):
"""Extract different sections from the markdown response"""
sections = {
'summary': '',
'roles': '',
'skills': '',
'learning': '',
'projects': '',
'certifications': ''
}
current_section = None
current_content = []
for line in markdown_text.split('\n'):
if line.startswith('### π«'):
current_section = 'summary'
current_content = []
elif line.startswith('### π―'):
current_section = 'roles'
current_content = []
elif line.startswith('### π'):
current_section = 'skills'
current_content = []
elif line.startswith('### π'):
current_section = 'learning'
current_content = []
elif line.startswith('### π‘'):
current_section = 'projects'
current_content = []
elif line.startswith('### π'):
current_section = 'certifications'
current_content = []
# Skip roadmap section
elif line.startswith('### πΊοΈ'):
current_section = None
current_content = []
elif current_section:
current_content.append(line)
sections[current_section] = '\n'.join(current_content)
return sections
def format_skill_bars(text):
"""Convert skill meter text to HTML progress bars"""
formatted = []
in_skill_meter = False
for line in text.split('\n'):
if '```skill-meter' in line:
in_skill_meter = True
formatted.append('
')
continue
elif '```' in line and in_skill_meter:
in_skill_meter = False
formatted.append('
')
continue
if in_skill_meter and '[' in line and ']' in line:
try:
skill_name, rest = line.split('[')
percentage = re.search(r'(\d+)%', rest)
if percentage:
pct = int(percentage.group(1))
formatted.append(f'''
{skill_name.strip()}
{pct}%
''')
except:
formatted.append(line)
else:
formatted.append(line)
return '\n'.join(formatted)
def format_project_cards(text):
"""Convert project-card text to HTML cards"""
formatted = []
in_project_card = False
current_card = {}
for line in text.split('\n'):
if '```project-card' in line:
in_project_card = True
current_card = {}
continue
elif '```' in line and in_project_card:
in_project_card = False
if current_card:
card_html = f'''
{current_card.get('project', 'Project')}
Difficulty: {current_card.get('difficulty', 'βββ')}
Duration: {current_card.get('duration', '2 weeks')}
Skills: {current_card.get('skills', 'Various skills')}
Description: {current_card.get('description', 'Project description')}
'''
formatted.append(card_html)
continue
if in_project_card:
line = line.strip()
if line.startswith('Project:'):
current_card['project'] = line[len('Project:'):].strip()
elif line.startswith('Difficulty:'):
current_card['difficulty'] = line[len('Difficulty:'):].strip()
elif line.startswith('Duration:'):
current_card['duration'] = line[len('Duration:'):].strip()
elif line.startswith('Skills:'):
current_card['skills'] = line[len('Skills:'):].strip()
elif line.startswith('Description:'):
current_card['description'] = line[len('Description:'):].strip()
else:
formatted.append(line)
return '\n'.join(formatted)
def format_roles(content):
"""Format roles as cards"""
formatted_content = []
current_role = []
in_role = False
for line in content.split('\n'):
if line.strip().startswith('1.') or line.strip().startswith('2.') or line.strip().startswith('3.'):
if in_role:
formatted_content.append(format_role_card(current_role))
in_role = True
current_role = [line]
elif in_role and line.strip():
current_role.append(line)
if current_role:
formatted_content.append(format_role_card(current_role))
return '\n'.join(formatted_content)
def format_role_card(role_lines):
"""Helper function to format role card"""
role_title = role_lines[0].strip()
# Extract role name and match score
role_name = ""
match_score = ""
if "**" in role_title:
parts = role_title.split("**")
if len(parts) > 1:
role_name = parts[1].strip()
if "(Match Score:" in role_title:
match_parts = role_title.split("(Match Score:")
if len(match_parts) > 1:
match_score = match_parts[1].split(")")[0].strip()
card_html = f'''
{role_name} (Match Score: {match_score})
'''
for line in role_lines[1:]:
line = line.strip()
if line.startswith('-'):
detail = line[1:].strip()
if "Salary Range:" in detail:
card_html += f'
Salary Range: {detail.split("Salary Range:")[1].strip()}
'
elif "Key Requirements:" in detail:
card_html += f'
Key Requirements: {detail.split("Key Requirements:")[1].strip()}
'
elif "Why It Fits:" in detail:
card_html += f'
Why It Fits: {detail.split("Why It Fits:")[1].strip()}
'
else:
card_html += f'
{detail}
'
card_html += '
'
return card_html
def format_certification_card(cert_lines):
"""Helper function to format certification card"""
cert_name = cert_lines[0].replace('*', '').strip()
card_html = f'''
{cert_name}
'''
for line in cert_lines[1:]:
if line.strip():
line = line.strip().strip('*').strip('-').strip()
if "difficulty level:" in line.lower():
stars = line.split(":", 1)[1].strip() if ":" in line else ""
card_html += f'
- Difficulty Level: {stars}
'
elif "time commitment:" in line.lower():
time = line.split(":", 1)[1].strip() if ":" in line else ""
card_html += f'
- Time Commitment: {time}
'
elif "cost range:" in line.lower():
cost = line.split(":", 1)[1].strip() if ":" in line else ""
card_html += f'
- Cost Range: {cost}
'
else:
card_html += f'
β’ {line}
'
card_html += '
'
return card_html
def parse_certifications(content):
"""Parse certification content to group details by certification"""
lines = content.split('\n')
cert_groups = []
current_cert = []
# Check if content follows the format from the screenshot with dashes
has_cert_headers = any(line.strip().startswith('- ') for line in lines)
if has_cert_headers:
cert_name = ""
cert_details = []
for i, line in enumerate(lines):
line = line.strip()
if not line:
continue
if line.startswith('- ') and not any(detail in line.lower() for detail in ['difficulty level', 'time commitment', 'cost range']):
# This is a new certificate name
if cert_name:
# Save the previous certificate
cert_groups.append([f"* {cert_name}"] + cert_details)
cert_name = line[2:].strip()
cert_details = []
elif line.startswith('- '):
# This is a detail for the current certificate
cert_details.append(line)
# Add the last certificate
if cert_name:
cert_groups.append([f"* {cert_name}"] + cert_details)
else:
# Fall back to original parsing
in_cert = False
for line in lines:
if line.strip().startswith('*'):
if in_cert and current_cert:
cert_groups.append(current_cert)
in_cert = True
current_cert = [line]
elif in_cert and line.strip():
current_cert.append(line)
if current_cert:
cert_groups.append(current_cert)
return cert_groups
def format_sections(sections):
"""Format all sections with proper styling"""
css = """
"""
formatted = {}
for key, content in sections.items():
if key == 'skills':
formatted[key] = css + format_skill_bars(content)
elif key == 'summary':
# Format summary as an expandable card
lines = content.split('\n')
formatted_content = ['']
formatted_content.append('')
formatted_content.append('
')
for line in lines:
if line.strip():
formatted_content.append(f'
{line}
')
formatted_content.append('
')
formatted_content.append('
')
formatted[key] = '\n'.join(formatted_content)
elif key == 'roles':
# Format roles as cards
formatted[key] = css + format_roles(content)
elif key == 'projects':
# Format projects as cards
formatted[key] = css + format_project_cards(content)
elif key == 'learning':
# Format learning path as cards
formatted_content = []
current_month = []
in_month = False
for line in content.split('\n'):
if line.strip().startswith('1.') or line.strip().startswith('2.') or line.strip().startswith('3.'):
if in_month:
card_content = '\n'.join(current_month)
month_range = current_month[0].split(':')[0].replace('*', '').strip()
card_html = f'''
{month_range}
{format_learning_content(card_content)}
'''
formatted_content.append(card_html)
in_month = True
current_month = [line]
elif in_month:
current_month.append(line)
if current_month:
card_content = '\n'.join(current_month)
month_range = current_month[0].split(':')[0].replace('*', '').strip()
card_html = f'''
{month_range}
{format_learning_content(card_content)}
'''
formatted_content.append(card_html)
formatted[key] = css + '\n'.join(formatted_content)
elif key == 'certifications':
# Format certifications as cards
formatted_content = []
# Parse certifications into groups
cert_groups = parse_certifications(content)
for cert_group in cert_groups:
formatted_content.append(format_certification_card(cert_group))
if not formatted_content:
# If no certifications were found, add a placeholder
formatted_content.append('')
formatted[key] = css + '\n'.join(formatted_content)
else:
formatted[key] = content
return formatted
def format_learning_content(content):
"""Helper function to format learning card content"""
formatted = []
for line in content.split('\n'):
line = line.strip()
if 'Course:' in line:
course = line.split('Course:')[1].strip().strip('"')
formatted.append(f'π Course: {course}
')
elif 'Project:' in line:
project = line.split('Project:')[1].strip().strip('"')
formatted.append(f'π» Project: {project}
')
elif 'Expected Outcome:' in line:
outcome = line.split('Expected Outcome:')[1].strip().strip('"')
formatted.append(f'π― Expected Outcome: {outcome}
')
return '\n'.join(formatted)
def get_advice_from_agent(bio, interest, resume_file):
"""
This function prepares the data and calls the Modal backend.
"""
if not bio or not interest:
sections = {
"summary": "Please provide your bio/goals and select an interest area.",
"roles": "", "skills": "", "learning": "",
"projects": "", "certifications": ""
}
formatted = format_sections(sections)
return [
formatted["summary"],
formatted["roles"],
formatted["skills"],
formatted["learning"],
formatted["projects"],
formatted["certifications"]
]
# Show a thinking message immediately
sections = {
"summary": "π€ Agent is thinking... Parsing your profile and crafting a response. This may take a moment.",
"roles": "", "skills": "", "learning": "",
"projects": "", "certifications": ""
}
formatted = format_sections(sections)
yield [
formatted["summary"],
formatted["roles"],
formatted["skills"],
formatted["learning"],
formatted["projects"],
formatted["certifications"]
]
payload = {
"bio": bio,
"interest": interest,
}
# Handle the optional resume file
if resume_file is not None:
with open(resume_file.name, "rb") as f:
file_content = f.read()
encoded_file = base64.b64encode(file_content).decode("utf-8")
payload["resume"] = {
"name": resume_file.name,
"data": encoded_file
}
try:
print("Making request to Modal endpoint...")
response = requests.post(MODAL_WEB_ENDPOINT_URL, json=payload, timeout=120)
print(f"Response status code: {response.status_code}")
response.raise_for_status()
result = response.json()
print("Raw response from Modal:", result)
advice_text = result.get("advice", "")
print("Advice text:", advice_text[:200] + "..." if advice_text else "No advice text")
sections = extract_sections(advice_text)
print("Extracted sections:", sections.keys())
# Format sections with proper styling
formatted = format_sections(sections)
output = [
formatted["summary"],
formatted["roles"],
formatted["skills"],
formatted["learning"],
formatted["projects"],
formatted["certifications"]
]
print("Returning output with lengths:", [len(str(x)) for x in output])
yield output
except Exception as e:
print(f"Error occurred: {str(e)}")
error_sections = {
"summary": f"An error occurred: {str(e)}",
"roles": "Error occurred",
"skills": "Error occurred",
"learning": "Error occurred",
"projects": "Error occurred",
"certifications": "Error occurred"
}
formatted = format_sections(error_sections)
yield [
formatted["summary"],
formatted["roles"],
formatted["skills"],
formatted["learning"],
formatted["projects"],
formatted["certifications"]
]
# Define the Gradio UI using Blocks for custom layout
with gr.Blocks(theme=gr.themes.Soft(), title="AI Career Advisor") as demo:
gr.Markdown(
"""
# π― AI Career Advisor
Get personalized career advice, skill-gap analysis, and a learning roadmap from an AI agent.
"""
)
with gr.Row():
# Left column for input
with gr.Column(scale=1):
gr.Markdown("### π Your Profile")
user_bio = gr.Textbox(
label="Your Bio or Goal",
placeholder="e.g., I'm a 3rd-year IT student interested in Machine Learning and want to become an ML Engineer.",
lines=4
)
interest_area = gr.Dropdown(
label="Primary Area of Interest",
choices=["AI / Data Science", "Web Development", "Cybersecurity",
"Cloud Computing", "DevOps", "Game Development"]
)
resume_upload = gr.File(
label="Upload Resume (PDF or DOCX)",
file_types=[".pdf", ".docx"]
)
submit_btn = gr.Button("Get Career Advice", variant="primary")
# Right column for output
with gr.Column(scale=2):
with gr.Tabs():
with gr.Tab("π Overview"):
summary_md = gr.HTML()
roles_md = gr.HTML()
with gr.Tab("π― Skills & Learning"):
skills_md = gr.HTML()
learning_md = gr.HTML()
with gr.Tab("π‘ Projects"):
projects_md = gr.HTML()
with gr.Tab("π Certifications"):
cert_md = gr.HTML()
submit_btn.click(
fn=get_advice_from_agent,
inputs=[user_bio, interest_area, resume_upload],
outputs=[summary_md, roles_md, skills_md, learning_md, projects_md, cert_md]
)
gr.Examples(
examples=[
["I am a software developer with 5 years of experience in Java, and I want to transition into a DevOps role.", "DevOps", None],
["I'm a final year marketing student fascinated by data. I know some basic Python and SQL and want a career that blends marketing with data analytics.", "AI / Data Science", None],
],
inputs=[user_bio, interest_area, resume_upload]
)
if __name__ == "__main__":
demo.launch()