|
import streamlit as st
|
|
import re
|
|
import os
|
|
from dotenv import load_dotenv
|
|
from openai import AzureOpenAI
|
|
import json
|
|
from nlp_parsed import parse_recruiter_query,prompt_enhancer
|
|
from SERP import query_making, serp_api_call
|
|
from apify import apify_call
|
|
from validate import validate_function, score_candidates
|
|
from postgres_db import fetch_from_saral_data, check_completeness, data_input, cur, conn, store_prompt
|
|
|
|
|
|
st.set_page_config(page_title="LinkedIn Recruiter Assistant", page_icon="🎯")
|
|
|
|
|
|
if "parsed_data" not in st.session_state:
|
|
st.session_state.parsed_data = {}
|
|
|
|
|
|
if "matched_results" not in st.session_state:
|
|
st.session_state.matched_results = []
|
|
if "unmatched_results" not in st.session_state:
|
|
st.session_state.unmatched_results = []
|
|
|
|
|
|
if "progress_placeholder" not in st.session_state:
|
|
st.session_state.progress_placeholder = None
|
|
if "progress" not in st.session_state:
|
|
st.session_state.progress = None
|
|
|
|
|
|
if "current_page" not in st.session_state:
|
|
st.session_state.current_page = 0
|
|
if "run_search" not in st.session_state:
|
|
st.session_state.run_search = False
|
|
|
|
|
|
if "user_input" not in st.session_state:
|
|
st.session_state.user_input = ""
|
|
|
|
|
|
st.header("Saral AI")
|
|
|
|
user_input = st.text_area(
|
|
"Enter your query here:",
|
|
placeholder="Enter your query here",
|
|
key="user_input_box",
|
|
value=st.session_state.user_input
|
|
)
|
|
|
|
|
|
st.session_state.user_input = user_input
|
|
|
|
|
|
|
|
if user_input.strip():
|
|
parsed_data = parse_recruiter_query(user_input)
|
|
|
|
|
|
st.session_state.parsed_data = parsed_data
|
|
|
|
if "error" in parsed_data:
|
|
st.error(parsed_data["error"])
|
|
elif parsed_data.get("is_indian") == False:
|
|
print("Our platform is not allowing search for out of india")
|
|
else:
|
|
with st.expander("Query", expanded=True):
|
|
col1, col2 = st.columns([1, 1])
|
|
with col1:
|
|
st.markdown(f'**Job Title:** {parsed_data.get("job_title", "None")}')
|
|
st.markdown(f'**Skills:** {parsed_data.get("skills", "None")}')
|
|
st.markdown(
|
|
f'**Experience:** {parsed_data.get("experience","None")} years of Experience'
|
|
)
|
|
st.markdown(f'is_indian :{parsed_data.get("is_indian","None")}')
|
|
with col2:
|
|
st.markdown(f'**Location:** {parsed_data.get("location", "None")}')
|
|
st.markdown(
|
|
f'**Work Preference:** {parsed_data.get("work_preference", "None")}'
|
|
)
|
|
st.markdown(f'**Job Type:** {parsed_data.get("job_type", "None")}')
|
|
|
|
|
|
|
|
|
|
if st.button("Enhance Prompt", use_container_width=True):
|
|
enhanced = prompt_enhancer(st.session_state.user_input)
|
|
|
|
|
|
st.session_state.user_input = enhanced
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if st.button(
|
|
"Enter",
|
|
use_container_width=True,
|
|
disabled=(parsed_data.get("is_indian") is False)
|
|
):
|
|
st.session_state.current_page = 0
|
|
st.session_state.run_search = True
|
|
|
|
|
|
if st.session_state.run_search:
|
|
if not user_input.strip():
|
|
st.warning("Please enter a valid query.")
|
|
st.stop()
|
|
|
|
|
|
|
|
store_prompt(conn,user_input,parsed_data)
|
|
|
|
|
|
st.session_state.progress_placeholder = st.empty()
|
|
st.session_state.progress = st.session_state.progress_placeholder.progress(0)
|
|
status = st.empty()
|
|
|
|
if user_input.strip() and "error" not in parsed_data:
|
|
query, location = query_making(parsed_data)
|
|
|
|
print(query)
|
|
|
|
|
|
|
|
|
|
if st.session_state.current_page >= 0 :
|
|
results_per_page = 10
|
|
start = st.session_state.current_page * results_per_page
|
|
|
|
serp_data = serp_api_call(
|
|
query,
|
|
start= start,
|
|
results_per_page=10
|
|
)
|
|
|
|
|
|
saral_data, remain_urls = fetch_from_saral_data(serp_data, conn)
|
|
|
|
print(remain_urls)
|
|
|
|
|
|
st.session_state.progress.progress(30)
|
|
|
|
serp_json = {}
|
|
|
|
apify_json = {}
|
|
|
|
if len(remain_urls) >= 1:
|
|
for idx, i in enumerate(remain_urls,start=1):
|
|
serp_json[idx] = i
|
|
|
|
apify_json = apify_call(serp_json)
|
|
st.session_state.progress.progress(70)
|
|
|
|
|
|
if apify_json:
|
|
total_candidates = saral_data + apify_json
|
|
|
|
else:
|
|
total_candidates = saral_data
|
|
|
|
data_input(total_candidates)
|
|
|
|
|
|
matched, unmatched = validate_function(location, total_candidates)
|
|
st.session_state.progress.progress(70)
|
|
|
|
|
|
|
|
matched = score_candidates(parsed_data , matched)
|
|
|
|
st.session_state.matched_results = matched
|
|
st.session_state.unmatched_results = unmatched
|
|
|
|
st.session_state.progress.progress(100)
|
|
st.session_state.progress_placeholder.empty()
|
|
st.session_state.progress = None
|
|
st.session_state.progress_placeholder = None
|
|
|
|
else:
|
|
st.warning("Please enter a valid query.")
|
|
|
|
|
|
if st.session_state.matched_results:
|
|
|
|
|
|
col1, col2 = st.columns([1, 1])
|
|
with col1:
|
|
st.success(f"Matched Profiles: {len(st.session_state.matched_results)}")
|
|
with col2:
|
|
st.warning(f"Unmatched Profiles: {len(st.session_state.unmatched_results)}")
|
|
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1,2,1])
|
|
with col1:
|
|
if st.button("< Previous") and st.session_state.current_page > 0:
|
|
st.session_state.current_page -= 1
|
|
st.session_state.run_search = True
|
|
|
|
with col2:
|
|
st.write(f'Current Page {st.session_state.current_page + 1}')
|
|
|
|
with col3:
|
|
if st.button("Next >"):
|
|
st.session_state.current_page += 1
|
|
st.session_state.run_search = True
|
|
|
|
|
|
st.subheader("Candidates Profiles")
|
|
for idx, profiles in enumerate(st.session_state.matched_results, start=1):
|
|
with st.expander(f"{idx}. {profiles.get('fullName', 'Unknown')}"):
|
|
st.json(profiles)
|
|
|
|
with st.expander(
|
|
f"{idx}. {profiles.get('fullName', 'Unknown')} • Score: {profiles.get('score','None')} ", expanded=True
|
|
):
|
|
col1, col2 = st.columns([1, 2])
|
|
with col1:
|
|
image = profiles.get("profilePic")
|
|
|
|
temp_image = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRDVO09x_DXK3p4Mt1j08Ab0R875TdhsDcG2A&s"
|
|
|
|
if profiles.get("profilePic"):
|
|
st.image(profiles.get("profilePic"), width=150)
|
|
else:
|
|
st.image(temp_image, width=150)
|
|
|
|
st.markdown(f"**Location:** {profiles.get('addressWithCountry','-')}")
|
|
st.markdown(f"**Email:** {profiles.get('email','None')}")
|
|
|
|
|
|
experiences = profiles.get("experiences", [])
|
|
open_to_work = True
|
|
|
|
for exp in experiences:
|
|
caption = exp.get("caption", "")
|
|
if "Present" in caption:
|
|
open_to_work = False
|
|
break
|
|
|
|
st.markdown(f"**Open to Work:** {'False' if not open_to_work else 'True'}")
|
|
|
|
st.markdown(
|
|
f"**LinkedIn:** [LinkedIn]({profiles.get('linkedinUrl','')})"
|
|
)
|
|
|
|
|
|
with col2:
|
|
st.markdown(f"### {profiles.get('fullName')}")
|
|
if profiles.get("headline"):
|
|
st.markdown(f"*{profiles.get('headline')}*")
|
|
|
|
skills_raw = profiles.get("skills", [])
|
|
skill_titles = [
|
|
s.get("title")
|
|
for s in skills_raw
|
|
if isinstance(s, dict) and "title" in s
|
|
]
|
|
if skill_titles:
|
|
st.markdown("**Skills:** " + " • ".join(skill_titles[:10]))
|
|
|
|
if profiles.get("about"):
|
|
about = profiles.get("about")
|
|
st.markdown(
|
|
"**About:** " + (about[:250] + "..." if len(about) > 250 else about)
|
|
)
|
|
|
|
if profiles.get("experiences"):
|
|
st.markdown("**Experience**")
|
|
for exp in profiles["experiences"]:
|
|
title = exp.get("title", "")
|
|
subtitle = exp.get("subtitle") or exp.get("metadata", "")
|
|
caption = exp.get("caption", "")
|
|
|
|
|
|
st.write(f"• {title} at {subtitle} — {caption}")
|
|
|
|
|
|
if exp.get("description"):
|
|
for desc in exp["description"]:
|
|
if isinstance(desc, dict) and "text" in desc:
|
|
st.markdown(f" - {desc['text']}")
|
|
|
|
if profiles.get("is_complete"):
|
|
st.markdown(f'{profiles.get("is_complete")}')
|
|
|
|
if st.session_state.unmatched_results:
|
|
st.subheader("Unmatched Profiles")
|
|
for idx, profiles in enumerate(st.session_state.unmatched_results, start=1):
|
|
|
|
st.markdown(
|
|
f"{idx}, {profiles.get('fullName', 'Unknown')} - {profiles.get('addressWithCountry', 'Unknown')} [LINKEDIN]({profiles.get('linkedinUrl', 'Unknown')})"
|
|
)
|
|
|
|
|
|
|