Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import plotly.express as px | |
| from datetime import datetime | |
| import json | |
| import os | |
| from pathlib import Path | |
| import logging | |
| from typing import List, Dict, Any | |
| from openai import OpenAI | |
| from dotenv import load_dotenv | |
| #from llm_job_assistant import LLMJobAssistant # Our previous class | |
| class JobAssistantUI: | |
| def __init__(self): | |
| self.setup_streamlit() | |
| self.load_dotenv() | |
| self.assistant = LLMJobAssistant() | |
| def setup_streamlit(self): | |
| """Configure Streamlit page settings""" | |
| st.set_page_config( | |
| page_title="AI Job Search Assistant", | |
| page_icon="🔍", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| def load_dotenv(self): | |
| """Load environment variables""" | |
| os.environ['PATH'] += f':{os.path.expanduser("~/.cargo/bin")}' | |
| load_dotenv() | |
| if not os.getenv('OPENAI_API_KEY'): | |
| st.sidebar.error("OpenAI API key not found. Please set it in .env file") | |
| def render_sidebar(self): | |
| """Render sidebar controls""" | |
| with st.sidebar: | |
| st.title("Search Settings") | |
| # Job Search Settings | |
| st.subheader("Job Search Criteria") | |
| keywords = st.text_area( | |
| "Search Keywords (one per line)", | |
| value="\n".join(self.assistant.config['keywords']) | |
| ) | |
| self.assistant.config['keywords'] = [k.strip() for k in keywords.split("\n") if k.strip()] | |
| # Location Settings | |
| location_type = st.radio( | |
| "Location Type", | |
| ["Remote Only", "Hybrid", "All Locations"] | |
| ) | |
| # Experience Level | |
| experience_level = st.multiselect( | |
| "Experience Level", | |
| ["Entry Level", "Mid Level", "Senior", "Lead"], | |
| default=["Entry Level", "Mid Level"] | |
| ) | |
| # Salary Range | |
| min_salary = st.slider( | |
| "Minimum Salary (USD)", | |
| 0, 200000, self.assistant.config['minimum_salary'], | |
| step=5000 | |
| ) | |
| # Save Settings | |
| if st.button("Save Settings"): | |
| self.assistant.config['minimum_salary'] = min_salary | |
| self.assistant.save_config() | |
| st.success("Settings saved!") | |
| def render_job_search_tab(self): | |
| """Render job search tab""" | |
| st.header("Job Search") | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| if st.button("Start New Job Search", type="primary"): | |
| with st.spinner("Searching for jobs..."): | |
| jobs_df = self.assistant.run_enhanced_job_search() | |
| st.session_state['jobs_df'] = jobs_df | |
| st.success(f"Found {len(jobs_df)} matching jobs!") | |
| with col2: | |
| if st.button("Load Previous Results"): | |
| try: | |
| jobs_df = pd.read_pickle('enhanced_jobs.pkl') | |
| st.session_state['jobs_df'] = jobs_df | |
| st.success("Previous results loaded!") | |
| except FileNotFoundError: | |
| st.error("No previous results found") | |
| if 'jobs_df' in st.session_state: | |
| self.display_job_results(st.session_state['jobs_df']) | |
| def display_job_results(self, df: pd.DataFrame): | |
| """Display job search results""" | |
| st.subheader("Search Results") | |
| # Filters | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| companies = st.multiselect( | |
| "Filter by Company", | |
| options=sorted(df['company'].unique()) | |
| ) | |
| with col2: | |
| min_match = st.slider( | |
| "Minimum Match Score", | |
| 0, 100, 50 | |
| ) | |
| with col3: | |
| sort_by = st.selectbox( | |
| "Sort by", | |
| ["Match Score", "Company", "Date Posted"] | |
| ) | |
| # Filter DataFrame | |
| filtered_df = df.copy() | |
| if companies: | |
| filtered_df = filtered_df[filtered_df['company'].isin(companies)] | |
| filtered_df = filtered_df[filtered_df['analysis.match_score'] >= min_match] | |
| # Sort DataFrame | |
| if sort_by == "Match Score": | |
| filtered_df = filtered_df.sort_values('analysis.match_score', ascending=False) | |
| elif sort_by == "Company": | |
| filtered_df = filtered_df.sort_values('company') | |
| else: | |
| filtered_df = filtered_df.sort_values('date_scraped', ascending=False) | |
| # Display results | |
| for _, job in filtered_df.iterrows(): | |
| with st.expander(f"{job['title']} at {job['company']} - Match: {job['analysis']['match_score']}%"): | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| st.write("**Job Description:**") | |
| st.write(job['full_description']) | |
| st.write("**Required Skills:**") | |
| for skill in job['analysis']['required_skills']: | |
| st.markdown(f"- {skill}") | |
| with col2: | |
| st.write("**Salary Range:**") | |
| st.write(job['analysis']['estimated_salary_range']) | |
| st.write("**Experience Required:**") | |
| st.write(job['analysis']['required_experience']) | |
| if st.button("Generate Application Materials", key=job['url']): | |
| with st.spinner("Generating materials..."): | |
| cover_letter = self.assistant.generate_custom_cover_letter( | |
| job['analysis'], | |
| job['company'] | |
| ) | |
| resume_suggestions = self.assistant.tailor_resume(job['analysis']) | |
| st.download_button( | |
| "Download Cover Letter", | |
| cover_letter, | |
| file_name=f"cover_letter_{job['company']}.txt" | |
| ) | |
| st.download_button( | |
| "Download Resume Suggestions", | |
| resume_suggestions, | |
| file_name=f"resume_suggestions_{job['company']}.txt" | |
| ) | |
| def render_analytics_tab(self): | |
| """Render analytics tab""" | |
| st.header("Job Search Analytics") | |
| if 'jobs_df' in st.session_state: | |
| df = st.session_state['jobs_df'] | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Match Score Distribution | |
| fig = px.histogram( | |
| df, | |
| x='analysis.match_score', | |
| title='Distribution of Match Scores', | |
| labels={'analysis.match_score': 'Match Score'} | |
| ) | |
| st.plotly_chart(fig) | |
| with col2: | |
| # Company Distribution | |
| company_counts = df['company'].value_counts().head(10) | |
| fig = px.bar( | |
| company_counts, | |
| title='Top Companies', | |
| labels={'value': 'Number of Jobs', 'index': 'Company'} | |
| ) | |
| st.plotly_chart(fig) | |
| # Salary Distribution | |
| fig = px.box( | |
| df, | |
| y='analysis.estimated_salary_range', | |
| title='Salary Distribution' | |
| ) | |
| st.plotly_chart(fig) | |
| def render_settings_tab(self): | |
| """Render settings tab""" | |
| st.header("Application Settings") | |
| # Resume Upload | |
| st.subheader("Resume") | |
| resume_file = st.file_uploader("Upload your resume (TXT format)", type=['txt']) | |
| if resume_file: | |
| resume_text = resume_file.read().decode() | |
| with open('templates/base_resume.txt', 'w') as f: | |
| f.write(resume_text) | |
| st.success("Resume uploaded successfully!") | |
| # API Settings | |
| st.subheader("API Settings") | |
| api_key = st.text_input( | |
| "OpenAI API Key", | |
| value=os.getenv('OPENAI_API_KEY', ''), | |
| type="password" | |
| ) | |
| if st.button("Save API Key"): | |
| with open('.env', 'w') as f: | |
| f.write(f"OPENAI_API_KEY={api_key}") | |
| st.success("API key saved!") | |
| def run(self): | |
| """Run the Streamlit application""" | |
| st.title("AI Job Search Assistant") | |
| # Render sidebar | |
| self.render_sidebar() | |
| # Main content tabs | |
| tab1, tab2, tab3 = st.tabs(["Job Search", "Analytics", "Settings"]) | |
| with tab1: | |
| self.render_job_search_tab() | |
| with tab2: | |
| self.render_analytics_tab() | |
| with tab3: | |
| self.render_settings_tab() | |
| def main(): | |
| app = JobAssistantUI() | |
| app.run() | |
| if __name__ == "__main__": | |
| main() | |