Spaces:
Sleeping
Sleeping
""" | |
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() |