CoolFace
Apppublic

mbosse99/dev-candidate-search

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
app.py760 linesDownload Raw Back to root
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