Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| from groq import Groq | |
| from jobspy import scrape_jobs | |
| from resume_advance_analysis import * | |
| from extraction import * | |
| from typing import List, Dict, Any | |
| import json | |
| import re | |
| import os | |
| import logging | |
| def make_clickable_link(link): | |
| return f'<a href="{link}" target="_blank">{link}</a>' | |
| # os.environ['GROQ_API_KEY'] = os.getenv("GROQ_API_KEY") | |
| groq_api_key = os.getenv("GROQ_API_KEY") | |
| if groq_api_key is None: | |
| try: | |
| groq_api_key = st.secrets["GROQ_API_KEY"] | |
| except Exception as e: | |
| st.error("GROQ_API_KEY is not set in the environment variables or Streamlit secrets.") | |
| groq_api_key = None | |
| # groq_api_key = st.secrets["GROQ_API_KEY"] | |
| # Configure logging | |
| # logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') | |
| # logger = logging.getLogger(__name__) | |
| class JobSuggestionEngine: | |
| def __init__(self): | |
| # self.llm = ChatGroq( | |
| # groq_api_key = groq_api_key, | |
| # model_name="llama-3.1-70b-versatile", | |
| # temperature=0.7, | |
| # max_tokens=4096 | |
| # ) | |
| self.client = Groq(api_key=groq_api_key) | |
| def _extract_json(self, text: str) -> Dict[str, Any]: | |
| """ | |
| Extracting JSON from LLM | |
| """ | |
| try: | |
| # logger.debug("Extracting JSON from LLM response") | |
| # Clean and extract JSON | |
| json_match = re.search(r'\{.*\}', text, re.DOTALL) | |
| if json_match: | |
| return json.loads(json_match.group(0)) | |
| return {} | |
| except Exception as e: | |
| st.error(f"JSON Extraction Error: {e}") | |
| # logger.error(f"JSON Extraction Error: {e}") | |
| return {} | |
| def generate_job_suggestions(self, resume_data: cv) -> List[Dict[str, str]]: | |
| # logger.info("Generating job suggestions based on resume") | |
| prompt = f"""Based on the following resume details, provide job suggestions: | |
| Resume Details: | |
| - Skills: {', '.join(resume_data.skills or [])} | |
| - Certifications: {', '.join(resume_data.certifications or [])} | |
| - Years of Experience: {resume_data.years_of_exp or 0} | |
| Tasks: | |
| 1. Suggest most potential 3 job roles that match the profile | |
| 2. Include job role, brief description, and why it's suitable | |
| 3. Respond in strict JSON format | |
| Required JSON Structure: | |
| {{ | |
| "job_suggestions": [ | |
| {{ | |
| "role": "Job Role", | |
| "description": "Brief job description", | |
| "suitability_reason": "Why this role matches the resume" | |
| }} | |
| ] | |
| }} | |
| """ | |
| try: | |
| # logger.debug(f"Calling Groq API with prompt: {prompt[:100]}...") # start of api call | |
| # API call to the Groq client for chat completions | |
| chat_completion = self.client.chat.completions.create( | |
| messages=[ | |
| {"role": "system", "content": "You are a career advisor generating job suggestions based on resume details."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| model="llama3-8b-8192", | |
| temperature=0.7, | |
| max_tokens=1024, | |
| top_p=1, | |
| stop=None, | |
| stream=False | |
| ) | |
| # Extract and parse the JSON response from the completion | |
| response_text = chat_completion.choices[0].message.content | |
| suggestions_data = self._extract_json(response_text) | |
| # logger.info(f"Job suggestions generated: {len(suggestions_data.get('job_suggestions', []))} found") | |
| # Return job suggestions, if not found -> empty list | |
| return suggestions_data.get('job_suggestions', []) | |
| except Exception as e: | |
| st.error(f"Job Suggestion Error: {e}") | |
| # logger.error(f"Job Suggestion Error: {e}") | |
| return [] | |
| def Job_assistant(): | |
| st.title("π Job Suggestion & Search Assistant") | |
| # Initialize session state for resume analysis tab | |
| if 'uploaded_resume' not in st.session_state: | |
| st.session_state.uploaded_resume = None | |
| if 'resume_data' not in st.session_state: | |
| st.session_state.resume_data = None | |
| if 'job_suggestions' not in st.session_state: | |
| st.session_state.job_suggestions = [] | |
| if 'improvement_suggestions' not in st.session_state: | |
| st.session_state.improvement_suggestions = {} | |
| # Initialize session state for job search tab | |
| if 'site_name' not in st.session_state: | |
| st.session_state.site_name = ["indeed", "glassdoor"] | |
| if 'search_term' not in st.session_state: | |
| st.session_state.search_term = "software engineer" | |
| if 'location' not in st.session_state: | |
| st.session_state.location = "San Francisco, CA" | |
| if 'results_wanted' not in st.session_state: | |
| st.session_state.results_wanted = 20 | |
| if 'hours_old' not in st.session_state: | |
| st.session_state.hours_old = 72 | |
| if 'country_indeed' not in st.session_state: | |
| st.session_state.country_indeed = "USA" | |
| if 'job_search_results' not in st.session_state: | |
| st.session_state.job_search_results = pd.DataFrame() | |
| # Tabs for functionalities | |
| tab1, tab2 = st.tabs(["Resume Analysis", "Direct Job Search"]) | |
| with tab1: | |
| st.header("Resume Analysis & Job Suggestions") | |
| # File Upload | |
| uploaded_resume = st.file_uploader( | |
| "Upload Resume", | |
| type=['pdf', 'txt'], | |
| help="Upload your resume in PDF or TXT format" | |
| ) | |
| # # Initialize LLM | |
| # try: | |
| # llm = initialize_llm() | |
| # logger.info("LLM initialized successfully") | |
| # except Exception as e: | |
| # st.error(f"LLM Initialization Error: {e}") | |
| # logger.error(f"LLM Initialization Error: {e}") | |
| # st.stop() | |
| if uploaded_resume: | |
| st.session_state.uploaded_resume = uploaded_resume | |
| # Process Resume | |
| with st.spinner("Analyzing Resume..."): | |
| try: | |
| # Extract resume text | |
| resume_text = process_file(uploaded_resume) | |
| # logger.info("Resume extracted successfully") | |
| # Extract structured CV data | |
| candidates = extract_cv_data(resume_text) | |
| if not candidates: | |
| st.error("Could not extract resume data") | |
| # logger.error("No candidates extracted from resume") | |
| st.stop() | |
| st.session_state.resume_data = candidates[0] | |
| # Display extracted candidate information | |
| st.subheader("Resume Analysis") | |
| display_candidates_info(candidates) | |
| resume_data = candidates[0] | |
| except Exception as e: | |
| st.error(f"Resume Processing Error: {e}") | |
| # logger.error(f"Resume Processing Error: {e}") | |
| st.stop() | |
| # Initialize Job Suggestion Engine | |
| if st.session_state.resume_data: | |
| suggestion_engine = JobSuggestionEngine() | |
| # logger.info("Job_Suggestion_Engine initialized") | |
| # Generate Job Suggestions | |
| job_suggestions = suggestion_engine.generate_job_suggestions(resume_data) | |
| # logger.info(f"Generated {len(job_suggestions)} job suggestions") | |
| st.session_state.job_suggestions = job_suggestions | |
| # Display Job Suggestions | |
| st.header("π― Job Suggestions") | |
| # for suggestion in job_suggestions: | |
| for suggestion in st.session_state.job_suggestions: | |
| with st.expander(f"{suggestion.get('role', 'Unnamed Role')}"): | |
| st.write(f"**Description:** {suggestion.get('description', 'No description')}") | |
| st.write(f"**Suitability:** {suggestion.get('suitability_reason', 'Not specified')}") | |
| try: | |
| # Extract resume text | |
| resume_text = process_file(uploaded_resume) | |
| # logger.info("Resume text extracted again for improvement suggestions") | |
| # Initialize Resume Improvement Engine | |
| improvement_engine = ResumeImprovementEngine() | |
| # Generate Improvement Suggestions | |
| improvement_suggestions = improvement_engine.generate_resume_improvement_suggestions(resume_text) | |
| # logger.info("Resume improvement suggestions generated") | |
| st.session_state.improvement_suggestions = improvement_suggestions | |
| # Display Suggestions | |
| st.subheader("π Comprehensive Resume Analysis") | |
| # Overall Assessment | |
| if improvement_suggestions.get('overall_assessment'): | |
| with st.expander("π Overall Assessment"): | |
| st.write("**Strengths:**") | |
| for strength in improvement_suggestions['overall_assessment'].get('strengths', []): | |
| st.markdown(f"- {strength}") | |
| st.write("**Weaknesses:**") | |
| for weakness in improvement_suggestions['overall_assessment'].get('weaknesses', []): | |
| st.markdown(f"- {weakness}") | |
| # Section Recommendations | |
| if improvement_suggestions.get('section_recommendations'): | |
| with st.expander("π Section-by-Section Recommendations"): | |
| for section, details in improvement_suggestions['section_recommendations'].items(): | |
| st.subheader(f"{section.replace('_', ' ').title()} Section") | |
| st.write(f"**Current Status:** {details.get('current_status', 'No assessment')}") | |
| st.write("**Improvement Suggestions:**") | |
| for suggestion in details.get('improvement_suggestions', []): | |
| st.markdown(f"- {suggestion}") | |
| # Additional Insights | |
| st.subheader("β¨ Additional Recommendations") | |
| # Writing Improvements | |
| if improvement_suggestions.get('writing_improvements'): | |
| with st.expander("βοΈ Writing & Formatting Advice"): | |
| st.write("**Language Suggestions:**") | |
| for lang_suggestion in improvement_suggestions['writing_improvements'].get('language_suggestions', []): | |
| st.markdown(f"- {lang_suggestion}") | |
| st.write("**Formatting Advice:**") | |
| for format_advice in improvement_suggestions['writing_improvements'].get('formatting_advice', []): | |
| st.markdown(f"- {format_advice}") | |
| # Additional Sections | |
| if improvement_suggestions.get('additional_sections_recommended'): | |
| with st.expander("π Suggested Additional Sections"): | |
| for section in improvement_suggestions['additional_sections_recommended']: | |
| st.markdown(f"- {section}") | |
| # Keyword Optimization | |
| if improvement_suggestions.get('keyword_optimization'): | |
| with st.expander("π Keyword & ATS Optimization"): | |
| st.write("**Missing Industry Keywords:**") | |
| for keyword in improvement_suggestions['keyword_optimization'].get('missing_industry_keywords', []): | |
| st.markdown(f"- {keyword}") | |
| st.write(f"**ATS Compatibility Score:** {improvement_suggestions['keyword_optimization'].get('ats_compatibility_score', 'Not available')}") | |
| # Career Positioning | |
| if improvement_suggestions.get('career_positioning'): | |
| with st.expander("π― Career Positioning"): | |
| st.write("**Personal Branding Suggestions:**") | |
| for branding_suggestion in improvement_suggestions['career_positioning'].get('personal_branding_suggestions', []): | |
| st.markdown(f"- {branding_suggestion}") | |
| st.write("**Skill Highlighting Recommendations:**") | |
| for skill_suggestion in improvement_suggestions['career_positioning'].get('skill_highlighting_recommendations', []): | |
| st.markdown(f"- {skill_suggestion}") | |
| except Exception as e: | |
| st.error(f"Resume Improvement Analysis Error: {e}") | |
| # logger.error(f"Resume Improvement Analysis Error: {e}") | |
| with tab2: | |
| st.header("π Direct Job Search") | |
| # Job Search Parameters | |
| with st.form(key='job_search_form'): | |
| # Job Search Parameters | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| site_name = st.multiselect( | |
| "Select Job Sites", | |
| ["indeed", "linkedin", "zip_recruiter", "glassdoor", "google"], | |
| default=st.session_state.site_name | |
| ) | |
| with col2: | |
| search_term = st.text_input("Search Term", st.session_state.search_term) | |
| with col3: | |
| location = st.text_input("Location", st.session_state.location) | |
| with col4: | |
| results_wanted = st.number_input("Number of Results", min_value=1, max_value=100, value=st.session_state.results_wanted) | |
| # Additional parameters | |
| col5, col6 = st.columns(2) | |
| with col5: | |
| hours_old = st.number_input("Jobs Posted Within (hours)", min_value=1, max_value=168, value=st.session_state.hours_old) | |
| with col6: | |
| country_indeed = st.text_input("Country (for Indeed)", st.session_state.country_indeed) | |
| # Submit button inside the form | |
| submit_button = st.form_submit_button("Search Jobs") | |
| # Only run search when form is submitted | |
| if submit_button: | |
| st.session_state.site_name = site_name | |
| st.session_state.search_term = search_term | |
| st.session_state.location = location | |
| st.session_state.results_wanted = results_wanted | |
| st.session_state.hours_old = hours_old | |
| st.session_state.country_indeed = country_indeed | |
| with st.spinner("Searching Jobs..."): | |
| try: | |
| # Your existing job search code here | |
| jobs = scrape_jobs( | |
| site_name=site_name, | |
| search_term=search_term, | |
| google_search_term=f"{search_term} jobs near {location}", | |
| location=location, | |
| results_wanted=results_wanted, | |
| hours_old=hours_old, | |
| country_indeed=country_indeed, | |
| ) | |
| st.session_state.job_search_results = jobs | |
| if len(jobs) > 0: | |
| st.success(f"Found {len(jobs)} jobs") | |
| jobs_filtered = jobs[['site', 'job_url', 'title', 'company', 'location', 'date_posted']] | |
| jobs_filtered['job_url'] = jobs_filtered['job_url'].apply(make_clickable_link) | |
| st.write(jobs_filtered.to_html(escape=False), unsafe_allow_html=True) | |
| csv_file = jobs.to_csv(index=False) | |
| st.download_button( | |
| label="Download Jobs as CSV", | |
| data=csv_file, | |
| file_name='job_search_results.csv', | |
| mime='text/csv' | |
| ) | |
| else: | |
| st.warning("No jobs found") | |
| except Exception as e: | |
| st.error(f"Job Search Error: {e}") | |
| # logger.error(f"Job Search Error: {e}") | |