|
|
|
""" |
|
LinkedIn Profile Enhancer - Gradio Interface (app2.py) |
|
A beautiful web interface for the LinkedIn Profile Enhancer using Gradio |
|
""" |
|
|
|
import sys |
|
import os |
|
import time |
|
import json |
|
from typing import Dict, Any, Tuple, Optional |
|
import gradio as gr |
|
from PIL import Image |
|
import requests |
|
from io import BytesIO |
|
|
|
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
|
|
|
from agents.orchestrator import ProfileOrchestrator |
|
from agents.scraper_agent import ScraperAgent |
|
from agents.analyzer_agent import AnalyzerAgent |
|
from agents.content_agent import ContentAgent |
|
|
|
class LinkedInEnhancerGradio: |
|
"""Gradio # Action Buttons - Single Enhanced Button |
|
with gr.Row(): |
|
enhance_btn = gr.Button("π Enhance LinkedIn Profile", variant="primary", size="lg") # Action Buttons - Single Enhanced Button with Download |
|
with gr.Row(): |
|
enhance_btn = gr.Button("π Enhance LinkedIn Profile", variant="primary", size="lg")rface for LinkedIn Profile Enhancer""" |
|
|
|
def __init__(self): |
|
self.orchestrator = ProfileOrchestrator() |
|
self.current_profile_data = None |
|
self.current_analysis = None |
|
self.current_suggestions = None |
|
|
|
def test_api_connections(self) -> Tuple[str, str]: |
|
"""Test API connections and return status""" |
|
apify_status = "β Failed" |
|
openai_status = "β Failed" |
|
|
|
try: |
|
scraper = ScraperAgent() |
|
if scraper.test_apify_connection(): |
|
apify_status = "β
Connected" |
|
except Exception as e: |
|
apify_status = f"β Error: {str(e)[:50]}..." |
|
|
|
try: |
|
content_agent = ContentAgent() |
|
if content_agent.test_openai_connection(): |
|
openai_status = "β
Connected" |
|
except Exception as e: |
|
openai_status = f"β Error: {str(e)[:50]}..." |
|
|
|
return apify_status, openai_status |
|
|
|
def load_profile_image(self, image_url: str) -> Optional[Image.Image]: |
|
"""Load profile image from URL""" |
|
try: |
|
if image_url: |
|
response = requests.get(image_url, timeout=10) |
|
if response.status_code == 200: |
|
return Image.open(BytesIO(response.content)) |
|
except Exception as e: |
|
print(f"Error loading image: {e}") |
|
return None |
|
|
|
def enhance_linkedin_profile(self, linkedin_url: str, job_description: str = "") -> Tuple[str, str, str, str, str, str, str, str, Optional[Image.Image]]: |
|
"""Complete LinkedIn profile enhancement with extraction, analysis, and suggestions""" |
|
if not linkedin_url.strip(): |
|
return "β Error", "Please enter a LinkedIn profile URL", "", "", "", "", "", "", None |
|
|
|
if not any(pattern in linkedin_url.lower() for pattern in ['linkedin.com/in/', 'www.linkedin.com/in/']): |
|
return "β Error", "Please enter a valid LinkedIn profile URL", "", "", "", "", "", "", None |
|
|
|
try: |
|
|
|
self.orchestrator.memory.session_data.clear() |
|
profile_data = self.orchestrator.scraper.extract_profile_data(linkedin_url) |
|
self.current_profile_data = profile_data |
|
|
|
|
|
basic_info = f""" |
|
**Name:** {profile_data.get('name', 'N/A')} |
|
**Headline:** {profile_data.get('headline', 'N/A')} |
|
**Location:** {profile_data.get('location', 'N/A')} |
|
**Connections:** {profile_data.get('connections', 'N/A')} |
|
**Followers:** {profile_data.get('followers', 'N/A')} |
|
**Email:** {profile_data.get('email', 'N/A')} |
|
**Current Job:** {profile_data.get('job_title', 'N/A')} at {profile_data.get('company_name', 'N/A')} |
|
""" |
|
|
|
|
|
about_section = profile_data.get('about', 'No about section available') |
|
|
|
|
|
experience_text = "" |
|
for i, exp in enumerate(profile_data.get('experience', [])[:5], 1): |
|
experience_text += f""" |
|
**{i}. {exp.get('title', 'Position')}** |
|
- Company: {exp.get('company', 'N/A')} |
|
- Duration: {exp.get('duration', 'N/A')} |
|
- Location: {exp.get('location', 'N/A')} |
|
- Current: {'Yes' if exp.get('is_current') else 'No'} |
|
""" |
|
if exp.get('description'): |
|
experience_text += f"- Description: {exp.get('description')[:200]}...\n" |
|
experience_text += "\n" |
|
|
|
|
|
education_text = "" |
|
for i, edu in enumerate(profile_data.get('education', []), 1): |
|
education_text += f""" |
|
**{i}. {edu.get('school', 'School')}** |
|
- Degree: {edu.get('degree', 'N/A')} |
|
- Field: {edu.get('field', 'N/A')} |
|
- Year: {edu.get('year', 'N/A')} |
|
- Grade: {edu.get('grade', 'N/A')} |
|
|
|
""" |
|
|
|
skills_text = ", ".join(profile_data.get('skills', [])[:20]) |
|
if len(profile_data.get('skills', [])) > 20: |
|
skills_text += f" ... and {len(profile_data.get('skills', [])) - 20} more" |
|
|
|
details_text = f""" |
|
## π Education |
|
{education_text if education_text else "No education information available"} |
|
|
|
## π οΈ Skills |
|
{skills_text if skills_text else "No skills information available"} |
|
|
|
## π Certifications |
|
{len(profile_data.get('certifications', []))} certifications found |
|
|
|
## π Additional Data |
|
- Projects: {len(profile_data.get('projects', []))} |
|
- Publications: {len(profile_data.get('publications', []))} |
|
- Recommendations: {len(profile_data.get('recommendations', []))} |
|
""" |
|
|
|
|
|
profile_image = self.load_profile_image(profile_data.get('profile_image_hq') or profile_data.get('profile_image')) |
|
|
|
|
|
try: |
|
analysis = self.orchestrator.analyzer.analyze_profile( |
|
self.current_profile_data, |
|
job_description |
|
) |
|
self.current_analysis = analysis |
|
|
|
|
|
analysis_text = f""" |
|
## π Analysis Results |
|
|
|
**Overall Rating:** {analysis.get('overall_rating', 'Unknown')} |
|
**Completeness Score:** {analysis.get('completeness_score', 0):.1f}% |
|
**Job Match Score:** {analysis.get('job_match_score', 0):.1f}% |
|
|
|
### π Strengths |
|
""" |
|
for strength in analysis.get('strengths', []): |
|
analysis_text += f"- {strength}\n" |
|
|
|
analysis_text += "\n### β οΈ Areas for Improvement\n" |
|
for weakness in analysis.get('weaknesses', []): |
|
analysis_text += f"- {weakness}\n" |
|
|
|
|
|
keyword_analysis = analysis.get('keyword_analysis', {}) |
|
keywords_text = "" |
|
if keyword_analysis: |
|
found_keywords = keyword_analysis.get('found_keywords', []) |
|
missing_keywords = keyword_analysis.get('missing_keywords', []) |
|
|
|
keywords_text = f""" |
|
## π Keyword Analysis |
|
|
|
**Found Keywords:** {', '.join(found_keywords[:10])} |
|
{"..." if len(found_keywords) > 10 else ""} |
|
|
|
**Missing Keywords:** {', '.join(missing_keywords[:5])} |
|
{"..." if len(missing_keywords) > 5 else ""} |
|
""" |
|
except Exception as e: |
|
analysis_text = f"β οΈ Analysis failed: {str(e)}" |
|
keywords_text = "" |
|
|
|
|
|
try: |
|
suggestions = self.orchestrator.content_generator.generate_suggestions( |
|
self.current_analysis, |
|
job_description |
|
) |
|
self.current_suggestions = suggestions |
|
|
|
suggestions_text = "" |
|
|
|
for category, items in suggestions.items(): |
|
if category == 'ai_generated_content': |
|
ai_content = items if isinstance(items, dict) else {} |
|
|
|
|
|
if 'ai_headlines' in ai_content and ai_content['ai_headlines']: |
|
suggestions_text += "## β¨ Professional Headlines\n\n" |
|
for i, headline in enumerate(ai_content['ai_headlines'], 1): |
|
cleaned_headline = headline.strip('"').replace('\\"', '"') |
|
if cleaned_headline.startswith(('1.', '2.', '3.', '4.', '5.')): |
|
cleaned_headline = cleaned_headline[2:].strip() |
|
suggestions_text += f"{i}. {cleaned_headline}\n\n" |
|
|
|
|
|
if 'ai_about_section' in ai_content and ai_content['ai_about_section']: |
|
suggestions_text += "## π Enhanced About Section\n\n" |
|
suggestions_text += f"```\n{ai_content['ai_about_section']}\n```\n\n" |
|
|
|
|
|
if 'ai_experience_descriptions' in ai_content and ai_content['ai_experience_descriptions']: |
|
suggestions_text += "## πΌ Experience Description Ideas\n\n" |
|
for desc in ai_content['ai_experience_descriptions']: |
|
suggestions_text += f"- {desc}\n" |
|
suggestions_text += "\n" |
|
else: |
|
|
|
category_name = category.replace('_', ' ').title() |
|
suggestions_text += f"## π {category_name}\n\n" |
|
if isinstance(items, list): |
|
for item in items: |
|
suggestions_text += f"- {item}\n" |
|
else: |
|
suggestions_text += f"- {items}\n" |
|
suggestions_text += "\n" |
|
except Exception as e: |
|
suggestions_text = f"β οΈ Suggestions generation failed: {str(e)}" |
|
|
|
return "β
Profile Enhanced Successfully", basic_info, about_section, experience_text, details_text, analysis_text, keywords_text, suggestions_text, profile_image |
|
|
|
except Exception as e: |
|
return "β Error", f"Failed to enhance profile: {str(e)}", "", "", "", "", "", "", None |
|
|
|
def analyze_profile(self, job_description: str = "") -> Tuple[str, str, str]: |
|
"""Analyze the extracted profile data""" |
|
if not self.current_profile_data: |
|
return "β Error", "Please extract profile data first", "" |
|
|
|
try: |
|
|
|
analysis = self.orchestrator.analyzer.analyze_profile( |
|
self.current_profile_data, |
|
job_description |
|
) |
|
self.current_analysis = analysis |
|
|
|
|
|
analysis_text = f""" |
|
## π Analysis Results |
|
|
|
**Overall Rating:** {analysis.get('overall_rating', 'Unknown')} |
|
**Completeness Score:** {analysis.get('completeness_score', 0):.1f}% |
|
**Job Match Score:** {analysis.get('job_match_score', 0):.1f}% |
|
|
|
### π Strengths |
|
""" |
|
for strength in analysis.get('strengths', []): |
|
analysis_text += f"- {strength}\n" |
|
|
|
analysis_text += "\n### οΏ½ Areas for Improvement\n" |
|
for weakness in analysis.get('weaknesses', []): |
|
analysis_text += f"- {weakness}\n" |
|
|
|
|
|
keyword_analysis = analysis.get('keyword_analysis', {}) |
|
keywords_text = "" |
|
if keyword_analysis: |
|
found_keywords = keyword_analysis.get('found_keywords', []) |
|
missing_keywords = keyword_analysis.get('missing_keywords', []) |
|
|
|
keywords_text = f""" |
|
## π Keyword Analysis |
|
|
|
**Found Keywords:** {', '.join(found_keywords[:10])} |
|
{"..." if len(found_keywords) > 10 else ""} |
|
|
|
**Missing Keywords:** {', '.join(missing_keywords[:5])} |
|
{"..." if len(missing_keywords) > 5 else ""} |
|
""" |
|
|
|
return "β
Success", analysis_text, keywords_text |
|
|
|
except Exception as e: |
|
return "β Error", f"Failed to analyze profile: {str(e)}", "" |
|
|
|
def generate_suggestions(self, job_description: str = "") -> Tuple[str, str]: |
|
"""Generate enhancement suggestions""" |
|
if not self.current_analysis: |
|
return "β Error", "Please analyze profile first" |
|
|
|
try: |
|
|
|
suggestions = self.orchestrator.content_generator.generate_suggestions( |
|
self.current_analysis, |
|
job_description |
|
) |
|
self.current_suggestions = suggestions |
|
|
|
suggestions_text = "" |
|
ai_content_text = "" |
|
|
|
for category, items in suggestions.items(): |
|
if category == 'ai_generated_content': |
|
ai_content = items if isinstance(items, dict) else {} |
|
|
|
|
|
if 'ai_headlines' in ai_content and ai_content['ai_headlines']: |
|
ai_content_text += "## β¨ Professional Headlines\n\n" |
|
for i, headline in enumerate(ai_content['ai_headlines'], 1): |
|
cleaned_headline = headline.strip('"').replace('\\"', '"') |
|
if cleaned_headline.startswith(('1.', '2.', '3.', '4.', '5.')): |
|
cleaned_headline = cleaned_headline[2:].strip() |
|
ai_content_text += f"{i}. {cleaned_headline}\n\n" |
|
|
|
|
|
if 'ai_about_section' in ai_content and ai_content['ai_about_section']: |
|
ai_content_text += "## οΏ½ Enhanced About Section\n\n" |
|
ai_content_text += f"```\n{ai_content['ai_about_section']}\n```\n\n" |
|
|
|
|
|
if 'ai_experience_descriptions' in ai_content and ai_content['ai_experience_descriptions']: |
|
ai_content_text += "## πΌ Experience Description Ideas\n\n" |
|
for desc in ai_content['ai_experience_descriptions']: |
|
ai_content_text += f"- {desc}\n" |
|
ai_content_text += "\n" |
|
else: |
|
|
|
category_name = category.replace('_', ' ').title() |
|
suggestions_text += f"## π {category_name}\n\n" |
|
if isinstance(items, list): |
|
for item in items: |
|
suggestions_text += f"- {item}\n" |
|
else: |
|
suggestions_text += f"- {items}\n" |
|
suggestions_text += "\n" |
|
|
|
return "β
Success", suggestions_text + ai_content_text |
|
|
|
except Exception as e: |
|
return "β Error", f"Failed to generate suggestions: {str(e)}" |
|
|
|
def export_results(self, linkedin_url: str) -> str: |
|
"""Export all results to a comprehensive downloadable file""" |
|
if not self.current_profile_data: |
|
return None |
|
|
|
try: |
|
|
|
profile_name = linkedin_url.split('/in/')[-1].split('/')[0] if linkedin_url else 'profile' |
|
timestamp = time.strftime('%Y%m%d_%H%M%S') |
|
filename = f"LinkedIn_Profile_Enhancement_{profile_name}_{timestamp}.md" |
|
|
|
|
|
file_path = os.path.join(os.getcwd(), filename) |
|
|
|
|
|
content = f"""# π LinkedIn Profile Enhancement Report |
|
|
|
**Generated:** {time.strftime('%B %d, %Y at %I:%M %p')} |
|
**Profile URL:** [{linkedin_url}]({linkedin_url}) |
|
**Enhancement Date:** {time.strftime('%Y-%m-%d')} |
|
|
|
--- |
|
|
|
## π Executive Summary |
|
|
|
This comprehensive report provides a detailed analysis of your LinkedIn profile along with AI-powered enhancement suggestions to improve your professional visibility and job match potential. |
|
|
|
--- |
|
|
|
## π€ Basic Profile Information |
|
|
|
| Field | Current Value | |
|
|-------|---------------| |
|
| **Name** | {self.current_profile_data.get('name', 'N/A')} | |
|
| **Professional Headline** | {self.current_profile_data.get('headline', 'N/A')} | |
|
| **Location** | {self.current_profile_data.get('location', 'N/A')} | |
|
| **Connections** | {self.current_profile_data.get('connections', 'N/A')} | |
|
| **Followers** | {self.current_profile_data.get('followers', 'N/A')} | |
|
| **Email** | {self.current_profile_data.get('email', 'N/A')} | |
|
| **Current Position** | {self.current_profile_data.get('job_title', 'N/A')} at {self.current_profile_data.get('company_name', 'N/A')} | |
|
|
|
--- |
|
|
|
## π Current About Section |
|
|
|
``` |
|
{self.current_profile_data.get('about', 'No about section available')} |
|
``` |
|
|
|
--- |
|
|
|
## πΌ Professional Experience |
|
|
|
""" |
|
|
|
for i, exp in enumerate(self.current_profile_data.get('experience', []), 1): |
|
content += f""" |
|
### {i}. {exp.get('title', 'Position')} |
|
**Company:** {exp.get('company', 'N/A')} |
|
**Duration:** {exp.get('duration', 'N/A')} |
|
**Location:** {exp.get('location', 'N/A')} |
|
**Current Role:** {'Yes' if exp.get('is_current') else 'No'} |
|
|
|
""" |
|
if exp.get('description'): |
|
content += f"**Description:**\n```\n{exp.get('description')}\n```\n\n" |
|
|
|
|
|
content += "---\n\n## π Education\n\n" |
|
for i, edu in enumerate(self.current_profile_data.get('education', []), 1): |
|
content += f""" |
|
### {i}. {edu.get('school', 'School')} |
|
- **Degree:** {edu.get('degree', 'N/A')} |
|
- **Field of Study:** {edu.get('field', 'N/A')} |
|
- **Year:** {edu.get('year', 'N/A')} |
|
- **Grade:** {edu.get('grade', 'N/A')} |
|
|
|
""" |
|
|
|
|
|
skills = self.current_profile_data.get('skills', []) |
|
content += f"""--- |
|
|
|
## π οΈ Skills & Expertise |
|
|
|
**Total Skills Listed:** {len(skills)} |
|
|
|
""" |
|
if skills: |
|
|
|
skills_per_line = 5 |
|
for i in range(0, len(skills), skills_per_line): |
|
skill_group = skills[i:i+skills_per_line] |
|
content += f"- {' β’ '.join(skill_group)}\n" |
|
|
|
|
|
content += f""" |
|
--- |
|
|
|
## π Additional Profile Data |
|
|
|
| Category | Count | |
|
|----------|-------| |
|
| **Certifications** | {len(self.current_profile_data.get('certifications', []))} | |
|
| **Projects** | {len(self.current_profile_data.get('projects', []))} | |
|
| **Publications** | {len(self.current_profile_data.get('publications', []))} | |
|
| **Recommendations** | {len(self.current_profile_data.get('recommendations', []))} | |
|
|
|
""" |
|
|
|
|
|
if self.current_analysis: |
|
content += f"""--- |
|
|
|
## π AI Analysis Results |
|
|
|
### Overall Assessment |
|
- **Overall Rating:** {self.current_analysis.get('overall_rating', 'Unknown')} |
|
- **Profile Completeness:** {self.current_analysis.get('completeness_score', 0):.1f}% |
|
- **Job Match Score:** {self.current_analysis.get('job_match_score', 0):.1f}% |
|
|
|
### π Identified Strengths |
|
""" |
|
for strength in self.current_analysis.get('strengths', []): |
|
content += f"- {strength}\n" |
|
|
|
content += "\n### β οΈ Areas for Improvement\n" |
|
for weakness in self.current_analysis.get('weaknesses', []): |
|
content += f"- {weakness}\n" |
|
|
|
|
|
keyword_analysis = self.current_analysis.get('keyword_analysis', {}) |
|
if keyword_analysis: |
|
found_keywords = keyword_analysis.get('found_keywords', []) |
|
missing_keywords = keyword_analysis.get('missing_keywords', []) |
|
|
|
content += f""" |
|
### π Keyword Analysis |
|
|
|
**Found Keywords ({len(found_keywords)}):** {', '.join(found_keywords[:15])} |
|
{"..." if len(found_keywords) > 15 else ""} |
|
|
|
**Missing Keywords ({len(missing_keywords)}):** {', '.join(missing_keywords[:10])} |
|
{"..." if len(missing_keywords) > 10 else ""} |
|
""" |
|
|
|
|
|
if self.current_suggestions: |
|
content += "\n---\n\n## π‘ AI-Powered Enhancement Suggestions\n\n" |
|
|
|
for category, items in self.current_suggestions.items(): |
|
if category == 'ai_generated_content': |
|
ai_content = items if isinstance(items, dict) else {} |
|
|
|
|
|
if 'ai_headlines' in ai_content and ai_content['ai_headlines']: |
|
content += "### β¨ Professional Headlines (Choose Your Favorite)\n\n" |
|
for i, headline in enumerate(ai_content['ai_headlines'], 1): |
|
cleaned_headline = headline.strip('"').replace('\\"', '"') |
|
if cleaned_headline.startswith(('1.', '2.', '3.', '4.', '5.')): |
|
cleaned_headline = cleaned_headline[2:].strip() |
|
content += f"{i}. {cleaned_headline}\n\n" |
|
|
|
|
|
if 'ai_about_section' in ai_content and ai_content['ai_about_section']: |
|
content += "### π Enhanced About Section\n\n" |
|
content += f"```\n{ai_content['ai_about_section']}\n```\n\n" |
|
|
|
|
|
if 'ai_experience_descriptions' in ai_content and ai_content['ai_experience_descriptions']: |
|
content += "### πΌ Experience Description Enhancements\n\n" |
|
for j, desc in enumerate(ai_content['ai_experience_descriptions'], 1): |
|
content += f"{j}. {desc}\n\n" |
|
else: |
|
|
|
category_name = category.replace('_', ' ').title() |
|
content += f"### π {category_name}\n\n" |
|
if isinstance(items, list): |
|
for item in items: |
|
content += f"- {item}\n" |
|
else: |
|
content += f"- {items}\n" |
|
content += "\n" |
|
|
|
|
|
content += """--- |
|
|
|
## π― Recommended Action Items |
|
|
|
### Immediate Actions (This Week) |
|
1. **Update Headline:** Choose one of the AI-generated headlines that best reflects your goals |
|
2. **Enhance About Section:** Implement the suggested about section improvements |
|
3. **Add Missing Keywords:** Incorporate relevant missing keywords naturally into your content |
|
4. **Complete Profile Sections:** Fill in any incomplete sections identified in the analysis |
|
|
|
### Medium-term Goals (This Month) |
|
1. **Experience Descriptions:** Update job descriptions using the AI-generated suggestions |
|
2. **Skills Optimization:** Add relevant skills identified in the keyword analysis |
|
3. **Network Growth:** Aim to increase connections in your industry |
|
4. **Content Strategy:** Start sharing relevant professional content |
|
|
|
### Long-term Strategy (Next 3 Months) |
|
1. **Regular Updates:** Keep your profile current with new achievements and skills |
|
2. **Engagement:** Actively engage with your network's content |
|
3. **Personal Branding:** Develop a consistent professional brand across all sections |
|
4. **Performance Monitoring:** Track profile views and connection requests |
|
|
|
--- |
|
|
|
## π Additional Resources |
|
|
|
- **LinkedIn Profile Optimization Guide:** [LinkedIn Help Center](https://www.linkedin.com/help/linkedin) |
|
- **Professional Photography:** Consider professional headshots for profile picture |
|
- **Skill Assessments:** Take LinkedIn skill assessments to verify your expertise |
|
- **Industry Groups:** Join relevant professional groups in your field |
|
|
|
|
|
|
|
*This is an automated analysis. Results may vary based on individual goals and industry standards.* |
|
""" |
|
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f: |
|
f.write(content) |
|
|
|
return file_path |
|
|
|
except Exception as e: |
|
print(f"Export error: {str(e)}") |
|
return None |
|
|
|
def create_gradio_interface(): |
|
"""Create and return the Gradio interface""" |
|
|
|
app = LinkedInEnhancerGradio() |
|
|
|
|
|
custom_css = """ |
|
.gradio-container { |
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
max-width: 1200px; |
|
margin: 0 auto; |
|
} |
|
|
|
.header-text { |
|
text-align: center; |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
color: white; |
|
padding: 2rem; |
|
border-radius: 10px; |
|
margin-bottom: 2rem; |
|
} |
|
|
|
.status-box { |
|
padding: 1rem; |
|
border-radius: 8px; |
|
margin: 0.5rem 0; |
|
} |
|
|
|
.success { |
|
background-color: #d4edda; |
|
border: 1px solid #c3e6cb; |
|
color: #155724; |
|
} |
|
|
|
.error { |
|
background-color: #f8d7da; |
|
border: 1px solid #f5c6cb; |
|
color: #721c24; |
|
} |
|
|
|
.info { |
|
background-color: #e7f3ff; |
|
border: 1px solid #b3d7ff; |
|
color: #0c5460; |
|
} |
|
""" |
|
|
|
with gr.Blocks(css=custom_css, title="π LinkedIn Profile Enhancer", theme=gr.themes.Soft()) as demo: |
|
|
|
|
|
gr.HTML(""" |
|
<div class="header-text"> |
|
<h1>π LinkedIn Profile Enhancer</h1> |
|
<p style="font-size: 1.2em; margin: 1rem 0;">AI-powered LinkedIn profile analysis and enhancement suggestions</p> |
|
<div style="display: flex; justify-content: center; gap: 2rem; margin-top: 1rem;"> |
|
<div style="text-align: center;"> |
|
<div style="font-size: 2em;">π</div> |
|
<div>Real Scraping</div> |
|
</div> |
|
<div style="text-align: center;"> |
|
<div style="font-size: 2em;">π€</div> |
|
<div>AI Analysis</div> |
|
</div> |
|
<div style="text-align: center;"> |
|
<div style="font-size: 2em;">π―</div> |
|
<div>Smart Suggestions</div> |
|
</div> |
|
<div style="text-align: center;"> |
|
<div style="font-size: 2em;">π</div> |
|
<div>Rich Data</div> |
|
</div> |
|
</div> |
|
</div> |
|
""") |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
gr.Markdown("## π API Status") |
|
with gr.Row(): |
|
apify_status = gr.Textbox(label="π‘ Apify API", interactive=False, value="Testing...") |
|
openai_status = gr.Textbox(label="π€ OpenAI API", interactive=False, value="Testing...") |
|
test_btn = gr.Button("π Test Connections", variant="secondary") |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
linkedin_url = gr.Textbox( |
|
label="π LinkedIn Profile URL", |
|
placeholder="https://www.linkedin.com/in/your-profile", |
|
lines=1 |
|
) |
|
job_description = gr.Textbox( |
|
label="π― Target Job Description (Optional)", |
|
placeholder="Paste the job description here for tailored suggestions...", |
|
lines=5 |
|
) |
|
|
|
with gr.Column(scale=1): |
|
profile_image = gr.Image( |
|
label="πΈ Profile Picture", |
|
height=200, |
|
width=200 |
|
) |
|
|
|
|
|
with gr.Row(): |
|
enhance_btn = gr.Button("οΏ½ Enhance LinkedIn Profile", variant="primary", size="lg") |
|
export_btn = gr.Button("π Export Results", variant="secondary") |
|
|
|
|
|
with gr.Tabs(): |
|
with gr.TabItem("π Basic Information"): |
|
enhance_status = gr.Textbox(label="Status", interactive=False) |
|
basic_info = gr.Markdown(label="Basic Information") |
|
|
|
with gr.TabItem("π About Section"): |
|
about_section = gr.Markdown(label="About Section") |
|
|
|
with gr.TabItem("πΌ Experience"): |
|
experience_info = gr.Markdown(label="Work Experience") |
|
|
|
with gr.TabItem("π Education & Skills"): |
|
education_skills = gr.Markdown(label="Education & Skills") |
|
|
|
with gr.TabItem("π Analysis Results"): |
|
analysis_results = gr.Markdown(label="Analysis Results") |
|
keyword_analysis = gr.Markdown(label="Keyword Analysis") |
|
|
|
with gr.TabItem("π‘ Enhancement Suggestions"): |
|
suggestions_content = gr.Markdown(label="Enhancement Suggestions") |
|
|
|
with gr.TabItem("οΏ½ Download Report"): |
|
download_status = gr.Textbox(label="Download Status", interactive=False) |
|
download_btn = gr.DownloadButton("π₯ Download Report", variant="secondary", visible=False) |
|
gr.Markdown(""" |
|
### οΏ½ **Download Complete Report** |
|
|
|
After enhancing your profile, click the **Download Report** button to get a comprehensive markdown file containing: |
|
|
|
#### π **Complete Profile Analysis** |
|
- Basic profile information and current content |
|
- Detailed experience and education sections |
|
- Skills analysis and completeness scoring |
|
|
|
#### π€ **AI Enhancement Suggestions** |
|
- Professional headline options |
|
- Enhanced about section recommendations |
|
- Experience description improvements |
|
- Keyword optimization suggestions |
|
|
|
#### π― **Action Plan** |
|
- Immediate action items (this week) |
|
- Medium-term goals (this month) |
|
- Long-term strategy (next 3 months) |
|
- Additional resources and tips |
|
|
|
**File Format:** Markdown (.md) - Compatible with GitHub, Notion, and most text editors |
|
""") |
|
|
|
|
|
def on_test_connections(): |
|
apify, openai = app.test_api_connections() |
|
return apify, openai |
|
|
|
def on_enhance_profile(url, job_desc): |
|
status, basic, about, exp, details, analysis, keywords, suggestions, image = app.enhance_linkedin_profile(url, job_desc) |
|
|
|
|
|
download_visible = status.startswith("β
") |
|
download_message = "β
Profile enhanced! You can now download the report." if download_visible else "β Please enhance profile first to download report." |
|
|
|
return ( |
|
status, basic, about, exp, details, analysis, keywords, suggestions, image, |
|
gr.DownloadButton("π₯ Download Report", variant="secondary", visible=download_visible), |
|
download_message |
|
) |
|
|
|
def on_download_report(url): |
|
file_path = app.export_results(url) |
|
if file_path and os.path.exists(file_path): |
|
return file_path |
|
return None |
|
|
|
|
|
test_btn.click( |
|
fn=on_test_connections, |
|
outputs=[apify_status, openai_status] |
|
) |
|
|
|
enhance_btn.click( |
|
fn=on_enhance_profile, |
|
inputs=[linkedin_url, job_description], |
|
outputs=[enhance_status, basic_info, about_section, experience_info, education_skills, analysis_results, keyword_analysis, suggestions_content, profile_image, download_btn, download_status] |
|
) |
|
|
|
download_btn.click( |
|
fn=on_download_report, |
|
inputs=[linkedin_url], |
|
outputs=[download_btn] |
|
) |
|
|
|
|
|
demo.load( |
|
fn=on_test_connections, |
|
outputs=[apify_status, openai_status] |
|
) |
|
|
|
|
|
gr.HTML(""" |
|
<div style="text-align: center; margin-top: 2rem; padding: 1rem; border-top: 1px solid #eee;"> |
|
<p>π <strong>LinkedIn Profile Enhancer</strong> | Powered by AI | Built with β€οΈ using Gradio</p> |
|
<p>Data scraped with respect to LinkedIn's ToS | Uses OpenAI GPT-4o-mini and Apify</p> |
|
</div> |
|
""") |
|
|
|
return demo |
|
|
|
def main(): |
|
"""Main function""" |
|
|
|
|
|
if len(sys.argv) > 1: |
|
if sys.argv[1] == '--help': |
|
print(""" |
|
LinkedIn Profile Enhancer - Gradio Interface |
|
|
|
Usage: |
|
python app.py # Launch Gradio web interface |
|
python app.py --help # Show this help |
|
|
|
Web Interface Features: |
|
- Beautiful modern UI |
|
- Real-time profile extraction |
|
- AI-powered analysis |
|
- Enhancement suggestions |
|
- Export functionality |
|
- Profile image display |
|
""") |
|
return |
|
else: |
|
print("β Unknown argument. Use --help for usage information.") |
|
return |
|
|
|
|
|
print("π Starting LinkedIn Profile Enhancer...") |
|
print("π± Launching Gradio interface...") |
|
|
|
|
|
demo = create_gradio_interface() |
|
|
|
|
|
demo.launch( |
|
share=False, |
|
server_name="0.0.0.0", |
|
server_port=7860, |
|
show_error=True |
|
) |
|
|
|
if __name__ == "__main__": |
|
main() |
|
|