import os import sys # Add source directory to path so sibling imports work sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "Code+Folder", "src")) import streamlit as st try: from streamlit_feedback import streamlit_feedback FEEDBACK_AVAILABLE = True except ImportError: FEEDBACK_AVAILABLE = False from langchain_classic.chains import ConversationChain from langchain_classic.memory import ConversationBufferWindowMemory from langchain_core.messages import SystemMessage from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate from langchain_community.chat_models import ChatLiteLLM from constants import PROVIDERS, TEMPLATE_CONTENT, comparison_prompt, resume_analysis_prompt, \ job_description_analysis_prompt, gap_analysis_prompt, actionable_steps_prompt, experience_enhancement_prompt, \ additional_qualifications_prompt, resume_tailoring_prompt, relevant_skills_highlight_prompt, \ resume_formatting_prompt, resume_length_prompt from directory_reader import DirectoryReader st.set_page_config(page_title="Resume Reviewer") # Initialize llm as None at the top level llm = None resume_chain = None # Initialize variables resume_content = None job_description_content = None # Sidebar with st.sidebar: st.title('Resume Reviewer') st.write("Upload your resume for my recommendations. Job description is optional.") # Provider & Model selection st.write("---") st.write("### LLM Provider") provider_names = list(PROVIDERS.keys()) selected_provider = st.selectbox("Provider", provider_names) provider_info = PROVIDERS[selected_provider] selected_model = st.selectbox("Model", provider_info["models"]) st.write(f"Get an API key at [{selected_provider}]({provider_info['url']})") api_key = st.text_input( f"{selected_provider} API Key", type="password", help="Your API key will not be stored", ) if api_key: os.environ[provider_info["env_var"]] = api_key llm = ChatLiteLLM(model=selected_model, temperature=0.0) else: st.info(f"Please enter your {selected_provider} API key to start") llm = None # Resume upload (file only) st.write("---") st.write("### Resume") st.write("Note: File size should be less than 5MB") resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"], accept_multiple_files=False) # JD input (file or text, optional) st.write("---") st.write("### Job Description (optional)") jd_file = st.file_uploader("Upload a JD (txt file)", type=["txt"], accept_multiple_files=False) jd_text = st.text_area("Or paste the job description here:", height=150) # Process resume if resume_file is not None and api_key: try: with st.spinner("Processing resume file..."): directory_reader = DirectoryReader("", "") resume_content = directory_reader.extract_text_from_pdf(resume_file) st.sidebar.success("Resume processed successfully!") except Exception as e: st.sidebar.error(f"Error processing resume file: {str(e)}") resume_content = None # Process JD - prefer file upload, fall back to text input if jd_file is not None: try: from io import StringIO stringio = StringIO(jd_file.getvalue().decode('utf-8')) job_description_content = stringio.read() st.sidebar.success("JD processed successfully!") except Exception as e: st.sidebar.error(f"Error processing JD file: {str(e)}") elif jd_text: job_description_content = jd_text # Build system prompt based on what's provided if resume_content and job_description_content: SYSTEM_PROMPT = "\n\n" + TEMPLATE_CONTENT + \ " {}. with the job description: " \ " {}.\n\n" \ "Be crisp and clear in response. DO NOT provide the resume and job description in the response.\n\n".format( resume_content, job_description_content) elif resume_content: SYSTEM_PROMPT = "\n\n" + TEMPLATE_CONTENT + \ " {}. \n\n" \ "No job description was provided. Focus on general resume feedback, strengths, and areas for improvement. " \ "Be crisp and clear in response. DO NOT provide the resume in the response.\n\n".format(resume_content) else: SYSTEM_PROMPT = "\n\n" + TEMPLATE_CONTENT # Store LLM generated responses if "messages" not in st.session_state.keys(): st.session_state.messages = [{"role": "assistant", "content": "How may I assist you today?"}] # Display or clear chat messages for message in st.session_state.messages: if message["role"] != "feedback": with st.chat_message(message["role"]): st.write(message["content"]) def clear_chat_history(): global resume_chain st.session_state.messages = [{"role": "assistant", "content": "How may I help you today?"}] if llm is not None: resume_chain = ConversationChain( llm=llm, prompt=prompt_template, memory=memory, verbose=False ) def generate_report(): user_message = {"role": "user", "content": "Generate a Report!"} st.session_state.messages.append(user_message) if resume_content is None: st.error("Please upload a resume first!") return with st.chat_message("assistant"): with st.spinner("Just a moment..."): resume_analysis = generate_response(resume_analysis_prompt.format(resume_content)) resume_formatting_analysis = generate_response( resume_formatting_prompt.format(resume_content, "N/A")) report = f"**Resume Analysis:**\n{resume_analysis}\n\n" \ f"**Resume Formatting:**\n{resume_formatting_analysis}" if job_description_content is not None: comparison_analysis = generate_response( comparison_prompt.format(resume_content, job_description_content)) job_description_analysis = generate_response( job_description_analysis_prompt.format(job_description_content)) gap_analysis = generate_response( gap_analysis_prompt.format(resume_content, job_description_content)) actionable_steps_analysis = generate_response( actionable_steps_prompt.format(resume_content, job_description_content)) experience_enhancement_analysis = generate_response( experience_enhancement_prompt.format(resume_content, job_description_content)) additional_qualifications_analysis = generate_response( additional_qualifications_prompt.format(resume_content, job_description_content)) resume_tailoring_analysis = generate_response( resume_tailoring_prompt.format(resume_content, job_description_content)) relevant_skills_highlight_analysis = generate_response( relevant_skills_highlight_prompt.format(resume_content, job_description_content)) resume_length_analysis = generate_response( resume_length_prompt.format(resume_content, job_description_content)) report += f"\n\n**Comparison Analysis:**\n{comparison_analysis}\n\n" \ f"**Job Description Analysis:**\n{job_description_analysis}\n\n" \ f"**Gap Analysis:**\n{gap_analysis}\n\n" \ f"**Actionable Steps:**\n{actionable_steps_analysis}\n\n" \ f"**Experience Enhancement:**\n{experience_enhancement_analysis}\n\n" \ f"**Additional Qualifications:**\n{additional_qualifications_analysis}\n\n" \ f"**Resume Tailoring:**\n{resume_tailoring_analysis}\n\n" \ f"**Relevant Skills Highlight:**\n{relevant_skills_highlight_analysis}\n\n" \ f"**Resume Length:**\n{resume_length_analysis}" report_message = {"role": "assistant", "content": report} st.session_state.messages.append(report_message) # Setup the system message and prompt template system_message = SystemMessage(content=TEMPLATE_CONTENT) human_message = HumanMessagePromptTemplate.from_template("{history} User:{input} Assistant:") prompt_template = ChatPromptTemplate(messages=[system_message, human_message]) memory = ConversationBufferWindowMemory(k=2) # Initialize the chain if llm is available if llm is not None: resume_chain = ConversationChain( llm=llm, prompt=prompt_template, memory=memory, verbose=False ) def generate_response(prompt_input): if resume_chain is None: return "Please enter your API key to use this application" output = resume_chain.predict(input=prompt_input) return output st.sidebar.button('Clear Chat History', on_click=clear_chat_history) st.sidebar.button('Generate Report', on_click=generate_report) def get_feedback(): st.session_state.messages.append({"role": "feedback", "content": st.session_state.fbk}) # At the beginning of your script, initialize the prompt in session state if "current_prompt" not in st.session_state: st.session_state.current_prompt = "" # When user enters a prompt if prompt := st.chat_input(): st.session_state.current_prompt = prompt st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.write(prompt) def get_llm_response(): with st.chat_message("assistant"): with st.spinner("Thinking..."): response = generate_response(st.session_state.current_prompt + SYSTEM_PROMPT) placeholder = st.empty() placeholder.markdown(response) full_response = response message = {"role": "assistant", "content": full_response} st.session_state.messages.append(message) # Only show feedback form if the feature is available if FEEDBACK_AVAILABLE: with st.form("form"): streamlit_feedback(feedback_type="thumbs", optional_text_label="[Optional] Please provide an explanation", key="fbk") st.form_submit_button('Save feedback', on_click=get_feedback) # Generate a new response if last message is not from assistant if st.session_state.messages[-1]["role"] not in ["assistant", "feedback"]: get_llm_response() if st.session_state.messages[-1]["role"] in ["feedback"]: try: feedback_response = st.session_state.messages[-1]["content"] score_mappings = { "thumbs": {"thumbs_up": 1, "thumbs_down": 0}, } score = score_mappings[feedback_response["type"]][feedback_response["score"]] if score == 0: feedback = st.session_state.messages[-1]["content"]['text'] prompt = "Please respond according to feedback '{0}' on the previous response on \n".format(feedback) \ + st.session_state.messages[-3]["content"] get_llm_response() except: pass