CareerAgent / app.py
TouradAi's picture
Update application file
673aa37
"""
Career Agent - Intelligent CV and Cover Letter Generator
========================================================
An AI agent that autonomously analyzes job postings and candidate profiles
to create tailored application documents using multi-step reasoning.
Author: Career Agent Team
Version: 1.2 - Modern UI Edition
"""
import os
import logging
import gradio as gr
from gradio.themes.soft import Soft
from gradio.themes import GoogleFont
from typing import Optional, Dict, Any
from dotenv import load_dotenv
from openai import OpenAI
import time
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Load environment variables
load_dotenv()
# === SambaNova API Integration ===
class SambaNovaClient:
"""SambaNova client using OpenAI SDK."""
def __init__(self, api_key: str, model_name: str = "Meta-Llama-3.3-70B-Instruct"):
self.client = OpenAI(
api_key=api_key,
base_url="https://api.sambanova.ai/v1"
)
self.model_name = model_name
def generate(self, prompt: str, system_prompt: str = "You are a helpful assistant.") -> str:
"""Generate response using SambaNova API."""
try:
response = self.client.chat.completions.create(
model=self.model_name,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
temperature=0.1,
top_p=0.1,
max_tokens=3000
)
return response.choices[0].message.content or ""
except Exception as e:
logger.error(f"SambaNova API call failed: {e}")
return f"❌ API Error: {str(e)}"
# === Analysis Functions ===
def analyze_candidate_profile(client: SambaNovaClient, user_info: str) -> str:
"""Analyzes the candidate's profile to extract insights and positioning."""
system_prompt = "You are a senior career strategist with expertise in talent assessment and professional positioning."
prompt = f"""
Analyze this candidate profile in detail:
{user_info}
Provide a comprehensive analysis including:
- Experience level and career stage assessment
- Core competencies and unique value propositions
- Industry expertise and domain knowledge
- Leadership capabilities and soft skills evidence
- Career trajectory and growth potential
- Strategic positioning opportunities
- Areas of competitive advantage
Format your response as a structured professional assessment with clear sections.
"""
return client.generate(prompt, system_prompt)
def research_job_posting(client: SambaNovaClient, job_description: str) -> str:
"""Analyzes the job posting for strategic application guidance."""
system_prompt = "You are an expert recruiter and talent acquisition specialist with deep knowledge of job market trends."
prompt = f"""
Conduct a comprehensive analysis of this job posting:
{job_description}
Extract and analyze:
- Must-have vs nice-to-have qualifications
- Technical skills and experience requirements breakdown
- Company culture indicators and values alignment
- Key responsibilities and success metrics
- Career growth and advancement signals
- Compensation and benefits analysis
- Critical ATS keywords and industry terminology
- Hiring urgency and competition level indicators
- Decision-maker priorities and pain points
Provide strategic intelligence for optimal application positioning.
"""
return client.generate(prompt, system_prompt)
def assess_fit_and_strategy(client: SambaNovaClient, profile_analysis: str, job_research: str) -> str:
"""Evaluates fit between candidate and job, develops application strategy."""
system_prompt = "You are a strategic career consultant specializing in job application optimization and candidate positioning."
prompt = f"""
Based on the candidate analysis and job research below, develop a comprehensive application strategy:
CANDIDATE ANALYSIS:
{profile_analysis}
JOB INTELLIGENCE:
{job_research}
Provide detailed strategic assessment:
- Overall fit percentage with detailed justification
- Top 5 strongest selling points to emphasize
- Potential concerns and mitigation strategies
- Unique value proposition development
- Competitive differentiation approach
- Key messaging themes and positioning
- Recommended application tone and style
- Interview preparation insights
"""
return client.generate(prompt, system_prompt)
def generate_strategic_resume(client: SambaNovaClient, candidate_info: str, strategy: str) -> str:
"""Generates a tailored, ATS-optimized resume."""
system_prompt = "You are a professional resume writer and ATS optimization expert with extensive experience in creating winning resumes."
prompt = f"""
Create a professional, ATS-optimized resume based on:
CANDIDATE INFORMATION:
{candidate_info}
STRATEGIC GUIDANCE:
{strategy}
Generate a complete resume with:
- Compelling professional summary (3-4 lines)
- Core competencies/skills section optimized for ATS
- Professional experience with quantified achievements
- Education and certifications
- Additional relevant sections (projects, awards, etc.)
Format professionally with clear sections and bullet points.
Focus on impact, metrics, and value delivery.
"""
return client.generate(prompt, system_prompt)
def create_cover_letter(client: SambaNovaClient, candidate_info: str, strategy: str, job_description: str) -> str:
"""Generates a persuasive, personalized cover letter."""
system_prompt = "You are an expert cover letter writer specializing in compelling, personalized application letters that get results."
prompt = f"""
Write a persuasive cover letter based on:
CANDIDATE INFORMATION:
{candidate_info}
STRATEGIC POSITIONING:
{strategy}
JOB DESCRIPTION:
{job_description}
Create an engaging cover letter that:
- Opens with a compelling hook that grabs attention
- Demonstrates specific research about the company/role
- Connects candidate experience to concrete value delivery
- Shows cultural fit and genuine enthusiasm
- Addresses potential concerns proactively
- Closes with a confident, action-oriented call-to-action
Keep it concise (3-4 paragraphs) but impactful.
"""
return client.generate(prompt, system_prompt)
def quality_assurance_check(client: SambaNovaClient, resume: str, cover_letter: str, job_posting: str) -> str:
"""Performs quality review of generated documents."""
system_prompt = "You are a senior HR professional and application reviewer with expertise in document quality assessment."
prompt = f"""
Perform a comprehensive quality review of these application materials:
RESUME:
{resume}
COVER LETTER:
{cover_letter}
ORIGINAL JOB POSTING:
{job_posting}
Evaluate and provide feedback on:
- ATS optimization and keyword alignment (score /10)
- Document consistency and professional presentation (score /10)
- Value proposition clarity and impact (score /10)
- Competitive differentiation strength (score /10)
- Overall application effectiveness (score /10)
Provide specific improvement recommendations and an overall quality assessment.
"""
return client.generate(prompt, system_prompt)
# === Main Career Agent Class ===
class CareerAgent:
"""Main Career Agent class handling AI-powered document generation."""
def __init__(self):
self.client = self._initialize_client()
def _initialize_client(self) -> SambaNovaClient:
"""Initialize SambaNova client."""
api_key = os.getenv("SAMBANOVA_API_KEY")
if not api_key:
raise ValueError("SAMBANOVA_API_KEY environment variable is required.")
return SambaNovaClient(api_key=api_key)
def process_application(self, user_info: str, job_description: str, progress=gr.Progress()) -> str:
"""Process the complete application with step-by-step analysis."""
if not user_info.strip() or not job_description.strip():
return "❌ Please provide both your profile and the job description."
try:
progress(0.1, desc="πŸ” Analyzing your profile...")
profile_analysis = analyze_candidate_profile(self.client, user_info)
time.sleep(0.5)
progress(0.3, desc="🎯 Researching job requirements...")
job_research = research_job_posting(self.client, job_description)
time.sleep(0.5)
progress(0.5, desc="🧠 Developing strategy...")
strategy = assess_fit_and_strategy(self.client, profile_analysis, job_research)
time.sleep(0.5)
progress(0.7, desc="πŸ“„ Generating tailored resume...")
resume = generate_strategic_resume(self.client, user_info, strategy)
time.sleep(0.5)
progress(0.85, desc="βœ‰οΈ Creating cover letter...")
cover_letter = create_cover_letter(self.client, user_info, strategy, job_description)
time.sleep(0.5)
progress(0.95, desc="βœ… Quality assurance check...")
qa_review = quality_assurance_check(self.client, resume, cover_letter, job_description)
progress(1.0, desc="πŸŽ‰ Complete!")
# Format final output
result = f"""
# πŸ“„ TAILORED RESUME
{resume}
---
# βœ‰οΈ PERSONALIZED COVER LETTER
{cover_letter}
---
# 🎯 STRATEGIC ANALYSIS
{strategy}
---
# βœ… QUALITY ASSURANCE REVIEW
{qa_review}
---
*Generated by SambaNova Llama 3.3 70B Instruct*
"""
logger.info("βœ… Application processing completed successfully!")
return result.strip()
except Exception as e:
logger.error(f"Processing error: {e}")
return f"""❌ **Error occurred:** {str(e)}
πŸ”§ **Troubleshooting Steps:**
- Verify your SAMBANOVA_API_KEY is valid
- Check your internet connection
- Ensure you have sufficient API credits
- Try again in a few moments
If the problem persists, please check your API key configuration."""
# === Modern Gradio Interface ===
class ModernCareerInterface:
def __init__(self, agent: CareerAgent):
self.agent = agent
def validate_api_key(self, api_key):
"""Validate API SambaNova API key"""
if not api_key or not api_key.strip():
return gr.HTML("""
<div class="api-status api-status-empty">
<div class="api-status-indicator"></div>
<span>πŸ” Enter your SambaNova API key to get started</span>
</div>
""")
try:
# Test simple de la clΓ© API
test_client = SambaNovaClient(api_key=api_key.strip())
return gr.HTML("""
<div class="api-status api-status-valid">
<div class="api-status-indicator"></div>
<span>βœ… API key validated successfully</span>
</div>
""")
except Exception as e:
return gr.HTML(f"""
<div class="api-status api-status-invalid">
<div class="api-status-indicator"></div>
<span>❌ API key validation failed: {str(e)[:50]}...</span>
</div>
""")
def create_interface(self) -> gr.Blocks:
"""Create a modern, beautiful dark interface."""
theme = Soft(
primary_hue="blue",
secondary_hue="slate",
font=GoogleFont("Inter")
).set(
body_background_fill="linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
block_background_fill="rgba(30, 41, 59, 0.95)", # Dark theme
block_border_width="1px",
block_border_color="rgba(148, 163, 184, 0.2)",
block_shadow="0 20px 25px -5px rgba(0, 0, 0, 0.25), 0 10px 10px -5px rgba(0, 0, 0, 0.1)",
block_radius="16px",
button_primary_background_fill="linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
button_primary_background_fill_hover="linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%)",
button_primary_text_color="white",
input_background_fill="rgba(51, 65, 85, 0.8)", # Dark input background
input_border_color="rgba(148, 163, 184, 0.3)",
input_border_width="1px",
input_radius="12px"
)
with gr.Blocks(theme=theme, title="πŸš€ AI Career Agent", css=self._dark_modern_css()) as demo:
# Hero Header
with gr.Row(elem_classes="hero-header"):
with gr.Column(scale=1):
gr.HTML("""
<div class="hero-content">
<div class="hero-icon">πŸš€</div>
<h1 class="hero-title">AI Career Agent</h1>
<p class="hero-subtitle">Generate winning resumes & cover letters in minutes</p>
<div class="hero-badges">
<span class="badge">✨ AI-Powered</span>
<span class="badge">🎯 ATS-Optimized</span>
<span class="badge">⚑ Lightning Fast</span>
</div>
</div>
""")
# Status Bar
status_bar = gr.HTML("""
<div class="status-bar status-ready">
<div class="status-indicator"></div>
<span>🟒 <strong>Ready</strong> - Llama 3.3 70B Model Loaded</span>
</div>
""")
# API Key Input Section
with gr.Row(elem_classes="api-key-section"):
with gr.Column():
gr.HTML('<div class="api-section-header">πŸ”‘ SambaNova API Configuration</div>')
with gr.Group(elem_classes="api-input-group"):
api_key_input = gr.Textbox(
label="SambaNova API Key",
placeholder="Enter your SambaNova API key here (sk-...)",
type="password",
value=os.getenv("SAMBANOVA_API_KEY", ""),
info="Your API key is used securely and not stored permanently. Get your free key at: https://sambanova.ai/",
elem_classes="api-key-input",
container=True,
show_label=True,
interactive=True
)
api_status = gr.HTML("""
<div class="api-status api-status-empty">
<div class="api-status-indicator"></div>
<span>πŸ” Enter your SambaNova API key to get started</span>
</div>
""")
# Main Content Area
with gr.Row(elem_classes="main-content"):
# Left Panel - Input
with gr.Column(scale=1, elem_classes="input-panel"):
gr.HTML('<div class="panel-header">πŸ“ Your Information</div>')
# Profile Input
with gr.Group(elem_classes="input-group"):
gr.HTML('<div class="input-label">πŸ‘€ Professional Profile</div>')
user_info = gr.Textbox(
placeholder="""Tell me about yourself! Include:
β€’ Your current role and experience level
β€’ Key skills and technologies you know
β€’ Notable achievements (with numbers/metrics)
β€’ Education and certifications
β€’ Career goals and interests
Example:
Senior Software Engineer with 5+ years experience in full-stack development. Expert in Python, React, and AWS. Led a team of 8 developers at TechCorp, increasing deployment efficiency by 40%. MBA in Technology Management from Stanford. Passionate about AI and machine learning...""",
lines=8,
max_lines=12,
container=False,
show_label=False,
elem_classes="modern-input"
)
# Job Description Input
with gr.Group(elem_classes="input-group"):
gr.HTML('<div class="input-label">πŸ“„ Target Job Description</div>')
job_description = gr.Textbox(
placeholder="""Paste the complete job posting here:
Include everything you can find:
β€’ Job title and company name
β€’ Required qualifications and skills
β€’ Job responsibilities and duties
β€’ Company culture information
β€’ Benefits and compensation details
β€’ Any other relevant information
The more details you provide, the better I can tailor your application!""",
lines=6,
max_lines=10,
container=False,
show_label=False,
elem_classes="modern-input"
)
# Action Buttons
with gr.Row(elem_classes="button-row"):
generate_btn = gr.Button(
"πŸš€ Generate My Application",
variant="primary",
size="lg",
elem_classes="generate-button"
)
clear_btn = gr.Button(
"πŸ—‘οΈ Clear All",
variant="secondary",
elem_classes="clear-button"
)
# Right Panel - Output
with gr.Column(scale=1, elem_classes="output-panel"):
gr.HTML('<div class="panel-header">πŸ“„ Generated Documents</div>')
# Output Display
with gr.Group(elem_classes="output-group"):
output = gr.Markdown(
value="### 🎯 Your personalized application materials will appear here\n\nClick **'Generate My Application'** to get started with your AI-powered resume and cover letter!",
elem_classes="output-display",
show_label=False,
container=False
)
# Tips Section
with gr.Row(elem_classes="tips-section"):
with gr.Column():
gr.HTML("""
<div class="tips-container">
<h3>πŸ’‘ Pro Tips for Best Results</h3>
<div class="tips-grid">
<div class="tip-card">
<div class="tip-icon">πŸ“Š</div>
<div class="tip-content">
<strong>Use Numbers</strong>
<p>Include specific metrics and achievements</p>
</div>
</div>
<div class="tip-card">
<div class="tip-icon">πŸ”‘</div>
<div class="tip-content">
<strong>Match Keywords</strong>
<p>Include relevant industry terminology</p>
</div>
</div>
<div class="tip-card">
<div class="tip-icon">πŸ“‹</div>
<div class="tip-content">
<strong>Complete Details</strong>
<p>Provide full job description for better matching</p>
</div>
</div>
<div class="tip-card">
<div class="tip-icon">✨</div>
<div class="tip-content">
<strong>Review & Customize</strong>
<p>Always personalize the final output</p>
</div>
</div>
</div>
</div>
""")
# Processing Steps Indicator
with gr.Row(elem_classes="steps-section", visible=False) as steps_row:
gr.HTML("""
<div class="steps-container">
<div class="steps-header">πŸ”„ Processing Steps</div>
<div class="steps-list">
<div class="step">
<div class="step-number">1</div>
<div class="step-text">Profile Analysis</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-text">Job Research</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-text">Strategy Development</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-text">Resume Generation</div>
</div>
<div class="step">
<div class="step-number">5</div>
<div class="step-text">Cover Letter</div>
</div>
<div class="step">
<div class="step-number">6</div>
<div class="step-text">Quality Review</div>
</div>
</div>
</div>
""")
# Event Handlers
def update_status_processing():
return gr.HTML("""
<div class="status-bar status-processing">
<div class="status-indicator processing"></div>
<span>πŸ”„ <strong>Processing...</strong> SambaNova Llama 3.3 70B is crafting your application</span>
</div>
""")
def update_status_complete():
return gr.HTML("""
<div class="status-bar status-complete">
<div class="status-indicator"></div>
<span>βœ… <strong>Complete!</strong> Your application materials are ready</span>
</div>
""")
def update_status_error():
return gr.HTML("""
<div class="status-bar status-error">
<div class="status-indicator"></div>
<span>❌ <strong>Error</strong> Please check your inputs and try again</span>
</div>
""")
def process_with_status(user_info, job_desc, api_key, progress=gr.Progress()):
try:
result = self.agent.process_application(user_info, job_desc, progress)
return result, update_status_complete(), gr.Row(visible=False)
except Exception as e:
error_msg = f"❌ **Error:** {str(e)}\n\nPlease check your API key and try again."
return error_msg, update_status_error(), gr.Row(visible=False)
def clear_all():
return (
"", # user_info
"", # job_description
"", # api_key_input - vider aussi la clΓ© API
"### 🎯 Your personalized application materials will appear here...",
gr.HTML("""<div class="status-bar status-ready">...</div>"""),
gr.Row(visible=False),
gr.HTML("""<div class="api-status api-status-empty">...</div>""") # Reset API status
)
# Button Events
generate_btn.click(
fn=lambda: (update_status_processing(), gr.Row(visible=True)),
outputs=[status_bar, steps_row]
).then(
fn=process_with_status,
inputs=[user_info, job_description, api_key_input], # Ajouter api_key_input
outputs=[output, status_bar, steps_row]
)
clear_btn.click(
fn=clear_all,
outputs=[user_info, job_description, api_key_input, output, status_bar, steps_row, api_status]
)
api_key_input.change(
fn=self.validate_api_key, # Notez le 'self.' ici
inputs=[api_key_input],
outputs=[api_status]
)
return demo
def _dark_modern_css(self) -> str:
"""Modern, beautiful dark theme CSS styling."""
return """
/* Global Styles */
.gradio-container {
max-width: 1600px !important;
margin: 0 auto !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
}
/* Hero Header - Kept beautiful and bright */
.hero-header {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
border-radius: 20px;
padding: 2rem;
margin-bottom: 2rem;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
}
.hero-content {
text-align: center;
color: white;
}
.hero-icon {
font-size: 4rem;
margin-bottom: 1rem;
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
}
.hero-title {
font-size: 3rem;
font-weight: 800;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.hero-subtitle {
font-size: 1.25rem;
color: rgba(255, 255, 255, 0.9);
margin: 0.5rem 0 1.5rem 0;
font-weight: 400;
}
.hero-badges {
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
}
.badge {
background: rgba(255, 255, 255, 0.15);
color: white;
padding: 0.5rem 1rem;
border-radius: 50px;
font-size: 0.875rem;
font-weight: 500;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
}
/* Status Bar */
.status-bar {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
border-radius: 12px;
margin-bottom: 2rem;
font-weight: 500;
transition: all 0.3s ease;
}
.status-ready {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 4px 6px -1px rgba(16, 185, 129, 0.25);
}
.status-processing {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
box-shadow: 0 4px 6px -1px rgba(245, 158, 11, 0.25);
}
.status-complete {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 4px 6px -1px rgba(16, 185, 129, 0.25);
}
.status-error {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
box-shadow: 0 4px 6px -1px rgba(239, 68, 68, 0.25);
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.8);
}
.status-indicator.processing {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Main Content - Dark Theme */
.main-content {
gap: 2rem !important;
}
.input-panel, .output-panel {
background: rgba(30, 41, 59, 0.95) !important;
border-radius: 20px;
padding: 2rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.25), 0 10px 10px -5px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(148, 163, 184, 0.2);
}
.panel-header {
font-size: 1.5rem;
font-weight: 700;
color: #f1f5f9 !important;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Input Groups - Dark Theme */
.input-group {
margin-bottom: 1.5rem;
}
.input-label {
font-size: 1.1rem;
font-weight: 600;
color: #e2e8f0 !important;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.modern-input textarea {
background: rgba(51, 65, 85, 0.8) !important;
border: 2px solid rgba(148, 163, 184, 0.3) !important;
border-radius: 12px !important;
padding: 1rem !important;
font-size: 0.95rem !important;
line-height: 1.6 !important;
color: #f1f5f9 !important;
transition: all 0.3s ease !important;
}
.modern-input textarea::placeholder {
color: #94a3b8 !important;
opacity: 0.8;
}
.modern-input textarea:focus {
border-color: #667eea !important;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2) !important;
background: rgba(51, 65, 85, 0.95) !important;
}
/* Buttons */
.button-row {
gap: 1rem !important;
margin-top: 2rem;
}
.generate-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
color: white !important;
font-weight: 600 !important;
padding: 0.75rem 2rem !important;
border-radius: 12px !important;
font-size: 1.1rem !important;
box-shadow: 0 4px 6px -1px rgba(102, 126, 234, 0.25) !important;
transition: all 0.3s ease !important;
}
.generate-button:hover {
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%) !important;
transform: translateY(-1px) !important;
box-shadow: 0 8px 12px -2px rgba(102, 126, 234, 0.35) !important;
}
.clear-button {
background: rgba(148, 163, 184, 0.15) !important;
border: 2px solid rgba(148, 163, 184, 0.3) !important;
color: #e2e8f0 !important;
font-weight: 500 !important;
padding: 0.75rem 1.5rem !important;
border-radius: 12px !important;
transition: all 0.3s ease !important;
}
.clear-button:hover {
background: rgba(148, 163, 184, 0.25) !important;
border-color: rgba(148, 163, 184, 0.4) !important;
}
/* Output - Dark Theme */
.output-group {
min-height: 400px;
}
.output-display {
background: rgba(51, 65, 85, 0.6) !important;
border: 1px solid rgba(148, 163, 184, 0.3) !important;
border-radius: 12px !important;
padding: 1.5rem !important;
min-height: 400px !important;
font-size: 0.95rem !important;
line-height: 1.6 !important;
color: #f1f5f9 !important;
}
.output-display h1, .output-display h2, .output-display h3,
.output-display h4, .output-display h5, .output-display h6 {
color: #f1f5f9 !important;
}
.output-display strong {
color: #e2e8f0 !important;
}
/* Tips Section - Dark Theme */
.tips-section {
margin-top: 3rem;
}
.tips-container {
background: rgba(30, 41, 59, 0.95) !important;
border-radius: 20px;
padding: 2rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.25), 0 10px 10px -5px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(148, 163, 184, 0.2);
}
.tips-container h3 {
font-size: 1.5rem;
font-weight: 700;
color: #f1f5f9 !important;
margin-bottom: 1.5rem;
text-align: center;
}
.tips-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
}
.tip-card {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1.5rem;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%) !important;
border-radius: 12px;
border: 1px solid rgba(102, 126, 234, 0.2);
transition: all 0.3s ease;
}
.tip-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px -5px rgba(102, 126, 234, 0.25);
border-color: rgba(102, 126, 234, 0.3);
background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(118, 75, 162, 0.15) 100%) !important;
}
.tip-icon {
font-size: 2rem;
min-width: 2rem;
}
.tip-content strong {
display: block;
font-weight: 600;
color: #f1f5f9 !important;
margin-bottom: 0.25rem;
}
.tip-content p {
margin: 0;
color: #cbd5e1 !important;
font-size: 0.9rem;
line-height: 1.4;
}
/* Steps Section - Dark Theme */
.steps-section {
margin-top: 2rem;
}
.steps-container {
background: rgba(30, 41, 59, 0.95) !important;
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(148, 163, 184, 0.2);
}
.steps-header {
font-size: 1.25rem;
font-weight: 600;
color: #f1f5f9 !important;
margin-bottom: 1.5rem;
text-align: center;
}
.steps-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 1rem;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
flex: 1;
min-width: 120px;
}
.step-number {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 1.1rem;
box-shadow: 0 4px 6px -1px rgba(102, 126, 234, 0.25);
}
.step-text {
font-size: 0.875rem;
font-weight: 500;
color: #e2e8f0 !important;
text-align: center;
line-height: 1.3;
}
/* Responsive Design */
@media (max-width: 768px) {
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
}
.main-content {
flex-direction: column;
}
.input-panel, .output-panel {
padding: 1.5rem;
}
.tips-grid {
grid-template-columns: 1fr;
}
.steps-list {
flex-direction: column;
align-items: center;
}
.step {
flex-direction: row;
justify-content: flex-start;
min-width: auto;
width: 100%;
max-width: 300px;
}
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.input-panel, .output-panel, .tips-container {
animation: fadeInUp 0.6s ease-out;
}
/* Custom Scrollbar - Dark Theme */
.output-display::-webkit-scrollbar {
width: 8px;
}
.output-display::-webkit-scrollbar-track {
background: rgba(51, 65, 85, 0.5);
border-radius: 4px;
}
.output-display::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 4px;
}
.output-display::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
}
/* Loading States */
.processing .generate-button {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
cursor: not-allowed !important;
}
.processing .generate-button:hover {
transform: none !important;
}
/* Focus States for Accessibility */
.modern-input textarea:focus,
.generate-button:focus,
.clear-button:focus {
outline: 2px solid #667eea !important;
outline-offset: 2px !important;
}
/* High Contrast Mode Support */
@media (prefers-contrast: high) {
.hero-header,
.input-panel,
.output-panel,
.tips-container,
.steps-container {
border: 2px solid #fff;
}
.modern-input textarea {
border: 2px solid #fff !important;
}
}
/* Reduced Motion Support */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Print Styles */
@media print {
.hero-header,
.tips-section,
.steps-section,
.button-row {
display: none !important;
}
.output-display {
border: none !important;
background: white !important;
color: black !important;
box-shadow: none !important;
}
}
.api-key-section {
margin: 1rem 0 2rem 0;
}
.api-section-header {
font-size: 1.25rem;
font-weight: 600;
color: #f1f5f9 !important;
margin-bottom: 1rem;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.api-input-group {
background: rgba(30, 41, 59, 0.95) !important;
border-radius: 16px;
padding: 1.5rem;
border: 1px solid rgba(148, 163, 184, 0.2);
backdrop-filter: blur(10px);
}
.api-key-input input {
background: rgba(51, 65, 85, 0.8) !important;
border: 2px solid rgba(148, 163, 184, 0.3) !important;
border-radius: 8px !important;
padding: 0.75rem !important;
color: #f1f5f9 !important;
font-family: 'Monaco', 'Menlo', monospace !important;
font-size: 0.9rem !important;
transition: all 0.3s ease !important;
}
.api-key-input input:focus {
border-color: #667eea !important;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2) !important;
background: rgba(51, 65, 85, 0.95) !important;
}
.api-key-input input::placeholder {
color: #94a3b8 !important;
opacity: 0.7;
}
.api-key-input label {
color: #e2e8f0 !important;
font-weight: 500 !important;
margin-bottom: 0.5rem !important;
}
.api-key-input .gr-form {
color: #cbd5e1 !important;
font-size: 0.875rem !important;
margin-top: 0.5rem !important;
}
.api-status {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 8px;
margin-top: 1rem;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.3s ease;
}
.api-status-empty {
background: linear-gradient(135deg, #64748b 0%, #475569 100%);
color: white;
}
.api-status-valid {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.api-status-invalid {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
}
.api-status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.8);
}
"""
# === Entry Point ===
def main():
"""Main entry point with enhanced error handling."""
try:
print("πŸš€ Initializing Modern Career Agent with SambaNova...")
# Check for API key
if not os.getenv("SAMBANOVA_API_KEY"):
print("❌ Error: SAMBANOVA_API_KEY environment variable not found!")
print("Please set your SambaNova API key:")
print("export SAMBANOVA_API_KEY='your-api-key-here'")
return
career_agent = CareerAgent()
print("βœ… SambaNova Llama 3.3 70B initialized successfully!")
interface = ModernCareerInterface(career_agent)
demo = interface.create_interface()
print("🌐 Launching Modern Gradio Interface...")
demo.launch(
share=False,
show_error=True,
favicon_path=None,
show_api=False
)
except Exception as e:
logger.error(f"Startup error: {e}")
print(f"❌ Startup Error: {str(e)}")
print("\nTroubleshooting:")
print("1. Check your SAMBANOVA_API_KEY is valid")
print("2. Ensure you have internet connection")
print("3. Verify all dependencies are installed: pip install openai gradio python-dotenv")
if __name__ == "__main__":
main()