File size: 6,793 Bytes
1457228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ff8b322
 
 
 
 
 
 
1457228
 
 
 
5ed032f
 
 
 
 
 
1457228
 
5ed032f
 
 
 
 
 
1457228
 
 
4237ea9
1457228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ff8b322
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import gradio as gr
import yaml
import os
from utils import process_ksas, process_analysis, process_core_content, process_summary, _cover_letter

def load_txt_file(txt_file):
    with open(txt_file, 'r', encoding='utf-8') as f:
        txt_str_contents = f.read()
        return txt_str_contents

def load_initial_resume(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            resume_data = yaml.safe_load(f)
            if not isinstance(resume_data, dict):
                print(f"Warning: Resume data is not a dictionary: {type(resume_data)}")
                return None
            return resume_data
    except Exception as e:
        print(f"Error loading initial resume: {e}")
        return None

with gr.Blocks() as demo:
    gr.Markdown('''# Resume Generator
                
                This tool generates a tailored resume and cover letter for a job description using Anthropic's LLM API.
                Instead of doing this in one shot, each step of the process is broken down into separate blocks to allow for manual edits between steps. Edit the outputs as you go to align with historical accuracy and with your writing preferences.
                Sample files have been provided for each input to help you get started. You can upload your own files to replace the samples.

                ## Instructions
                Upload all of the following files:
                - Job Description: a .txt file containing the job description.
                - Resume YAML: a .yaml file containing your work experience. See the provided template for formatting structure. Work experience bullets are grouped by job and category, where multiple variations of bullets may be stored to provide additional texture to the LLM.
                - Additional Resume Context: a .txt file containing additional resume details that you don't want to tailor, but want to share with the LLM as extra context.
                - Additional Freeform Context: a .txt file that elaborates on resume details to support the LLM. This information wouldn't normally be included on the resume. It can be unpolished writing and brutally honest background details that prevent the LLM from stretching the truth.
                - Resume Summary Writing Sample: a .txt file containing a writing sample of a bullet list summary for the top of your resume.
                - Cover Letter Writing Sample: a .txt file containing a writing sample of a cover letter.
                ''')

    ### File management

    job_desc_file = gr.File(label="Job Description", value="job_description.txt")
    resume_file = gr.File(label="Resume YAML", value="full_cv.yaml")
    extra_resume_context_file = gr.File(label="Additional Resume Context", value="additional_resume_context.txt")
    freeform_context_file = gr.File(label="Additional Freeform Context", value="freeform_context.txt")
    summary_sample_file = gr.File(label="Resume Summary Writing Sample", value="summary_sample.txt")
    cover_letter_sample_file = gr.File(label="Cover Letter Writing Sample", value="cover_letter_sample.txt")

    # Create States that store ontent as variables
    job_desc_content = gr.State(load_txt_file("job_description.txt"))
    resume_content = gr.State(load_initial_resume("full_cv.yaml"))
    extra_resume_context_content = gr.State(load_txt_file("additional_resume_context.txt"))
    freeform_context_content = gr.State(load_txt_file("freeform_context.txt"))
    summary_sample_content = gr.State(load_txt_file("summary_sample.txt"))
    cover_letter_sample_content = gr.State(load_txt_file("cover_letter_sample.txt"))

    # If a file is uploaded, automatically update the respective variable storing its contents
    job_desc_file.upload(fn=load_txt_file, inputs=[job_desc_file], outputs=[job_desc_content])
    resume_file.upload(fn=load_initial_resume, inputs=[resume_file], outputs=[resume_content])
    extra_resume_context_file.upload(fn=load_txt_file, inputs=[extra_resume_context_file], outputs=[extra_resume_context_content])
    freeform_context_file.upload(fn=load_txt_file, inputs=[freeform_context_file], outputs=[freeform_context_content])
    summary_sample_file.upload(fn=load_txt_file, inputs=[summary_sample_file], outputs=[summary_sample_content])
    cover_letter_sample_file.upload(fn=load_txt_file, inputs=[cover_letter_sample_file], outputs=[cover_letter_sample_content])

    # Extract KSAs
    ksas_content = gr.State(None)
    ksas_btn = gr.Button("1. Extract KSAs")
    ksas_output = gr.Textbox(label="Extracted KSAs", lines=10, interactive=True)
    ksas_btn.click(process_ksas, inputs=[job_desc_content], outputs=[ksas_output])
    ksas_output.change(lambda x: x, ksas_output, ksas_content)

    # Analyze first to determine whether or not to proceed
    analyze_btn = gr.Button("2. Analyze Fit")
    analysis_output = gr.Textbox(label="Analysis & Recommendation", lines=10, interactive=True)
    analyze_btn.click(process_analysis, inputs=[resume_content, job_desc_content, ksas_content, freeform_context_content], outputs=analysis_output)

    # Generate core resume content (experience bullets and technical skills)
    core_content_btn = gr.Button("3. Generate Core Content")

    exp_content = gr.State(None)
    exp_output = gr.Textbox(label="Experience Bullets", lines=10, interactive=True)
    exp_output.change(lambda x: x, exp_output, exp_content)

    skills_content = gr.State(None)
    skills_output = gr.Textbox(label="Technical Skills", lines=5, interactive=True)
    skills_output.change(lambda x: x, skills_output, skills_content)

    core_content_btn.click(process_core_content, inputs=[resume_content, job_desc_content, ksas_content, freeform_context_content], outputs=[exp_output, skills_output])

    # Generate summary
    summary_content = gr.State(None)
    summary_btn = gr.Button("4. Generate Summary")
    summary_output = gr.Textbox(label="Summary", lines=8, interactive=True)
    summary_btn.click(process_summary, inputs=[job_desc_content, ksas_content, exp_content, skills_content, extra_resume_context_content, summary_sample_content], outputs=[summary_output])
    summary_output.change(lambda x: x, summary_output, summary_content)

    # Generate cover letter
    cover_letter_content = gr.State(None)
    cover_letter_btn = gr.Button("5. Generate Cover Letter")
    cover_letter_output = gr.Textbox(label="Cover Letter", lines=10, interactive=True)
    cover_letter_btn.click(_cover_letter, inputs=[job_desc_content, ksas_content, exp_content, skills_content, extra_resume_context_content, summary_content, cover_letter_sample_content], outputs=[cover_letter_output])
    cover_letter_output.change(lambda x: x, cover_letter_output, cover_letter_content)


if __name__ == "__main__":
    demo.launch(share=True)