Spaces:
No application file
No application file
| #!/usr/bin/env python3 | |
| """ | |
| IQKiller - Simplified Complete Platform | |
| All core functionality with Apple-inspired UI, avoiding Gradio compatibility issues | |
| """ | |
| import gradio as gr | |
| import asyncio | |
| import time | |
| import json | |
| import re | |
| from typing import Dict, Any, Optional, Tuple | |
| # Configuration and API setup | |
| import os | |
| # Set up API keys from environment | |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") | |
| SERPAPI_KEY = os.getenv("SERPAPI_KEY") | |
| # PDF processing imports | |
| try: | |
| import PyPDF2 | |
| import pdfplumber | |
| PDF_AVAILABLE = True | |
| except ImportError: | |
| PDF_AVAILABLE = False | |
| # Import our modules with error handling | |
| try: | |
| from salary_negotiation_simulator import get_simulator, get_random_scenario, evaluate_scenario_answer | |
| negotiation_available = True | |
| except ImportError: | |
| negotiation_available = False | |
| try: | |
| from llm_client import get_llm_client | |
| llm_available = True | |
| except ImportError: | |
| llm_available = False | |
| # Import comprehensive interview guide generator | |
| try: | |
| from interview_guide_generator import ComprehensiveAnalyzer, format_interview_guide_html | |
| comprehensive_analyzer = ComprehensiveAnalyzer() | |
| comprehensive_available = True | |
| except ImportError: | |
| comprehensive_available = False | |
| comprehensive_analyzer = None | |
| # Import URL scraping functionality | |
| try: | |
| from micro.scrape import scrape_job_url, get_optimal_scraping_method | |
| scraping_available = True | |
| except ImportError: | |
| scraping_available = False | |
| # URL detection | |
| def is_url(text: str) -> bool: | |
| """Check if text is a URL""" | |
| import re | |
| url_pattern = re.compile( | |
| r'^https?://' # http:// or https:// | |
| r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain... | |
| r'localhost|' # localhost... | |
| r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip | |
| r'(?::\d+)?' # optional port | |
| r'(?:/?|[/?]\S+)$', re.IGNORECASE) | |
| return bool(url_pattern.match(text.strip())) | |
| # PDF text extraction | |
| def extract_text_from_pdf(pdf_file) -> str: | |
| """Extract text from uploaded PDF file""" | |
| if not PDF_AVAILABLE: | |
| return "β PDF processing not available. Please install PyPDF2 and pdfplumber." | |
| if pdf_file is None: | |
| return "" | |
| try: | |
| # Try with pdfplumber first (better text extraction) | |
| with pdfplumber.open(pdf_file.name) as pdf: | |
| text = "" | |
| for page in pdf.pages: | |
| page_text = page.extract_text() | |
| if page_text: | |
| text += page_text + "\n" | |
| if text.strip(): | |
| return text.strip() | |
| except Exception as e: | |
| print(f"Pdfplumber failed: {e}, trying PyPDF2...") | |
| try: | |
| # Fallback to PyPDF2 | |
| with open(pdf_file.name, 'rb') as file: | |
| pdf_reader = PyPDF2.PdfReader(file) | |
| text = "" | |
| for page_num in range(len(pdf_reader.pages)): | |
| page = pdf_reader.pages[page_num] | |
| page_text = page.extract_text() | |
| if page_text: | |
| text += page_text + "\n" | |
| return text.strip() | |
| except Exception as e: | |
| return f"β Failed to extract text from PDF: {str(e)}" | |
| def combine_resume_sources(resume_text: str, pdf_file) -> str: | |
| """Combine text from manual input and PDF upload""" | |
| combined_text = "" | |
| # Add manual text input | |
| if resume_text and resume_text.strip(): | |
| combined_text += resume_text.strip() + "\n\n" | |
| # Add PDF text if uploaded | |
| if pdf_file is not None: | |
| pdf_text = extract_text_from_pdf(pdf_file) | |
| if pdf_text and not pdf_text.startswith("β"): | |
| combined_text += "=== EXTRACTED FROM PDF ===\n" | |
| combined_text += pdf_text | |
| elif pdf_text.startswith("β"): | |
| return pdf_text # Return error message | |
| if not combined_text.strip(): | |
| return "" | |
| return combined_text.strip() | |
| # Apple-inspired CSS | |
| APPLE_CSS = """ | |
| /* === APPLE-INSPIRED DESIGN === */ | |
| :root { | |
| --apple-blue: #007AFF; | |
| --apple-blue-dark: #0051D5; | |
| --apple-gray: #8E8E93; | |
| --apple-light-gray: #F2F2F7; | |
| --apple-green: #34C759; | |
| --apple-orange: #FF9500; | |
| --apple-red: #FF3B30; | |
| --glass-bg: rgba(255, 255, 255, 0.1); | |
| --glass-border: rgba(255, 255, 255, 0.2); | |
| --shadow-soft: 0 8px 32px rgba(0, 0, 0, 0.1); | |
| --shadow-medium: 0 16px 64px rgba(0, 0, 0, 0.15); | |
| } | |
| .gradio-container { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| background: var(--glass-bg) !important; | |
| backdrop-filter: blur(20px) !important; | |
| -webkit-backdrop-filter: blur(20px) !important; | |
| border: 1px solid var(--glass-border) !important; | |
| border-radius: 20px !important; | |
| box-shadow: var(--shadow-medium) !important; | |
| margin: 20px !important; | |
| padding: 30px !important; | |
| } | |
| .main-header { | |
| text-align: center; | |
| margin-bottom: 40px; | |
| color: white; | |
| } | |
| .main-title { | |
| font-size: 3rem !important; | |
| font-weight: 700 !important; | |
| background: linear-gradient(45deg, #fff, #e0e0e0) !important; | |
| -webkit-background-clip: text !important; | |
| -webkit-text-fill-color: transparent !important; | |
| margin-bottom: 10px !important; | |
| } | |
| .glass-panel { | |
| background: var(--glass-bg) !important; | |
| backdrop-filter: blur(15px) !important; | |
| -webkit-backdrop-filter: blur(15px) !important; | |
| border: 1px solid var(--glass-border) !important; | |
| border-radius: 16px !important; | |
| box-shadow: var(--shadow-soft) !important; | |
| padding: 24px !important; | |
| margin: 16px 0 !important; | |
| } | |
| .gr-textbox, .gr-textarea { | |
| background: var(--glass-bg) !important; | |
| border: 1px solid var(--glass-border) !important; | |
| border-radius: 12px !important; | |
| color: white !important; | |
| backdrop-filter: blur(10px) !important; | |
| } | |
| .gr-button { | |
| background: var(--apple-blue) !important; | |
| border: none !important; | |
| border-radius: 12px !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| padding: 12px 24px !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .gr-button:hover { | |
| background: var(--apple-blue-dark) !important; | |
| transform: translateY(-2px) !important; | |
| } | |
| .result-card { | |
| background: var(--glass-bg) !important; | |
| border: 1px solid var(--glass-border) !important; | |
| border-radius: 16px !important; | |
| padding: 24px !important; | |
| margin: 16px 0 !important; | |
| backdrop-filter: blur(15px) !important; | |
| box-shadow: var(--shadow-soft) !important; | |
| } | |
| .match-score { | |
| font-size: 3rem !important; | |
| font-weight: 700 !important; | |
| text-align: center !important; | |
| background: linear-gradient(45deg, var(--apple-green), var(--apple-blue)) !important; | |
| -webkit-background-clip: text !important; | |
| -webkit-text-fill-color: transparent !important; | |
| } | |
| @keyframes slideInUp { | |
| from { opacity: 0; transform: translateY(30px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .slide-in { animation: slideInUp 0.6s ease-out; } | |
| html { scroll-behavior: smooth; } | |
| """ | |
| # Auto-scroll JavaScript | |
| AUTO_SCROLL_JS = """ | |
| function autoScrollToResults() { | |
| setTimeout(() => { | |
| const targets = [ | |
| document.querySelector('.result-card'), | |
| document.querySelector('.match-score'), | |
| document.querySelector('.glass-panel') | |
| ]; | |
| for (let target of targets) { | |
| if (target) { | |
| target.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| break; | |
| } | |
| } | |
| // Fallback: scroll to top | |
| setTimeout(() => window.scrollTo({ top: 0, behavior: 'smooth' }), 200); | |
| }, 500); | |
| return "Scrolling to results..."; | |
| } | |
| """ | |
| def create_status_display() -> str: | |
| """Create system status display""" | |
| openai_status = "π’" if OPENAI_API_KEY else "π΄" | |
| anthropic_status = "π’" if ANTHROPIC_API_KEY else "π‘" | |
| serp_status = "π’" if SERPAPI_KEY else "π‘" | |
| return f""" | |
| <div class="glass-panel" style="text-align: center; margin-bottom: 20px;"> | |
| <h3 style="color: white; margin-bottom: 15px;">π§ System Status</h3> | |
| <div style="display: flex; justify-content: space-around; flex-wrap: wrap;"> | |
| <div style="color: rgba(255,255,255,0.9); margin: 5px;"> | |
| {openai_status} OpenAI: {"Ready" if OPENAI_API_KEY else "Missing"} | |
| </div> | |
| <div style="color: rgba(255,255,255,0.9); margin: 5px;"> | |
| {anthropic_status} Anthropic: {"Ready" if ANTHROPIC_API_KEY else "Optional"} | |
| </div> | |
| <div style="color: rgba(255,255,255,0.9); margin: 5px;"> | |
| {serp_status} SerpAPI: {"Ready" if SERPAPI_KEY else "Optional"} | |
| </div> | |
| <div style="color: rgba(255,255,255,0.9); margin: 5px;"> | |
| π URL Scraping: {"Ready" if scraping_available else "Limited"} | |
| </div> | |
| <div style="color: rgba(255,255,255,0.9); margin: 5px;"> | |
| π― Negotiation: {"Ready" if negotiation_available else "Limited"} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def simple_resume_analysis(resume_text: str) -> dict: | |
| """Simple resume analysis with keyword extraction""" | |
| if not resume_text.strip(): | |
| return {"skills": [], "experience": 0, "roles": []} | |
| # Extract skills | |
| tech_skills = ["Python", "JavaScript", "Java", "SQL", "React", "Node.js", "AWS", "Docker", "Git"] | |
| soft_skills = ["Leadership", "Communication", "Project Management", "Team Work", "Problem Solving"] | |
| found_skills = [] | |
| for skill in tech_skills + soft_skills: | |
| if skill.lower() in resume_text.lower(): | |
| found_skills.append(skill) | |
| # Extract experience years | |
| experience_match = re.search(r'(\d+)[\s\+]*years?\s+(?:of\s+)?experience', resume_text, re.IGNORECASE) | |
| experience_years = int(experience_match.group(1)) if experience_match else 2 | |
| # Extract roles (simplified) | |
| role_keywords = ["engineer", "developer", "manager", "analyst", "scientist", "designer"] | |
| found_roles = [] | |
| for keyword in role_keywords: | |
| if keyword in resume_text.lower(): | |
| found_roles.append(keyword.title()) | |
| return { | |
| "skills": found_skills, | |
| "experience": experience_years, | |
| "roles": found_roles or ["Professional"] | |
| } | |
| async def smart_job_analysis(job_input: str) -> dict: | |
| """Smart job analysis with URL scraping support""" | |
| if not job_input.strip(): | |
| return {"company": "Unknown", "role": "Unknown", "required_skills": [], "location": "Remote", "source": "empty"} | |
| job_text = job_input.strip() | |
| source_info = {"source": "text"} | |
| # Check if input is a URL and scrape if available | |
| if is_url(job_text) and scraping_available: | |
| try: | |
| print(f"π Detected URL: {job_text}") | |
| print(f"π Scraping with optimal method...") | |
| # Get optimal scraping method for this URL | |
| method = get_optimal_scraping_method(job_text) | |
| print(f"π‘ Using {method} method for scraping") | |
| # Scrape the URL | |
| scrape_result = await scrape_job_url(job_text, prefer_method=method) | |
| if scrape_result.success and scrape_result.content: | |
| job_text = scrape_result.content | |
| source_info = { | |
| "source": "scraped", | |
| "url": job_input, # Store original URL | |
| "method": scrape_result.method, | |
| "processing_time": scrape_result.processing_time, | |
| "content_length": len(scrape_result.content), | |
| "scraped_text": scrape_result.content # Include scraped content | |
| } | |
| print(f"β Successfully scraped {len(job_text)} characters using {scrape_result.method}") | |
| else: | |
| print(f"β οΈ Scraping failed: {scrape_result.error}") | |
| print("π Falling back to treating input as job description text") | |
| job_text = job_input # Fallback to original input | |
| source_info["source"] = "text_fallback" | |
| except Exception as e: | |
| print(f"β Scraping error: {e}") | |
| job_text = job_input # Fallback to original input | |
| source_info["source"] = "text_fallback" | |
| # Extract company (enhanced patterns) | |
| company_patterns = [ | |
| r'at\s+([A-Z][a-zA-Z\s&\.]+?)(?:\s|$|,|\n)', | |
| r'([A-Z][a-zA-Z\s&\.]+?)\s+is\s+(?:hiring|looking)', | |
| r'join\s+([A-Z][a-zA-Z\s&\.]+?)(?:\s|$|,|\n)', | |
| r'company:\s*([A-Z][a-zA-Z\s&\.]+?)(?:\s|$|,|\n)', | |
| r'([A-Z][a-zA-Z\s&\.]+?)\s+(?:job|position|role)', | |
| # Common company patterns | |
| r'(spotify|google|amazon|microsoft|meta|apple|netflix|uber|airbnb)', | |
| ] | |
| company = "Unknown Company" | |
| for pattern in company_patterns: | |
| match = re.search(pattern, job_text, re.IGNORECASE) | |
| if match: | |
| company = match.group(1).strip() | |
| # Clean up common suffixes | |
| company = re.sub(r'\s+(is|has|we|the|a|an).*$', '', company, flags=re.IGNORECASE) | |
| break | |
| # Extract role (enhanced patterns) | |
| role_patterns = [ | |
| r'(senior\s+)?(data\s+scientist|software\s+engineer|product\s+manager|frontend\s+developer|backend\s+developer|full\s+stack|machine\s+learning\s+engineer|devops\s+engineer|site\s+reliability\s+engineer)', | |
| r'position[:\s]+(senior\s+)?([a-zA-Z\s]+)', | |
| r'role[:\s]+(senior\s+)?([a-zA-Z\s]+)', | |
| r'job\s+title[:\s]+(senior\s+)?([a-zA-Z\s]+)', | |
| r'we\'re\s+looking\s+for\s+(?:a\s+)?(senior\s+)?([a-zA-Z\s]+)', | |
| r'hiring\s+(?:a\s+)?(senior\s+)?([a-zA-Z\s]+)', | |
| ] | |
| role = "Unknown Role" | |
| seniority = "Mid-level" | |
| for pattern in role_patterns: | |
| match = re.search(pattern, job_text, re.IGNORECASE) | |
| if match: | |
| groups = match.groups() | |
| if len(groups) >= 2: | |
| senior_part = groups[0] or "" | |
| role_part = groups[1] or groups[-1] | |
| if "senior" in senior_part.lower(): | |
| seniority = "Senior" | |
| role = (senior_part + role_part).strip().title() | |
| break | |
| # Extract required skills (expanded) | |
| tech_skills = [ | |
| "Python", "JavaScript", "Java", "SQL", "React", "Node.js", "AWS", "Docker", "Git", | |
| "Machine Learning", "Data Science", "Analytics", "R", "Tableau", "Pandas", "NumPy", | |
| "TensorFlow", "PyTorch", "Kubernetes", "MongoDB", "PostgreSQL", "Redis", "Apache Spark", | |
| "Scala", "Go", "Rust", "TypeScript", "Vue.js", "Angular", "Django", "Flask", "Express", | |
| "GraphQL", "REST API", "Microservices", "CI/CD", "Jenkins", "Terraform", "Ansible" | |
| ] | |
| required_skills = [] | |
| for skill in tech_skills: | |
| if skill.lower() in job_text.lower(): | |
| required_skills.append(skill) | |
| # Extract location (enhanced) | |
| location = "Remote" | |
| location_patterns = [ | |
| r'location[:\s]+([a-zA-Z\s,]+)', | |
| r'based\s+in\s+([a-zA-Z\s,]+)', | |
| r'([a-zA-Z\s]+),\s*([A-Z]{2})', | |
| r'(remote|hybrid|on-site)', | |
| r'(san francisco|new york|seattle|austin|boston|chicago|los angeles|denver|atlanta|miami)', | |
| ] | |
| for pattern in location_patterns: | |
| match = re.search(pattern, job_text, re.IGNORECASE) | |
| if match: | |
| location = match.group(1).strip().title() | |
| break | |
| # Determine industry | |
| industry = "Technology" | |
| if any(keyword in job_text.lower() for keyword in ["spotify", "music", "streaming", "audio"]): | |
| industry = "Music & Entertainment" | |
| elif any(keyword in job_text.lower() for keyword in ["finance", "bank", "trading", "fintech"]): | |
| industry = "Finance" | |
| elif any(keyword in job_text.lower() for keyword in ["healthcare", "medical", "biotech", "pharma"]): | |
| industry = "Healthcare" | |
| elif any(keyword in job_text.lower() for keyword in ["retail", "e-commerce", "shopping"]): | |
| industry = "Retail & E-commerce" | |
| result = { | |
| "company": company, | |
| "role": role, | |
| "required_skills": required_skills, | |
| "location": location, | |
| "industry": industry, | |
| "seniority": seniority, | |
| **source_info | |
| } | |
| return result | |
| def simple_job_analysis(job_text: str) -> dict: | |
| """Legacy function - synchronous job analysis""" | |
| if not job_text.strip(): | |
| return {"company": "Unknown", "role": "Unknown", "required_skills": [], "location": "Remote", "source": "empty"} | |
| # Basic synchronous analysis (fallback) | |
| import re | |
| # Extract company (simple patterns) | |
| company_patterns = [ | |
| r'at\s+([A-Z][a-zA-Z\s&\.]+?)(?:\s|$|,|\n)', | |
| r'([A-Z][a-zA-Z\s&\.]+?)\s+is\s+(?:hiring|looking)', | |
| r'join\s+([A-Z][a-zA-Z\s&\.]+?)(?:\s|$|,|\n)', | |
| ] | |
| company = "Unknown Company" | |
| for pattern in company_patterns: | |
| match = re.search(pattern, job_text, re.IGNORECASE) | |
| if match: | |
| company = match.group(1).strip() | |
| break | |
| # Extract role | |
| role_patterns = [ | |
| r'(senior\s+)?(data\s+scientist|software\s+engineer|product\s+manager)', | |
| r'position[:\s]+(senior\s+)?([a-zA-Z\s]+)', | |
| r'role[:\s]+(senior\s+)?([a-zA-Z\s]+)', | |
| ] | |
| role = "Unknown Role" | |
| for pattern in role_patterns: | |
| match = re.search(pattern, job_text, re.IGNORECASE) | |
| if match: | |
| groups = match.groups() | |
| if len(groups) >= 2: | |
| senior_part = groups[0] or "" | |
| role_part = groups[1] or groups[-1] | |
| role = (senior_part + role_part).strip().title() | |
| break | |
| # Extract required skills | |
| tech_skills = ["Python", "JavaScript", "Java", "SQL", "React", "Node.js", "AWS", "Docker", "Git", "Machine Learning"] | |
| required_skills = [] | |
| for skill in tech_skills: | |
| if skill.lower() in job_text.lower(): | |
| required_skills.append(skill) | |
| return { | |
| "company": company, | |
| "role": role, | |
| "required_skills": required_skills, | |
| "location": "Remote", | |
| "industry": "Technology", | |
| "seniority": "Mid-level", | |
| "source": "text" | |
| } | |
| def calculate_match_score(resume_data: dict, job_data: dict) -> float: | |
| """Calculate compatibility match score""" | |
| resume_skills = set(skill.lower() for skill in resume_data["skills"]) | |
| job_skills = set(skill.lower() for skill in job_data["required_skills"]) | |
| if not job_skills: | |
| return 75.0 # Default score if no skills detected | |
| # Calculate skill overlap | |
| skill_overlap = len(resume_skills & job_skills) | |
| skill_score = (skill_overlap / len(job_skills)) * 100 if job_skills else 50 | |
| # Experience factor | |
| experience_score = min(resume_data["experience"] * 10, 100) | |
| # Combine scores | |
| final_score = (skill_score * 0.7) + (experience_score * 0.3) | |
| return min(max(final_score, 30), 95) # Ensure reasonable bounds | |
| async def analyze_job_compatibility(resume_text: str, job_input: str) -> Tuple[str, str, str]: | |
| """Quick analysis function (30 seconds)""" | |
| if not resume_text.strip(): | |
| return "β Please provide your resume text.", "", "" | |
| if not job_input.strip(): | |
| return "β Please provide a job URL or job description.", "", "" | |
| # Show processing indicator | |
| processing_html = """ | |
| <div class="glass-panel" style="text-align: center;"> | |
| <h3 style="color: white;">β‘ Quick Analysis...</h3> | |
| <div style="margin: 20px 0;"> | |
| <div style="display: inline-block; width: 60px; height: 60px; border: 4px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: #007AFF; animation: spin 1s linear infinite;"></div> | |
| </div> | |
| <p style="color: rgba(255,255,255,0.8);">Parsing resume β’ Analyzing job β’ Generating insights</p> | |
| </div> | |
| <style> | |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } | |
| </style> | |
| """ | |
| try: | |
| # Simulate processing time | |
| await asyncio.sleep(2) | |
| # Analyze resume and job (legacy - simple analysis only) | |
| resume_data = simple_resume_analysis(resume_text) | |
| job_data = simple_job_analysis(job_input) | |
| # Calculate match score | |
| match_score = calculate_match_score(resume_data, job_data) | |
| # Generate insights | |
| skill_matches = list(set(resume_data["skills"]) & set(job_data["required_skills"])) | |
| skill_gaps = list(set(job_data["required_skills"]) - set(resume_data["skills"])) | |
| # Create results HTML | |
| results_html = f""" | |
| <div class="result-card slide-in"> | |
| <div class="match-score">{match_score:.0f}%</div> | |
| <div style="text-align: center; color: rgba(255,255,255,0.8); font-size: 1.1rem; margin-bottom: 30px;"> | |
| Job Match Score | |
| </div> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 30px;"> | |
| <div> | |
| <h4 style="color: var(--apple-green); margin-bottom: 15px;">πͺ Your Strengths</h4> | |
| <ul style="color: rgba(255,255,255,0.9); line-height: 1.6;"> | |
| <li>{resume_data["experience"]} years of professional experience</li> | |
| <li>Skills in {', '.join(skill_matches[:3]) if skill_matches else 'various technologies'}</li> | |
| <li>Background in {', '.join(resume_data["roles"][:2])}</li> | |
| <li>Strong technical foundation</li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 style="color: var(--apple-orange); margin-bottom: 15px;">π― Areas to Address</h4> | |
| <ul style="color: rgba(255,255,255,0.9); line-height: 1.6;"> | |
| {"".join([f"<li>Consider learning {skill}</li>" for skill in skill_gaps[:3]]) if skill_gaps else "<li>Continue strengthening current skills</li>"} | |
| <li>Practice interview storytelling</li> | |
| <li>Research the company culture</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div style="margin-top: 30px;"> | |
| <h4 style="color: var(--apple-blue); margin-bottom: 15px;">π Interview Questions to Prepare</h4> | |
| <div style="color: rgba(255,255,255,0.9);"> | |
| <div style="margin-bottom: 10px; padding: 12px; background: var(--glass-bg); border-radius: 8px;"> | |
| <strong>Technical:</strong> Tell me about your experience with {skill_matches[0] if skill_matches else 'your main technology stack'} | |
| </div> | |
| <div style="margin-bottom: 10px; padding: 12px; background: var(--glass-bg); border-radius: 8px;"> | |
| <strong>Behavioral:</strong> Describe a challenging project you worked on and how you overcame obstacles | |
| </div> | |
| <div style="margin-bottom: 10px; padding: 12px; background: var(--glass-bg); border-radius: 8px;"> | |
| <strong>Experience:</strong> How do you handle working in a team environment? | |
| </div> | |
| <div style="margin-bottom: 10px; padding: 12px; background: var(--glass-bg); border-radius: 8px;"> | |
| <strong>Role-specific:</strong> What interests you about working at {job_data["company"]}? | |
| </div> | |
| </div> | |
| </div> | |
| <div style="margin-top: 30px;"> | |
| <h4 style="color: var(--apple-green); margin-bottom: 15px;">π° Salary Insights</h4> | |
| <div style="background: var(--glass-bg); padding: 16px; border-radius: 12px; color: rgba(255,255,255,0.9);"> | |
| <p><strong>Experience Level:</strong> {resume_data["experience"]} years qualifies for mid-level positions</p> | |
| <p><strong>Negotiation Tip:</strong> Highlight your {skill_matches[0] if skill_matches else 'technical'} skills and experience</p> | |
| <p><strong>Market Position:</strong> {"Strong" if match_score > 80 else "Good" if match_score > 60 else "Developing"} candidate profile</p> | |
| </div> | |
| </div> | |
| <div style="margin-top: 30px;"> | |
| <h4 style="color: white; margin-bottom: 15px;">π Next Steps</h4> | |
| <ul style="color: rgba(255,255,255,0.9); line-height: 1.6;"> | |
| <li>Practice answers to the suggested interview questions</li> | |
| <li>Research {job_data["company"]} company background and values</li> | |
| <li>Prepare specific examples using the STAR method</li> | |
| <li>{"Consider learning " + skill_gaps[0] if skill_gaps else "Continue strengthening your skill set"}</li> | |
| </ul> | |
| </div> | |
| <div style="margin-top: 20px; text-align: center; color: rgba(255,255,255,0.6); font-size: 0.9rem;"> | |
| Analysis completed β’ Confidence: {"High" if match_score > 80 else "Medium" if match_score > 60 else "Good"} | |
| </div> | |
| </div> | |
| """ | |
| # Create negotiation scenario if available | |
| negotiation_html = "" | |
| if negotiation_available: | |
| try: | |
| scenario = get_random_scenario() | |
| negotiation_html = f""" | |
| <div style="background: linear-gradient(135deg, var(--apple-orange), var(--apple-red)); color: white; border-radius: 16px; padding: 24px; margin: 16px 0; box-shadow: var(--shadow-medium);" class="slide-in"> | |
| <h3 style="margin-bottom: 20px;">πΌ Salary Negotiation Practice</h3> | |
| <h4 style="margin-bottom: 15px;">{scenario.title}</h4> | |
| <p style="margin-bottom: 20px; line-height: 1.6;">{scenario.situation}</p> | |
| <p style="font-weight: 600; margin-bottom: 20px;">{scenario.question}</p> | |
| <div style="margin-top: 15px; font-size: 0.9rem; opacity: 0.8;"> | |
| π‘ Practice different negotiation scenarios to improve your skills! | |
| <br>Difficulty: {scenario.difficulty} β’ Type: {scenario.type.value.replace('_', ' ').title()} | |
| </div> | |
| </div> | |
| """ | |
| except Exception: | |
| negotiation_html = """ | |
| <div style="background: var(--glass-bg); border-radius: 16px; padding: 24px; margin: 16px 0;" class="slide-in"> | |
| <h3 style="color: white; margin-bottom: 15px;">πΌ Salary Negotiation Tips</h3> | |
| <ul style="color: rgba(255,255,255,0.9); line-height: 1.6;"> | |
| <li>Research market rates for your role and experience level</li> | |
| <li>Prepare to articulate your value proposition</li> | |
| <li>Consider the full compensation package, not just base salary</li> | |
| <li>Practice negotiation scenarios with friends or mentors</li> | |
| </ul> | |
| </div> | |
| """ | |
| return results_html, negotiation_html, AUTO_SCROLL_JS | |
| except Exception as e: | |
| error_html = f""" | |
| <div class="result-card"> | |
| <h3 style="color: var(--apple-red);">β Analysis Error</h3> | |
| <p style="color: rgba(255,255,255,0.8);"> | |
| We encountered an issue: {str(e)} | |
| </p> | |
| <p style="color: rgba(255,255,255,0.6); font-size: 0.9rem;"> | |
| Please check your inputs and try again. | |
| </p> | |
| </div> | |
| """ | |
| return error_html, "", "" | |
| async def generate_comprehensive_guide(resume_text: str, job_input: str) -> Tuple[str, str, str]: | |
| """Generate comprehensive interview guide with URL scraping support""" | |
| if not resume_text.strip(): | |
| return "β Please provide your resume text.", "", "" | |
| if not job_input.strip(): | |
| return "β Please provide a job URL or job description.", "", "" | |
| # Show enhanced processing indicator | |
| is_url_input = is_url(job_input.strip()) | |
| processing_message = "π Scraping job posting β’ Analyzing resume β’ Generating comprehensive guide..." if is_url_input else "π Analyzing resume & job β’ Generating comprehensive guide..." | |
| processing_html = f""" | |
| <div class="glass-panel" style="text-align: center;"> | |
| <h3 style="color: white;">π― Creating Your Personalized Interview Guide...</h3> | |
| <div style="margin: 20px 0;"> | |
| <div style="display: inline-block; width: 60px; height: 60px; border: 4px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: #007AFF; animation: spin 1s linear infinite;"></div> | |
| </div> | |
| <p style="color: rgba(255,255,255,0.8);">{processing_message}</p> | |
| </div> | |
| <style> | |
| @keyframes spin {{ 0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }} }} | |
| </style> | |
| """ | |
| try: | |
| # Simulate processing time (longer for URL scraping) | |
| await asyncio.sleep(4 if is_url_input else 3) | |
| # Smart job analysis with URL scraping | |
| resume_data = simple_resume_analysis(resume_text) | |
| job_data = await smart_job_analysis(job_input) | |
| # Extract scraped content for comprehensive analysis | |
| scraped_content = job_input # default to original input | |
| if job_data.get("source") == "scraped" and "scraped_text" in job_data: | |
| scraped_content = job_data["scraped_text"] | |
| # Add scraping status to display | |
| scraping_status = "" | |
| if job_data.get("source") == "scraped": | |
| scraping_status = f""" | |
| <div style="background: var(--apple-green); color: white; padding: 10px; border-radius: 8px; margin: 10px 0; text-align: center;"> | |
| β Successfully scraped job posting using {job_data.get('method', 'unknown')} method | |
| ({job_data.get('content_length', 0)} characters in {job_data.get('processing_time', 0):.1f}s) | |
| </div> | |
| """ | |
| elif job_data.get("source") == "text_fallback": | |
| scraping_status = f""" | |
| <div style="background: var(--apple-orange); color: white; padding: 10px; border-radius: 8px; margin: 10px 0; text-align: center;"> | |
| β οΈ URL scraping failed, analyzing as text description | |
| </div> | |
| """ | |
| # Use comprehensive analyzer if available | |
| if comprehensive_available and comprehensive_analyzer: | |
| # Use scraped content if available, otherwise use original input | |
| guide = comprehensive_analyzer.generate_comprehensive_guide(resume_text, scraped_content) | |
| results_html = scraping_status + format_interview_guide_html(guide) | |
| else: | |
| # Fallback to enhanced simple analysis | |
| results_html = scraping_status + await generate_enhanced_simple_analysis(resume_text, job_input) | |
| # Create negotiation scenario if available | |
| negotiation_html = "" | |
| if negotiation_available: | |
| try: | |
| scenario = get_random_scenario() | |
| negotiation_html = f""" | |
| <div style="background: linear-gradient(135deg, var(--apple-orange), var(--apple-red)); color: white; border-radius: 16px; padding: 24px; margin: 16px 0; box-shadow: var(--shadow-medium);" class="slide-in"> | |
| <h3 style="margin-bottom: 20px;">πΌ Salary Negotiation Practice</h3> | |
| <h4 style="margin-bottom: 15px;">{scenario.title}</h4> | |
| <p style="margin-bottom: 20px; line-height: 1.6;">{scenario.situation}</p> | |
| <p style="font-weight: 600; margin-bottom: 20px;">{scenario.question}</p> | |
| <div style="margin-top: 15px; font-size: 0.9rem; opacity: 0.8;"> | |
| π‘ Practice different negotiation scenarios to improve your skills! | |
| <br>Difficulty: {scenario.difficulty} β’ Type: {scenario.type.value.replace('_', ' ').title()} | |
| </div> | |
| </div> | |
| """ | |
| except Exception: | |
| negotiation_html = """ | |
| <div style="background: var(--glass-bg); border-radius: 16px; padding: 24px; margin: 16px 0;" class="slide-in"> | |
| <h3 style="color: white; margin-bottom: 15px;">πΌ Salary Negotiation Tips</h3> | |
| <ul style="color: rgba(255,255,255,0.9); line-height: 1.6;"> | |
| <li>Research market rates for your role and experience level</li> | |
| <li>Prepare to articulate your value proposition</li> | |
| <li>Consider the full compensation package, not just base salary</li> | |
| <li>Practice negotiation scenarios with friends or mentors</li> | |
| </ul> | |
| </div> | |
| """ | |
| return results_html, negotiation_html, AUTO_SCROLL_JS | |
| except Exception as e: | |
| error_html = f""" | |
| <div class="result-card"> | |
| <h3 style="color: var(--apple-red);">β Analysis Error</h3> | |
| <p style="color: rgba(255,255,255,0.8);"> | |
| We encountered an issue: {str(e)} | |
| </p> | |
| <p style="color: rgba(255,255,255,0.6); font-size: 0.9rem;"> | |
| Please check your inputs and try again. | |
| </p> | |
| </div> | |
| """ | |
| return error_html, "", "" | |
| async def generate_enhanced_simple_analysis(resume_text: str, job_input: str) -> str: | |
| """Enhanced simple analysis as fallback""" | |
| resume_data = simple_resume_analysis(resume_text) | |
| job_data = simple_job_analysis(job_input) | |
| match_score = calculate_match_score(resume_data, job_data) | |
| # Generate comprehensive-style output with simple analysis | |
| return f""" | |
| <div class="result-card slide-in" style="max-width: 1200px; margin: 0 auto;"> | |
| <h1 style="color: white; text-align: center; margin-bottom: 20px;">Enhanced Interview Guide: {job_data['role']} at {job_data['company']}</h1> | |
| <div style="text-align: center; margin-bottom: 30px;"> | |
| <div style="font-size: 1.2rem; color: var(--apple-green); font-weight: 600; margin-bottom: 10px;"> | |
| Match Score: {"π’ Excellent Match" if match_score >= 85 else "π‘ Good Match" if match_score >= 70 else "π΄ Developing Match"} ({match_score:.1f}%) | |
| </div> | |
| </div> | |
| <h2 style="color: white; margin-bottom: 20px;">π Introduction</h2> | |
| <p style="color: rgba(255,255,255,0.9); line-height: 1.6; margin-bottom: 30px;"> | |
| This {job_data['role']} position at {job_data['company']} represents an excellent opportunity for someone with your background. | |
| With {resume_data['experience']} years of experience and skills in {', '.join(resume_data['skills'][:3]) if resume_data['skills'] else 'various technologies'}, | |
| you're well-positioned to contribute meaningfully to their team. Your technical foundation and experience make you a strong candidate for this role. | |
| </p> | |
| <h2 style="color: white; margin-bottom: 20px;">π― Skills Assessment</h2> | |
| <div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;"> | |
| <p style="color: rgba(255,255,255,0.9); margin-bottom: 15px;"> | |
| <strong>Your Strengths:</strong> {', '.join(list(set(resume_data['skills']) & set(job_data['required_skills']))[:5]) if set(resume_data['skills']) & set(job_data['required_skills']) else 'Technical foundation, problem-solving skills'} | |
| </p> | |
| <p style="color: rgba(255,255,255,0.9);"> | |
| <strong>Areas to Develop:</strong> {', '.join(list(set(job_data['required_skills']) - set(resume_data['skills']))[:3]) if set(job_data['required_skills']) - set(resume_data['skills']) else 'Continue strengthening existing skills'} | |
| </p> | |
| </div> | |
| <h2 style="color: white; margin-bottom: 20px;">π Interview Questions to Prepare</h2> | |
| <div style="margin-bottom: 30px;"> | |
| <div style="margin-bottom: 20px; padding: 16px; background: var(--glass-bg); border-radius: 12px; border-left: 4px solid var(--apple-blue);"> | |
| <h4 style="color: var(--apple-orange); margin-bottom: 10px;">Technical Question</h4> | |
| <p style="color: rgba(255,255,255,0.9);">Tell me about your experience with {list(set(resume_data['skills']) & set(job_data['required_skills']))[0] if set(resume_data['skills']) & set(job_data['required_skills']) else 'your main technology stack'}.</p> | |
| </div> | |
| <div style="margin-bottom: 20px; padding: 16px; background: var(--glass-bg); border-radius: 12px; border-left: 4px solid var(--apple-green);"> | |
| <h4 style="color: var(--apple-orange); margin-bottom: 10px;">Behavioral Question</h4> | |
| <p style="color: rgba(255,255,255,0.9);">Describe a challenging project you worked on and how you overcame obstacles.</p> | |
| </div> | |
| <div style="margin-bottom: 20px; padding: 16px; background: var(--glass-bg); border-radius: 12px; border-left: 4px solid var(--apple-orange);"> | |
| <h4 style="color: var(--apple-orange); margin-bottom: 10px;">Company Question</h4> | |
| <p style="color: rgba(255,255,255,0.9);">What interests you about working at {job_data['company']}?</p> | |
| </div> | |
| </div> | |
| <h2 style="color: white; margin-bottom: 20px;">π Preparation Strategy</h2> | |
| <div style="background: var(--glass-bg); padding: 20px; border-radius: 12px; margin-bottom: 30px;"> | |
| <ul style="color: rgba(255,255,255,0.9); line-height: 1.6;"> | |
| <li>Research {job_data['company']} company background and recent developments</li> | |
| <li>Prepare specific examples using the STAR method (Situation, Task, Action, Result)</li> | |
| <li>Practice explaining your technical experience clearly</li> | |
| <li>Prepare thoughtful questions about the role and team</li> | |
| </ul> | |
| </div> | |
| <div style="text-align: center; margin-top: 30px; color: rgba(255,255,255,0.6); font-size: 0.9rem;"> | |
| <p><em>Enhanced analysis completed β’ Your match score of {match_score:.1f}% indicates {"strong" if match_score >= 80 else "good" if match_score >= 60 else "developing"} alignment</em></p> | |
| </div> | |
| </div> | |
| """ | |
| def create_main_interface(): | |
| """Create the main Gradio interface""" | |
| with gr.Blocks( | |
| css=APPLE_CSS, | |
| title="IQKiller - AI Interview Prep" | |
| ) as demo: | |
| # Header | |
| gr.HTML(""" | |
| <div class="main-header"> | |
| <h1 class="main-title">π― IQKiller</h1> | |
| <p style="color: rgba(255, 255, 255, 0.8); font-size: 1.2rem; margin-bottom: 10px;"> | |
| AI-Powered Interview Preparation Platform | |
| </p> | |
| <p style="color: rgba(255, 255, 255, 0.6); font-size: 0.9rem;"> | |
| π URL Scraping β’ π Comprehensive Guides β’ πΌ Salary Negotiation Training | |
| </p> | |
| </div> | |
| """) | |
| # System Status | |
| gr.HTML(create_status_display()) | |
| # Main Interface | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.HTML(""" | |
| <div class="glass-panel"> | |
| <h3 style="color: white; margin-bottom: 20px;">π Your Resume</h3> | |
| </div> | |
| """) | |
| resume_input = gr.Textbox( | |
| label="", | |
| placeholder="Paste your resume text here...\n\nInclude your experience, skills, education, and achievements.\n\nExample:\n- 5 years software engineering experience\n- Skills: Python, JavaScript, SQL\n- Led team of 3 developers\n- Built scalable applications\n\nπ‘ Have a PDF resume? Use the pdf_upload_tool.py script to extract text first!", | |
| lines=12, | |
| max_lines=20 | |
| ) | |
| with gr.Column(scale=1): | |
| gr.HTML(""" | |
| <div class="glass-panel"> | |
| <h3 style="color: white; margin-bottom: 20px;">πΌ Job Opportunity</h3> | |
| </div> | |
| """) | |
| job_input = gr.Textbox( | |
| label="", | |
| placeholder="π Paste any job URL for automatic scraping:\nβ’ https://linkedin.com/jobs/view/123456\nβ’ https://jobs.lever.co/company/role-id\nβ’ https://apply.workable.com/company/...\n\nπ Or paste the full job description text:\nβ’ Company name and role\nβ’ Required skills and experience \nβ’ Responsibilities and requirements\n\nβ¨ URL scraping provides the most comprehensive analysis!", | |
| lines=12, | |
| max_lines=20 | |
| ) | |
| # Single Action Button | |
| with gr.Row(): | |
| guide_btn = gr.Button( | |
| "π― Generate My Personalized Interview Guide", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| # Results Section | |
| results_output = gr.HTML(label="") | |
| negotiation_output = gr.HTML(label="") | |
| scroll_js = gr.HTML(visible=False) | |
| # Event handler for comprehensive guide generation | |
| guide_btn.click( | |
| fn=lambda r, j: asyncio.run(generate_comprehensive_guide(r, j)), | |
| inputs=[resume_input, job_input], | |
| outputs=[results_output, negotiation_output, scroll_js] | |
| ) | |
| # Footer | |
| gr.HTML(""" | |
| <div style="text-align: center; margin-top: 40px; color: rgba(255,255,255,0.6);"> | |
| <p>π― Built for job seekers who want to ace their interviews</p> | |
| <p style="font-size: 0.8rem;">IQKiller v2.0 β’ URL Scraping β’ Comprehensive Guides β’ Zero data retention</p> | |
| </div> | |
| """) | |
| return demo | |
| def main(): | |
| """Main function to launch the IQKiller platform""" | |
| print("π― IQKiller - Simplified Complete Platform") | |
| print("=" * 50) | |
| # Check API key status | |
| if not OPENAI_API_KEY: | |
| print("β οΈ OpenAI API key not found - using simplified analysis") | |
| else: | |
| print("β OpenAI API key configured") | |
| if ANTHROPIC_API_KEY: | |
| print("β Anthropic API key configured") | |
| if SERPAPI_KEY: | |
| print("β SerpAPI key configured") | |
| print(f"β URL Scraping: {'Ready' if scraping_available else 'Limited mode'}") | |
| print(f"β Negotiation simulator: {'Ready' if negotiation_available else 'Simplified mode'}") | |
| print(f"β LLM client: {'Ready' if llm_available else 'Simplified mode'}") | |
| print(f"β Comprehensive guides: {'Ready' if comprehensive_available else 'Basic mode'}") | |
| print("\nπ Starting IQKiller Platform...") | |
| print("π Open your browser to: http://localhost:7860") | |
| print("π‘ Paste any job URL for automatic scraping and comprehensive analysis!") | |
| print("=" * 50) | |
| # Create and launch | |
| demo = create_main_interface() | |
| try: | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True, | |
| quiet=False | |
| ) | |
| except Exception as e: | |
| print(f"β Failed to launch: {e}") | |
| print("π οΈ Try using a different port: python3 simple_iqkiller.py") | |
| if __name__ == "__main__": | |
| main() |