mbosse99/dev-candidate-search
0
1import io2import os3import openai4import re5import sqlite36import base647import calendar8import json9import time10import uuid11from reportlab.platypus import SimpleDocTemplate, Paragraph12from reportlab.lib.styles import getSampleStyleSheet13import streamlit as st14from streamlit_js_eval import streamlit_js_eval15from langchain.embeddings.openai import OpenAIEmbeddings16from langchain.vectorstores.azuresearch import AzureSearch17from azure.storage.blob import BlobServiceClient18from azure.cosmos import CosmosClient, exceptions19from PyPDF2 import PdfReader20import openai21import sendgrid22from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition23from twilio.rest import Client24import ssl25ssl._create_default_https_context = ssl._create_unverified_context26 27openai.api_key = os.getenv("OPENAI_API_KEY")28openai.api_base = "https://tensora-oai.openai.azure.com/"29openai.api_type = "azure"30openai.api_version = "2023-05-15"31 32connection_string = os.getenv("CONNECTION")33blob_service_client = BlobServiceClient.from_connection_string(connection_string)34 35def upload_blob(pdf_name, json_data, pdf_data_jobdescription,pdf_data_cvs, pre_generated_bool, custom_questions):36 try:37 container_name = "jobdescriptions"38 # json_blob_name = f"{pdf_name}_jsondata.json"39 pdf_blob_name_jobdescription = f"{pdf_name}.pdf"40 41 container_client = blob_service_client.get_container_client(container_name)42 43 # json_blob_client = container_client.get_blob_client(json_blob_name)44 # json_blob_client.upload_blob(json_data.encode('utf-8'), overwrite=True)45 46 pdf_blob_client = container_client.get_blob_client(pdf_blob_name_jobdescription)47 pdf_blob_client.upload_blob(pdf_data_jobdescription, overwrite=True)48 49 upload_job_db_item(pdf_name,len(pdf_data_cvs),json.loads(json_data),pre_generated_bool, custom_questions)50 if pre_generated_bool:51 for i,question in enumerate(custom_questions):52 question_nr_for_id = i+153 question_id = pdf_name + "-question-nr-" + str(question_nr_for_id)+str(calendar.timegm(time.gmtime()))54 upload_question_db_item(question_id, pdf_name, question,st.session_state["job_string"])55 links = []56 names = []57 for i,cv in enumerate(pdf_data_cvs):58 59 cv_nr_for_id = i+160 cv_session_state_string = "cv-"+str(cv_nr_for_id)61 session_state_name = st.session_state["final_candidates"][i][0].metadata["name"]62 names.append(session_state_name)63 cv_id = pdf_name + "-cv-nr-" + str(cv_nr_for_id)+str(calendar.timegm(time.gmtime()))64 upload_db_item(session_state_name, json.loads(json_data), pdf_name, cv_id)65 pdf_blob_name_cv = f"{cv_id}.pdf"66 pdf_blob_client = container_client.get_blob_client(pdf_blob_name_cv)67 pdf_blob_client.upload_blob(pdf_data_cvs[i], overwrite=True)68 links.append("https://tensora.ai/workgenius/cv-evaluation2/?job="+cv_id)69 70 return links71 except Exception as e:72 print(f"Fehler beim Hochladen der Daten: {str(e)}")73 return []74 75def upload_job_db_item(id, number_of_applicants, data, pre_generated_bool, custom_questions):76 endpoint = "https://wg-candidate-data.documents.azure.com:443/"77 key = os.getenv("CONNECTION_DB")78 client = CosmosClient(endpoint, key)79 database = client.get_database_client("ToDoList")80 container = database.get_container_client("JobData")81 job_item = {82 "id": id,83 'partitionKey' : 'wg-job-data-v1',84 "title": data["title"],85 "number_of_applicants": number_of_applicants,86 "every_interview_conducted": False,87 "evaluation_email": data["email"],88 "question_one": data["question_one"],89 "question_two": data["question_two"],90 "question_three": data["question_three"],91 "pre_generated": pre_generated_bool,92 "custom_questions": custom_questions93 }94 try:95 # Fügen Sie das Element in den Container ein96 container.create_item(body=job_item)97 print("Eintrag erfolgreich in die Cosmos DB eingefügt. Container: Job Data")98 except exceptions.CosmosHttpResponseError as e:99 print(f"Fehler beim Schreiben in die Cosmos DB: {str(e)}")100 except Exception as e:101 print(f"Allgemeiner Fehler: {str(e)}")102 103def upload_db_item(name, data, job_description_id, cv_id):104 105 endpoint = "https://wg-candidate-data.documents.azure.com:443/"106 key = os.getenv("CONNECTION_DB")107 client = CosmosClient(endpoint, key)108 database = client.get_database_client("ToDoList")109 container = database.get_container_client("Items")110 candidate_item = {111 "id": cv_id,112 'partitionKey' : 'wg-candidate-data-v1',113 "name": name,114 "title": data["title"],115 "interview_conducted": False,116 "ai_summary": "",117 "evaluation_email": data["email"],118 "question_one": data["question_one"],119 "question_two": data["question_two"],120 "question_three": data["question_three"],121 "job_description_id": job_description_id,122 }123 124 try:125 # Fügen Sie das Element in den Container ein126 container.create_item(body=candidate_item)127 print("Eintrag erfolgreich in die Cosmos DB eingefügt. Container: Items(candidate Data)")128 except exceptions.CosmosHttpResponseError as e:129 print(f"Fehler beim Schreiben in die Cosmos DB: {str(e)}")130 except Exception as e:131 print(f"Allgemeiner Fehler: {str(e)}")132 133def upload_question_db_item(id, job_id, question, job_content):134 endpoint = "https://wg-candidate-data.documents.azure.com:443/"135 key = os.getenv("CONNECTION_DB")136 client = CosmosClient(endpoint, key)137 database = client.get_database_client("ToDoList")138 container = database.get_container_client("Questions")139 question_item = {140 "id": id,141 "partitionKey" : "wg-question-data-v1",142 "job_id": job_id,143 "question_content": question,144 "job_description": job_content,145 }146 try:147 # Fügen Sie das Element in den Container ein148 container.create_item(body=question_item)149 print("Eintrag erfolgreich in die Cosmos DB eingefügt. Container: Questions(Question Data)")150 except exceptions.CosmosHttpResponseError as e:151 print(f"Fehler beim Schreiben in die Cosmos DB: {str(e)}")152 except Exception as e:153 print(f"Allgemeiner Fehler: {str(e)}")154 155st.markdown(156"""157<style>158 [data-testid=column]{159 text-align: center;160 display: flex;161 align-items: center;162 justify-content: center;163 }164 h3{165 text-align: left;166 }167</style>168""",169 unsafe_allow_html=True,170)171 172with open("sys_prompt_frontend.txt") as f:173 sys_prompt = f.read()174with open("sys_prompt_job_optimization.txt") as j:175 sys_prompt_optimization = j.read()176 177def adjust_numbering(lst):178 return [f"{i + 1}. {item.split('. ', 1)[1]}" for i, item in enumerate(lst)]179 180def generate_candidate_mail(candidate, chat_link)-> str:181 candidate_first_name = candidate[0].metadata["name"].split(" ")[0]182 prompt = f"You are a professional recruiter who has selected a suitable candidate on the basis of a job description. Your task is to write two to three sentences about the applicant and explain why we think they are suitable for the job. The text will then be used in an e-mail to the applicant, so please address it to them. Please start the e-mail with 'Dear {candidate_first_name}'. I'll write the end of the mail myself."183 try:184 res = openai.ChatCompletion.create(185 engine="gpt-4",186 temperature=0.2,187 messages=[188 {189 "role": "system",190 "content": prompt,191 },192 {"role": "system", "content": "Job description: "+st.session_state["job_string"]+"; Resume: "+candidate[0].page_content}193 ],194 )195 # print(res.choices[0]["message"]["content"])196 except Exception as e:197 # Iterativ die Anfrage wiederholen und 200 Chars von hinten vom Resume weglassen198 max_retries = 5199 retries = 0200 while retries < max_retries:201 try:202 # Reduziere die Länge des Resume um 200 Chars von hinten203 candidate[0].page_content = candidate[0].page_content[:-200]204 205 # Neue Anfrage senden206 res = openai.ChatCompletion.create(207 engine="gpt-4",208 temperature=0.2,209 messages=[210 {211 "role": "system",212 "content": prompt,213 },214 {"role": "system", "content": "Job description: " + st.session_state["job_string"] + "; Resume: " + candidate[0].page_content}215 ],216 )217 # print(res.choices[0]["message"]["content"])218 219 # Wenn die Anfrage erfolgreich ist, den Schleifen-Iterator beenden220 break221 222 except Exception as e:223 # Bei erneuter Ausnahme die Schleife fortsetzen224 retries += 1225 if retries == max_retries:226 # Falls die maximale Anzahl von Wiederholungen erreicht ist, handle die Ausnahme entsprechend227 print("Max retries reached. Unable to get a valid response.")228 return "The CV was too long to generate a Mail"229 # Hier kannst du zusätzlichen Code für den Fall implementieren, dass die maximale Anzahl von Wiederholungen erreicht wurde.230 231 # Optional: Füge eine Wartezeit zwischen den Anfragen hinzu, um API-Beschränkungen zu respektieren232 time.sleep(1)233 234 output_string = f"""{res.choices[0]["message"]["content"]}235 236We have added the job description to the mail attachment. 237If you are interested in the position, please click on the following link, answer a few questions from our chatbot for about 10-15 minutes and we will get back to you.238 239Link to the interview chatbot: {chat_link}240 241Sincerely,242WorkGenius243"""244 print("Mail generated")245 return output_string246 247def generate_job_bullets(job)->str:248 prompt = "You are a professional recruiter whose task is to summarize the provided job description in the most important 5 key points. The key points should have a maximum of 8 words. The only thing you should return are the bullet points."249 try:250 res = openai.ChatCompletion.create(251 engine="gpt-4",252 temperature=0.2,253 messages=[254 {255 "role": "system",256 "content": prompt,257 },258 {"role": "system", "content": "Job description: "+job}259 ],260 )261 # print(res.choices[0]["message"]["content"])262 output_string = f"""{res.choices[0]["message"]["content"]}"""263 # print(output_string)264 return output_string265 except Exception as e:266 print(f"Fehler beim generieren der Bullets: {str(e)}")267 268def check_keywords_in_content(database_path, table_name, input_id, keywords):269 # Verbindung zur Datenbank herstellen270 conn = sqlite3.connect(database_path)271 cursor = conn.cursor()272 273 # SQL-Abfrage, um die Zeile mit der angegebenen ID abzurufen274 cursor.execute(f'SELECT * FROM {table_name} WHERE id = ?', (input_id,))275 276 # Ergebnis abrufen277 row = cursor.fetchone()278 279 # Wenn die Zeile nicht gefunden wurde, False zurückgeben280 if not row:281 conn.close()282 print("ID not found")283 return False284 285 # Überprüfen, ob die Keywords in der Spalte content enthalten sind (case-insensitive)286 content = row[1].lower() # Annahme: content ist die zweite Spalte, und wir wandeln ihn in Kleinbuchstaben um287 keywords_lower = [keyword.lower() for keyword in keywords]288 289 contains_keywords = all(keyword in content for keyword in keywords_lower)290 291 # Verbindung schließen292 conn.close()293 294 return contains_keywords295 296def clear_temp_candidates():297 if not st.session_state["final_candidates"]:298 print("i am cleared")299 st.session_state["docs_res"] = []300 301def load_candidates(fillup):302 with st.spinner("Load the candidates, this may take a moment..."):303 # print(st.session_state["job_string"])304 filter_string = ""305 query_string = "The following keywords must be included: " + text_area_params + " " + st.session_state["job_string"]306 checked_candidates = []307 db_path = 'cvdb.db'308 table_name = 'files'309 candidates_per_search = 100310 target_candidates_count = 10311 current_offset = 0312 313 if st.session_state["screened"]:314 filter_string = "amount_screenings gt 0 "315 if st.session_state["handed"]:316 if len(filter_string) > 0:317 filter_string += "and amount_handoffs gt 0 "318 else:319 filter_string += "amount_handoffs gt 0 "320 if st.session_state["placed"]:321 if len(filter_string) > 0:322 filter_string += "and amount_placed gt 0"323 else:324 filter_string += "amount_placed gt 0"325 # print(filter_string)326 if not fillup:327 while len(checked_candidates) < target_candidates_count:328 # # Führe eine similarity search durch und erhalte 100 Kandidaten329 # if st.session_state["search_type"]:330 # print("hybrid")331 # # raw_candidates = st.session_state["db"].hybrid_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)332 # raw_candidates = st.session_state["db"].hybrid_search_with_score(query_string, k=candidates_per_search+current_offset, filters=filter_string)333 # else:334 # print("similarity")335 # # raw_candidates = st.session_state["db"].similarity_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)336 # raw_candidates = st.session_state["db"].similarity_search_with_relevance_scores(query_string, k=candidates_per_search+current_offset, filters=filter_string)337 #"Similarity", "Hybrid", "Semantic ranking"338 if st.session_state["search_radio"] == "Similarity":339 print("similarity")340 # raw_candidates = st.session_state["db"].similarity_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)341 raw_candidates = st.session_state["db"].similarity_search_with_relevance_scores(query_string, k=candidates_per_search+current_offset, filters=filter_string)342 elif st.session_state["search_radio"] == "Hybrid":343 print("hybrid")344 # raw_candidates = st.session_state["db"].hybrid_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)345 raw_candidates = st.session_state["db"].hybrid_search_with_score(query_string, k=candidates_per_search+current_offset, filters=filter_string)346 elif st.session_state["search_radio"] == "Semantic ranking":347 print("Semantic ranking")348 print("Filter string"+filter_string)349 print("query"+query_string)350 print("offset: "+str(candidates_per_search+current_offset))351 # raw_candidates = st.session_state["db"].hybrid_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)352 try:353 raw_candidates = st.session_state["db"].semantic_hybrid_search_with_score_and_rerank(query_string, k=50, filters=filter_string)354 except Exception as e:355 print(f"Fehler beim laden der Kandidaten: {str(e)}")356 raw_candidates = []357 st.warning("Something went wrong. Please press 'Search candidates' again or reload the page.")358 for candidate in raw_candidates[current_offset:]:359 candidates_id = candidate[0].metadata["source"].split("/")[-1]360 keyword_bool = check_keywords_in_content(db_path, table_name, candidates_id, text_area_params.split(','))361 362 if keyword_bool:363 checked_candidates.append(candidate)364 365 # Überprüfe, ob die Zielanzahl erreicht wurde und breche die Schleife ab, wenn ja366 if len(checked_candidates) >= target_candidates_count:367 break368 369 current_offset += candidates_per_search370 if current_offset == 600:371 break372 # Setze die Ergebnisse in der Session State Variable373 st.session_state["docs_res"] = checked_candidates374 st.session_state["candidate_offset"] = current_offset375 if len(checked_candidates) == 0:376 st.error("No candidates can be found with these keywords. Please adjust the keywords and try again.", icon="🚨")377 else:378 # Setze die Zielanzahl auf 10379 target_candidates_count = 10380 381 current_offset = st.session_state["candidate_offset"]382 383 # Solange die Anzahl der überprüften Kandidaten kleiner als die Zielanzahl ist384 while len(st.session_state["docs_res"]) < target_candidates_count:385 # Führe eine similarity search durch und erhalte 100 Kandidaten386 if st.session_state["search_radio"] == "Similarity":387 print("similarity")388 # raw_candidates = st.session_state["db"].similarity_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)389 raw_candidates = st.session_state["db"].similarity_search_with_relevance_scores(query_string, k=candidates_per_search+current_offset, filters=filter_string)390 elif st.session_state["search_radio"] == "Hybrid":391 print("hybrid")392 # raw_candidates = st.session_state["db"].hybrid_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)393 raw_candidates = st.session_state["db"].hybrid_search_with_score(query_string, k=candidates_per_search+current_offset, filters=filter_string)394 elif st.session_state["search_radio"] == "Semantic ranking":395 print("Semantic ranking")396 print("Filter string"+filter_string)397 print("query"+query_string)398 print("offset: "+str(candidates_per_search+current_offset))399 # raw_candidates = st.session_state["db"].hybrid_search(query_string, k=candidates_per_search+current_offset, filters=filter_string)400 try:401 raw_candidates = st.session_state["db"].semantic_hybrid_search_with_score_and_rerank(query_string, k=50, filters=filter_string)402 except Exception as e:403 print(f"Fehler beim laden der Kandidaten: {str(e)}")404 raw_candidates = []405 st.warning("Something went wrong. Please press 'Search candidates' again or reload the page.")406 temp_offset_add = 0 407 for candidate in raw_candidates[current_offset:]:408 candidates_id = candidate[0].metadata["source"].split("/")[-1]409 keyword_bool = check_keywords_in_content(db_path, table_name, candidates_id, text_area_params.split(','))410 411 if keyword_bool:412 st.session_state["docs_res"].append(candidate)413 temp_offset_add += 1414 # Überprüfe, ob die Zielanzahl erreicht wurde und breche die Schleife ab, wenn ja415 if len(st.session_state["docs_res"]) >= target_candidates_count:416 st.session_state["candidate_offset"] = current_offset+temp_offset_add417 break418 419 current_offset += candidates_per_search420 if current_offset == 900:421 break422 423 # Wenn die Liste immer noch leer ist, zeige eine Fehlermeldung an424 if len(st.session_state["docs_res"]) == 0:425 st.warning("No more candidates can be found.", icon="🔥")426 427if "similarity_search_string" not in st.session_state:428 st.session_state["similarity_search_string"] = None429if "job_string" not in st.session_state:430 st.session_state["job_string"] = None431if "docs_res" not in st.session_state:432 st.session_state["docs_res"] = None433if "final_candidates" not in st.session_state:434 st.session_state["final_candidates"] = None435if "final_question_string" not in st.session_state:436 st.session_state["final_question_string"] = []437if "ai_questions" not in st.session_state:438 st.session_state["ai_questions"] = None439if "raw_job" not in st.session_state:440 st.session_state["raw_job"] = None441if "optimized_job" not in st.session_state:442 st.session_state["optimized_job"] = None443if "candidate_offset" not in st.session_state:444 st.session_state["candidate_offset"] = 0445if "db" not in st.session_state:446 embedder = OpenAIEmbeddings(deployment="text-embedding-ada-002", chunk_size=1)447 embedding_function = embedder.embed_query448 449 db = AzureSearch(450 index_name="wg-cvs-data",451 azure_search_endpoint=os.environ.get("AZURE_SEARCH_ENDPOINT"),452 azure_search_key=os.environ.get("AZURE_SEARCH_KEY"),453 embedding_function=embedding_function,454 # fields=fields455 )456 st.session_state["db"] = db457 458 459col1, col2 = st.columns([2, 1])460 461col1.title("Candidate Search")462col2.image("https://www.workgenius.com/wp-content/uploads/2023/03/WorkGenius_navy-1.svg")463 464st.write("Please upload the job description for which you would like candidates to be proposed.")465uploaded_file_jobdescription = st.file_uploader("Upload the job description:", type=["pdf"], key="job")466# col_file, col_clear = st.columns([6,1])467 468# with col_file:469# uploaded_file_jobdescription = st.file_uploader("Upload the job description:", type=["pdf"], key="job")470# with col_clear:471# if st.button("Clear", use_container_width=True):472# streamlit_js_eval(js_expressions="parent.window.location.reload()")473 474if st.session_state["job"]:475 if not st.session_state["job_string"]:476 if not st.session_state["optimized_job"]:477 with st.spinner("Optimizing the job description. This may take a moment..."):478 pdf_data_jobdescription = st.session_state["job"].read()479 pdf_data_jobdescription_string = ""480 pdf_reader_job = PdfReader(io.BytesIO(pdf_data_jobdescription))481 for page_num in range(len(pdf_reader_job.pages)):482 page = pdf_reader_job.pages[page_num]483 pdf_data_jobdescription_string += page.extract_text()484 # st.session_state["pdf_data_jobdescription"] = pdf_data_jobdescription activate and add sessio state if data is needed485 system_prompt_job = sys_prompt_optimization.format(job=pdf_data_jobdescription_string)486 try:487 res = openai.ChatCompletion.create(488 engine="gpt-4",489 temperature=0.2,490 messages=[491 {492 "role": "system",493 "content": system_prompt_job,494 },495 ],496 )497 # print(res.choices[0]["message"]["content"])498 output_string = f"""{res.choices[0]["message"]["content"]}"""499 st.session_state["optimized_job"] = output_string500 st.rerun()501 except Exception as e:502 print(f"Fehler beim generieren der optimierten JD: {str(e)}")503 st.error("An error has occurred. Please reload the page or contact the admin.", icon="🚨")504 # st.session_state["job_string"] = pdf_data_jobdescription_string505 # print(output_string)506 st.text_area("This is the AI-generated optimized job description. If necessary, change something to your liking:", value=st.session_state["optimized_job"], height=700, key="optimized_job_edited")507 if st.button("Accept the job description"):508 st.session_state["job_string"] = st.session_state["optimized_job_edited"]509 st.rerun()510 511# st.write("Switch from a similarity search (default) to a hybrid search (activated)")512# st.toggle("Switch Search", key="search_type")513 514st.radio("Select a search variant",options=["Similarity", "Hybrid", "Semantic ranking"], key="search_radio",on_change=clear_temp_candidates)515 516st.write("Activate the following toggles to filter according to the respective properties:")517col_screening, col_handoff, col_placed = st.columns([1,1,1])518with col_screening:519 st.toggle("Screened", key="screened")520with col_handoff:521 st.toggle("Handed over", key="handed")522with col_placed:523 st.toggle("Placed", key="placed")524 525text_area_params = st.text_area(label="Add additional search parameters, which are separated by commas (e.g. master, phd, web developer, spanish)")526 527submit = st.button("Search candidates",disabled= True if st.session_state["final_candidates"] else False)528 529 530if not st.session_state["job"] and submit:531 st.error("Please upload a job description to search for candidates")532if st.session_state["docs_res"] and submit:533 load_candidates(False)534if (st.session_state["job_string"] and submit) or st.session_state["docs_res"]:535 # if not st.session_state["job_string"]:536 # pdf_data_jobdescription = st.session_state["job"].read()537 # pdf_data_jobdescription_string = ""538 # pdf_reader_job = PdfReader(io.BytesIO(pdf_data_jobdescription))539 # for page_num in range(len(pdf_reader_job.pages)):540 # page = pdf_reader_job.pages[page_num]541 # pdf_data_jobdescription_string += page.extract_text()542 # # st.session_state["pdf_data_jobdescription"] = pdf_data_jobdescription activate and add sessio state if data is needed543 # st.session_state["job_string"] = pdf_data_jobdescription_string544 if not st.session_state["docs_res"]:545 load_candidates(False)546 if not st.session_state["final_candidates"]:547 for i,doc in enumerate(st.session_state["docs_res"]):548 # print(doc)549 cols_final = st.columns([6,1])550 with cols_final[1]:551 if st.button("Remove",use_container_width=True,key="btn_rm_cv_row_"+str(i)):552 # st.write(doc.page_content)553 st.session_state["docs_res"].pop(i)554 st.rerun()555 with cols_final[0]:556 # st.subheader(doc.metadata["source"])557 if st.session_state["search_radio"] == "Similarity":558 with st.expander(doc[0].metadata["name"]+" with a similarity percentage of: "+str(round(doc[1] * 100, 3))+ "%"):559 st.write(doc[0].page_content)560 elif st.session_state["search_radio"] == "Hybrid":561 with st.expander(doc[0].metadata["name"]+" with a hybrid search score of: "+str(round(doc[1] * 100, 3))):562 st.write(doc[0].page_content)563 elif st.session_state["search_radio"] == "Semantic ranking":564 with st.expander(doc[0].metadata["name"]+" with a rerank score of: "+str(round(doc[2] * 100, 3))):565 st.write(doc[0].page_content)566 if len(st.session_state["docs_res"])>=10: 567 if st.button("Accept candidates", key="accept_candidates_btn"): 568 print("hello")569 st.session_state["final_candidates"] = st.session_state["docs_res"].copy()570 st.rerun()571 else:572 col_accept, col_empty ,col_load_new = st.columns([2, 3, 2])573 with col_accept:574 if st.button("Accept candidates", key="accept_candidates_btn"): 575 print("hello")576 st.session_state["final_candidates"] = st.session_state["docs_res"].copy()577 st.rerun()578 with col_load_new:579 if st.button("Load new candidates", key="load_new_candidates"):580 print("loading new candidates")581 load_candidates(True)582 st.rerun()583 else:584 print("Now Questions")585 st.subheader("Your Candidates:")586 st.write(", ".join(candidate[0].metadata["name"] for candidate in st.session_state["final_candidates"]))587 # for i,candidate in enumerate(st.session_state["final_candidates"]):588 # st.write(candidate.metadata["source"])589 cv_strings = "; Next CV: ".join(candidate[0].page_content for candidate in st.session_state["final_candidates"])590 # print(len(cv_strings))591 system = sys_prompt.format(job=st.session_state["job_string"], resume=st.session_state["final_candidates"][0][0].page_content, n=15)592 if not st.session_state["ai_questions"]:593 try:594 # st.write("The questions are generated. This may take a short moment...")595 st.info("The questions are generated. This may take a short moment.", icon="ℹ️")596 with st.spinner("Loading..."):597 res = openai.ChatCompletion.create(598 engine="gpt-4",599 temperature=0.2,600 messages=[601 {602 "role": "system",603 "content": system,604 },605 ],606 )607 st.session_state["ai_questions"] = [item for item in res.choices[0]["message"]["content"].split("\n") if len(item) > 0]608 for i,q in enumerate(res.choices[0]["message"]["content"].split("\n")):609 st.session_state["disable_row_"+str(i)] = False610 st.rerun()611 except Exception as e:612 print(f"Fehler beim generieren der Fragen: {str(e)}")613 st.error("An error has occurred. Please reload the page or contact the admin.", icon="🚨")614 else:615 if len(st.session_state["final_question_string"]) <= 0:616 for i,question in enumerate(st.session_state["ai_questions"]):617 cols = st.columns([5,1])618 with cols[1]:619 # if st.button("Accept",use_container_width=True,key="btn_accept_row_"+str(i)):620 # print("accept")621 # pattern = re.compile(r"^[1-9][0-9]?\.")622 # questions_length = len(st.session_state["final_question_string"])623 # question_from_text_area = st.session_state["text_area_"+str(i)]624 # question_to_append = str(questions_length+1)+"."+re.sub(pattern, "", question_from_text_area)625 # st.session_state["final_question_string"].append(question_to_append)626 # st.session_state["disable_row_"+str(i)] = True627 # st.rerun() 628 if st.button("Delete",use_container_width=True,key="btn_del_row_"+str(i)):629 print("delete")630 st.session_state["ai_questions"].remove(question)631 st.rerun()632 with cols[0]:633 st.text_area(label="Question "+str(i+1)+":",value=question,label_visibility="collapsed",key="text_area_"+str(i),disabled=st.session_state["disable_row_"+str(i)])634 st.write("If you are satisfied with the questions, then accept them. You can still sort them afterwards.")635 if st.button("Accept all questions",use_container_width=True,key="accept_all_questions"):636 for i,question in enumerate(st.session_state["ai_questions"]):637 pattern = re.compile(r"^[1-9][0-9]?\.")638 questions_length = len(st.session_state["final_question_string"])639 question_from_text_area = st.session_state["text_area_"+str(i)]640 question_to_append = str(questions_length+1)+"."+re.sub(pattern, "", question_from_text_area)641 st.session_state["final_question_string"].append(question_to_append)642 st.session_state["disable_row_"+str(i)] = True643 st.rerun()644 for i,final_q in enumerate(st.session_state["final_question_string"]):645 cols_final = st.columns([5,1])646 with cols_final[1]:647 if st.button("Up",use_container_width=True,key="btn_up_row_"+str(i),disabled=True if i == 0 else False):648 if i > 0:649 # Tausche das aktuelle Element mit dem vorherigen Element650 st.session_state.final_question_string[i], st.session_state.final_question_string[i - 1] = \651 st.session_state.final_question_string[i - 1], st.session_state.final_question_string[i]652 st.session_state.final_question_string = adjust_numbering(st.session_state.final_question_string)653 st.rerun()654 if st.button("Down",use_container_width=True,key="btn_down_row_"+str(i), disabled=True if i == len(st.session_state["final_question_string"])-1 else False):655 if i < len(st.session_state.final_question_string) - 1:656 # Tausche das aktuelle Element mit dem nächsten Element657 st.session_state.final_question_string[i], st.session_state.final_question_string[i + 1] = \658 st.session_state.final_question_string[i + 1], st.session_state.final_question_string[i]659 st.session_state.final_question_string = adjust_numbering(st.session_state.final_question_string)660 st.rerun()661 with cols_final[0]:662 st.write(final_q)663 if len(st.session_state["final_question_string"])>0:664 st.text_input("Enter the email address to which the test emails should be sent:",key="recruiter_mail")665 st.text_input("Enter the phone number to which the test SMS should be sent (With country code, e.g. +1 for the USA or +49 for Germany):",key="recruiter_phone")666 st.text_input("Enter the job title:", key="job_title")667 if st.button("Submit", use_container_width=True):668 with st.spinner("Generation and dispatch of mails. This process may take a few minutes..."):669 sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API'))670 # Sender- und Empfänger-E-Mail-Adressen671 sender_email = "workgeniusjobevaluation@gmail.com"672 receiver_email = st.session_state["recruiter_mail"]673 print(receiver_email)674 subject = "Mails for potential candidates for the following position: "+st.session_state["job_title"]675 message = f"""Dear Recruiter,676 677enclosed in the text file you will find the e-mails that are sent to the potential candidates. 678 679The subject of the mail would be the following: Are you interested in a new position as a {st.session_state["job_title"]}? 680 681Sincerely,682Your Candidate-Search-Tool683"""684 # SendGrid-E-Mail erstellen685 message = Mail(686 from_email=sender_email,687 to_emails=receiver_email,688 subject=subject,689 plain_text_content=message,690 )691 data = {692 "title": st.session_state["job_title"],693 "email": st.session_state["recruiter_mail"],694 "question_one": "",695 "question_two": "",696 "question_three": "",697 }698 json_data = json.dumps(data, ensure_ascii=False)699 # Eine zufällige UUID generieren700 random_uuid = uuid.uuid4()701 702 # Die UUID als String darstellen703 uuid_string = str(random_uuid)704 705 pdf_name = uuid_string706 cvs_data = []707 temp_pdf_file = "candidate_pdf.pdf"708 for candidate in st.session_state["final_candidates"]:709 styles = getSampleStyleSheet()710 pdf = SimpleDocTemplate(temp_pdf_file)711 flowables = [Paragraph(candidate[0].page_content, styles['Normal'])]712 pdf.build(flowables)713 with open(temp_pdf_file, 'rb') as pdf_file:714 bytes_data = pdf_file.read()715 cvs_data.append(bytes_data)716 os.remove(temp_pdf_file)717 candidate_links = upload_blob(pdf_name, json_data, st.session_state["job"].read(),cvs_data,True,st.session_state["final_question_string"])718 mail_txt_string = ""719 for i, candidate in enumerate(st.session_state["final_candidates"]):720 if i > 0:721 mail_txt_string += "\n\nMail to the "+str(i+1)+". candidate: "+candidate[0].metadata["name"]+" "+candidate[0].metadata["candidateId"]+" \n\n"722 else:723 mail_txt_string += "Mail to the "+str(i+1)+". candidate: "+candidate[0].metadata["name"]+" "+candidate[0].metadata["candidateId"]+" \n\n"724 mail_txt_string += generate_candidate_mail(candidate,candidate_links[i])725 # Summary in eine TXT Datei schreiben726 mail_txt_path = "mailattachment.txt"727 with open(mail_txt_path, 'wb') as summary_file:728 summary_file.write(mail_txt_string.encode('utf-8'))729 # Resume als Anhang hinzufügen730 with open(mail_txt_path, 'rb') as summary_file:731 encode_file_summary = base64.b64encode(summary_file.read()).decode()732 summary_attachment = Attachment()733 summary_attachment.file_content = FileContent(encode_file_summary)734 summary_attachment.file_name = FileName('candidate_mails.txt')735 summary_attachment.file_type = FileType('text/plain')736 summary_attachment.disposition = Disposition('attachment')737 message.attachment = summary_attachment738 try:739 response = sg.send(message)740 print("E-Mail wurde erfolgreich gesendet. Statuscode:", response.status_code)741 os.remove("mailattachment.txt")742 except Exception as e:743 print("Fehler beim Senden der E-Mail:", str(e))744 st.error("Unfortunately the mail dispatch did not work. Please reload the page and try again or contact the administrator. ", icon="🚨")745 try:746 bullets = generate_job_bullets(st.session_state["job_string"])747 client = Client(os.getenv("TWILIO_SID"), os.getenv("TWILIO_API"))748 message_body = f"Dear candidate,\n\nare you interested in the following position: \n"+st.session_state["job_title"]+"\n\n"+bullets+"\n\nThen please answer with 'yes'\n\nSincerely,\n"+"WorkGenius"749 message = client.messages.create(750 to=st.session_state["recruiter_phone"],751 from_="+1 857 214 8753",752 body=message_body753 )754 755 print(f"Message sent with SID: {message.sid}")756 st.success('The dispatch and the upload of the data was successful')757 except Exception as e:758 st.error("Unfortunately the SMS dispatch did not work. Please reload the page and try again or contact the administrator. ", icon="🚨")759 print("Fehler beim Senden der SMS:", str(e))760 