File size: 31,728 Bytes
2ea5df0
6dbc1cb
a7d0d3f
 
2ea5df0
7067897
 
cfafc8f
b6fbedf
a7d0d3f
 
2ea5df0
0426e30
7067897
a7d0d3f
 
 
 
 
 
 
7067897
e631c60
6dbc1cb
 
0426e30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7d0d5d
 
1b088ec
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7d0d5d
 
 
 
 
 
 
 
 
 
 
2a38076
 
 
 
1b088ec
 
 
 
e7d0d5d
1b088ec
 
 
e7d0d5d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a38076
 
 
 
e7d0d5d
 
 
 
 
 
1b088ec
 
a77ecf2
 
989441e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a38076
 
 
 
989441e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baf47ca
989441e
 
fe9a090
989441e
 
 
 
2ea5df0
 
 
 
 
 
 
 
e7d0d5d
 
 
 
6dbc1cb
 
 
 
 
a7d0d3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6dbc1cb
a7d0d3f
 
 
 
 
 
 
 
 
 
 
 
6dbc1cb
 
 
 
a7d0d3f
 
 
 
 
 
 
 
 
 
 
 
 
6dbc1cb
 
 
 
 
 
 
 
 
b6fbedf
351cc8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7d0d3f
2ea5df0
7067897
23e9332
7067897
 
 
 
6dbc1cb
 
 
 
 
 
351cc8b
 
7067897
 
a77ecf2
2ea5df0
23e9332
91ff4e5
 
23e9332
 
91ff4e5
 
23e9332
61568f9
0426e30
2ea5df0
989441e
2ea5df0
 
 
 
 
61568f9
 
2a38076
 
 
 
 
2ea5df0
0426e30
2ea5df0
 
 
0426e30
 
2ea5df0
 
 
 
 
 
 
79e8566
2ea5df0
26a4af1
2ea5df0
79e8566
2ea5df0
1b088ec
7067897
 
989441e
23e9332
 
2ea5df0
 
 
 
 
 
 
 
 
 
 
 
2a38076
 
 
 
 
 
 
 
2ea5df0
7067897
 
a77ecf2
 
1b088ec
a77ecf2
 
e7d0d5d
989441e
 
 
7067897
 
 
e40dd00
7067897
a77ecf2
 
 
 
 
 
7067897
e40dd00
 
 
 
 
 
 
1b088ec
e40dd00
 
 
1b088ec
e40dd00
b4e9775
e40dd00
 
 
 
 
9174d3f
e40dd00
 
 
 
989441e
e40dd00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61568f9
e40dd00
61568f9
 
 
e40dd00
 
 
 
 
61568f9
e40dd00
 
 
 
 
 
 
 
 
61568f9
 
 
 
e40dd00
 
61568f9
 
e40dd00
 
 
989441e
7067897
 
2ea5df0
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
from time import sleep
from os import getenv
from json import dumps
from requests import post

import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer
from huggingface_hub import InferenceClient
import spaces
# from openai import OpenAI
# import torch
from duckduckgo_search import DDGS
import re

# # Load the SmolLM model and tokenizer
# # model_name = "HuggingFaceTB/SmolLM2-360M-Instruct"
# model_name = "HuggingFaceTB/SmolLM3-3B" # "HuggingFaceTB/SmolLM2-1.7B-Instruct"
# model = AutoModelForCausalLM.from_pretrained(model_name)
# tokenizer = AutoTokenizer.from_pretrained(model_name)
# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# model.to(device)

FIREWORKS_API_TOKEN = getenv("FIREWORKS_TOKEN")


class DDGSSearchClient:
    def __init__(self, max_retries=4, timeout=35, backoff_factor=1):
        """
        Initialize a DDGSSearchClient instance.

        Args:
        - max_retries (int): The maximum number of retries. Defaults to 4.
        - timeout (int): The timeout for the DDGS client. Defaults to 35.
        - backoff_factor (int): The backoff factor for exponential backoff. Defaults to 1.
        """
        self.max_retries = max_retries
        self.timeout = timeout
        self.backoff_factor = backoff_factor
        self.search_client = DDGS(timeout=timeout)

    def search(self, query):
        """
        Perform a DDGS search with retry mechanism.

        Args:
        - query (str): The search query.

        Returns:
        - search_results: The results of the DDGS search.
        """
        for attempt in range(self.max_retries + 1):
            try:
                search_results = self.search_client.text(query)
                return search_results
            except Exception as e:
                if attempt < self.max_retries:
                    # Exponential backoff
                    delay = self.backoff_factor * (2 ** attempt)
                    print(f"Search failed (attempt {attempt + 1}/{self.max_retries + 1}). Retrying query: {query} in {delay} seconds...")
                    sleep(delay)
                else:
                    raise Exception(f"Search failed after {self.max_retries + 1} attempts. Giving up. Error from Search API: {e}.")


def clean_string(s):
    # Replace hyphens with spaces
    s = s.replace('-', ' ')
    
    # Remove anything other than A-Z, a-z, and " "
    s = re.sub('[^A-Za-z ]+', '', s)
    s.replace("-","").replace('—', '. ')
    
    return s


def replace_em_dashes(s):
    # Replace em dashes with a period
    s = s.replace('—', '. ')
    return s


def get_gap_assessment_prompt(job_and_company_info, resume):
    gap_assessment_prompt = f"""

# Do a gap assessment on this job role and resume looking for gaps that can be overcome by strategy in writing the resume and cover letter. For each gap, include a strategy for remediation. Ignore and do not mention any gap that is structural and for which improved writing will not be helpful. Consider things like:

1. Missing Keywords or Phrases

Consider: Does the resume lack important keywords or phrases from the job description?
Strategy: Integrate relevant keywords naturally into the resume and cover letter, especially in the summary, skills, and experience sections.
2. Unclear or Weakly Stated Achievements

Consider: Are the candidate’s accomplishments described in vague or generic terms?
Strategy: Rewrite bullet points to be achievement-oriented and quantify results where possible (e.g., “Increased sales by 20% in six months”).
3. Transferable Skills Not Highlighted

Consider: Are transferable skills from other roles or industries not clearly connected to the target job?
Strategy: Explicitly map transferable skills to job requirements in both the resume and cover letter, using language that mirrors the job posting.
4. Relevant Experience Buried or Underemphasized

Consider: Is relevant experience hidden or not given enough prominence?
Strategy: Reorder sections or bullet points to lead with the most relevant experiences, and use bolding or section headings to draw attention.
5. Lack of Tailoring to the Job

Consider: Is the resume too generic and not tailored to the specific job?
Strategy: Customize the summary/profile, skills, and experience sections to directly address the job requirements and company values.
6. Gaps in Demonstrating Required Soft Skills

Consider: Are important soft skills (e.g., leadership, communication) not clearly demonstrated?
Strategy: Add specific examples or brief stories in the resume and cover letter that illustrate these soft skills in action.
7. Unclear Career Progression or Role Context

Consider: Is it unclear how past roles relate to the target position?
Strategy: Add brief context or explanations in job descriptions or the cover letter to clarify how previous roles prepared the candidate for this job.
8. Formatting or Readability Issues

Consider: Is the resume difficult to scan or visually unappealing?
Strategy: Improve formatting for clarity and readability—use bullet points, clear headings, and consistent formatting.
9. Missing Education or Certification Details (If Present)

Consider: Are relevant credentials present but not highlighted?
Strategy: Move education or certifications to a more prominent position or call them out in the summary if they are key requirements.
10. Absence of Action Verbs

Consider: Are bullet points passive or lacking strong action verbs?
Strategy: Revise statements to start with powerful action verbs that convey impact and initiative.

11. Non-Linear Career Trajectory

Consider: Does the resume reflect a career path with frequent changes in industry, function, or direction, which may appear unfocused to employers?

Remediation Strategies: Craft a strong narrative in the summary section to tie diverse experiences together, emphasizing adaptability, learning agility, and the unique value brought by varied roles

. Use a skills-based or hybrid resume format to foreground transferable skills and relevant achievements rather than strict chronology

. In the cover letter, proactively explain the logic and benefits behind career pivots, highlighting how each transition contributed to your professional growth and how it aligns with the target role


12. Lack of Progression

Consider: Does the resume show little upward movement or increased responsibility over time?

Remediation Strategies: Highlight progression in responsibilities, even within the same job title or company, by emphasizing new projects, leadership roles, or skill development

. Quantify achievements and use action verbs to demonstrate impact and growth, even if formal titles did not change
. In the cover letter, address the context (e.g., company structure, personal choices) and focus on continuous learning or expanded scope within roles

13. Gaps in Employment History

Consider: Are there periods of unemployment or unaccounted time between jobs?

Remediation Strategies: Briefly explain significant gaps in the cover letter, framing them as periods of skill development, education, caregiving, or personal growth

. Include any freelance, contract, volunteer, or project work during gaps to demonstrate ongoing engagement and skill application

. Use a functional or hybrid resume format to de-emphasize strict chronology and spotlight relevant skills and accomplishments

. By strategically reframing these potential red flags, you can present a compelling, cohesive story that emphasizes your adaptability, continuous development, and readiness for the target role—turning perceived weaknesses into strengths

# This is the role description, company information, and results of company research, and 

    Company Name: {job_and_company_info['Company Name']}
    Company URL: {job_and_company_info['Company URL']}
    Job Title: {job_and_company_info['job_title']}
    Role Requirements: {job_and_company_info['Role Requirements']}
    Clean Job Description: {job_and_company_info['Clean Job Description']}
    Company Research:
        Company Values: {job_and_company_info['Company Research']['Company Values Summary']}
        Corporate Culture: {job_and_company_info['Company Research']['Corporate Culture Summary']}
        Leadership Team and Possible Key Opinion Leaders: {job_and_company_info['Company Research']['Leadership Team Summary']}
        Recent Company News: {job_and_company_info['Company Research']['Recent News Summary']}
        Company Competitive Advantages: {job_and_company_info['Company Research']['Competitive Advantages Summary']}
        Company History: {job_and_company_info['Company Research']['Company History Summary']}
        Company Mission Statement: {job_and_company_info['Company Research']['Mission Statement Summary']}
        Company Investor Relations Info: {job_and_company_info['Company Research']['Investor Relations Summary']}
        Summary of Info Gathered From Company Social Media: {job_and_company_info['Company Research']["Social Media Summary"]}


## This is the candidate's resume:

{resume}

# For this task, do not re-write their resume. Simply identify any of these described gaps or other potential gaps and strategies in re-writing and re-framing the resume which are likely to be effective in improving the odds of being offered an interview or job. 
 
"""
    return gap_assessment_prompt


def get_key_accomplishments_prompt(job_and_company_info, resume):

    key_skills_and_accomplaishments = f"""
# Extract relevant 1. 1-5 key accomplishments accomplishments and 2. all transferable skills from the applicant's resume that aligns with the job description.

# This is the role description, company information, and results of company research, and 

    Company Name: {job_and_company_info['Company Name']}
    Company URL: {job_and_company_info['Company URL']}
    Job Title: {job_and_company_info['job_title']}
    Role Requirements: {job_and_company_info['Role Requirements']}
    Clean Job Description: {job_and_company_info['Clean Job Description']}
    Company Research:
        Company Values: {job_and_company_info['Company Research']['Company Values Summary']}
        Corporate Culture: {job_and_company_info['Company Research']['Corporate Culture Summary']}
        Leadership Team and Possible Key Opinion Leaders: {job_and_company_info['Company Research']['Leadership Team Summary']}
        Recent Company News: {job_and_company_info['Company Research']['Recent News Summary']}
        Company Competitive Advantages: {job_and_company_info['Company Research']['Competitive Advantages Summary']}
        Company History: {job_and_company_info['Company Research']['Company History Summary']}
        Company Mission Statement: {job_and_company_info['Company Research']['Mission Statement Summary']}
        Company Investor Relations Info: {job_and_company_info['Company Research']['Investor Relations Summary']}
        Summary of Info Gathered From Company Social Media: {job_and_company_info['Company Research']["Social Media Summary"]}

## This is the candidate's resume:

{resume}

Please provide the following information in a concise and straightforward manner, without any extraneous comments or information that may confuse an LLM that this response will be piped to: Output the results in a simple format, with the key accomplishments first, followed by the transferable skills."
"""

    return key_skills_and_accomplaishments

def get_resume_prompt(job_and_company_info, resume, gap_assessment_result, key_accomplishments_and_skills_result):
    resume_prompt = f"""

# The applicant is applying for this role:




Company Name: {job_and_company_info['Company Name']}
    Company URL: {job_and_company_info['Company URL']}
    Job Title: {job_and_company_info['job_title']}
    Role Requirements: {job_and_company_info['Role Requirements']}
    Clean Job Description: {job_and_company_info['Clean Job Description']}

# Additional Information about the company to consider and leverage for strength and company - specificity:

    Company Values: {job_and_company_info['Company Research']['Company Values Summary']}
    Corporate Culture: {job_and_company_info['Company Research']['Corporate Culture Summary']}
    Leadership Team and Possible Key Opinion Leaders: {job_and_company_info['Company Research']['Leadership Team Summary']}
    Recent Company News: {job_and_company_info['Company Research']['Recent News Summary']}
    Company Competitive Advantages: {job_and_company_info['Company Research']['Competitive Advantages Summary']}
        Company History: {job_and_company_info['Company Research']['Company History Summary']}
        Company Mission Statement: {job_and_company_info['Company Research']['Mission Statement Summary']}
        Company Investor Relations Info: {job_and_company_info['Company Research']['Investor Relations Summary']}
        Summary of Info Gathered From Company Social Media: {job_and_company_info['Company Research']["Social Media Summary"]}

## This is the candidate's resume:

{resume}

## Consider this gap analysis

{gap_assessment_result}


## Also be sure to include these key accomplishments and skills:

{key_accomplishments_and_skills_result}

Please follow these best practices to make the resume optimized for the ATS visibility to the extent realistic and truthful.\n\n

1. **Replace Non-Standard Formatting**  
   - Convert tables, columns, or graphical elements in the resume text into plain-text lists using dashes or asterisks.  
   - Replace non-traditional bullet points with standard symbols (e.g., hyphens or circles).  

2. **Standardize Section Headings**  
   - Rename unconventional headings (e.g., "Career Highlights" → "Work Experience").  
   - Ensure headings match ATS-recognized categories: "Work Experience," "Education," "Skills," etc.  

3. **Normalize Date Formats**  
   - Convert all dates to a consistent format (e.g., "Month YYYY" or "MM/YYYY") throughout the resume.  

4. **Integrate Job-Specific Keywords**  
   - Extract exact keywords/phrases from the job description (especially verbs and skills) and insert them contextually into work experience bullet points.  
   - Prioritize keywords in the "Skills" section and within achievement-oriented statements (e.g., "Spearheaded [keyword] project resulting in [metric]").  

5. **Spell Out Acronyms**  
   - Identify and expand acronyms (e.g., "CRM" → "Customer Relationship Management (CRM)") unless the job description uses the abbreviation verbatim.  

6. **Quantify Achievements**  
   - Add metrics to vague statements in work experience (e.g., "Increased sales" → "Increased sales by 25% over 6 months").  
   - Prioritize percentages, time frames, and numerical outcomes for clarity and ATS scoring.  

7. **Tailor the Professional Summary**  
   - Rewrite the summary to mirror the job title, key qualifications, and industry-specific terms from the job description.  
   - Example: "Seeking a [Job Title] role at [Company] to leverage [X years] in [relevant skill] and [X%] success in [metric]."  

8. **Optimize Skills Section**  
   - List skills as a comma-separated list (no bullets) using the exact terminology from the job description.  
   - Include both hard skills (e.g., "Python," "Google Analytics") and soft skills (e.g., "Team Leadership") if mentioned in the job post.  

9. **Remove Hidden Text or Formatting Artifacts**  
   - Eliminate invisible text (e.g., white font keywords) or special characters (e.g., symbols in headers) that may confuse ATS parsing.  

10. **Contextualize Keywords**  
    - Ensure keywords appear in context (e.g., "Managed [keyword] project" vs. a standalone keyword in a skills list).  

11. **Active Voice Conversion**  
    - Replace passive language with active verbs (e.g., "Responsible for managing a team" → "Managed a team of X members").  

12. **Proofread for ATS-Critical Errors**  
    - Correct typos, misspellings, and inconsistent punctuation (especially in key fields like name, job titles, and skills).  
    - Remove accents or special characters from the name and contact details.  

13. **ATS Simulation Feedback Loop**  
   - Simulate ATS parsing by checking for missing keywords or formatting issues (e.g., fragmented sections caused by tables).  

14. **Customize for Company Culture**  
   - Analyze company metadata (e.g., industry, values) and align resume language with their priorities (e.g., "Agile methodology" for tech firms).  

15. **Achievement Over Responsibility**  
   - Replace generic duties with achievement-focused statements (e.g., "Handled customer service" → "Resolved 50+ customer inquiries daily, improving satisfaction by 30%").  

16. **Keyword Density Balance**  
   - Ensure keywords are distributed naturally across sections without overuse (e.g., 2-3 mentions of a critical keyword in work experience vs. 10 in a skills list).  
   - Instruct the user to paste this into a suitable word processor and use the result to build a resume. Save it in word, then in PDF (JaneDoe_Resume_CompanyName.pdf). Submit the PDF.

# Write this in a format that can be copied striaght in to Word and will be formatted properly, but in a way that the ATS can understand. Make no additional comments about the resume or how you did this.

"""
    return resume_prompt

class Role:
    def __init__(self, company_name, company_url, job_description, clean_job_description, job_title):
        self.company_name = company_name
        self.company_url = company_url
        self.job_description = job_description
        self.clean_job_description = clean_job_description
        self.job_title = job_title

class Applicant:
    def __init__(self, resume):
        self.resume = resume

# @spaces.GPU
# def write(inputs, max_new_tokens, do_sample=True, temperature=0.6, top_k=40, top_p=0.9, repetition_penalty=1.1):
#     _output = model.generate(**inputs, max_new_tokens=max_new_tokens, do_sample=True, temperature=0.6, top_k=40, top_p=0.9, repetition_penalty=1.1)
#     return _output

# def writing_task(prompt: str) -> str:
#     api_key = getenv("HF_TOKEN")
#     if not api_key:
#         raise ValueError("Huggingface token missing. Need to set HF_TOKEN, refer to https://discuss.huggingface.co/t/how-to-manage-user-secrets-and-api-keys/67948")
#     client = OpenAI(
#         base_url="https://router.huggingface.co/v1",
#         api_key = getenv("HF_TOKEN")
#     )

#     completion = client.chat.completions.create(
#         model="HuggingFaceTB/SmolLM3-3B:hf-inference",
#         messages=[
#             {
#                 "role": "user",
#                 "content": prompt
#             }
#         ],
#     )

#     raw_response_content = completion.choices[0].message.content
#     content_split = raw_response_content.split("</think>")
#     if len(content_split) > 1:
#         think = content_split[0]
#         content = "".join(content_split[1:])
#     else:
#         think = content_split[0]
#         content = "No data found."
    
#     return content

def writing_task(prompt: str) -> str:
    url = "https://api.fireworks.ai/inference/v1/chat/completions"
    model = "accounts/fireworks/models/qwen3-235b-a22b-thinking-2507"
    # "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
    payload = {
        "model": model,
        "max_tokens": 32768,
        "top_p": 1,
        "top_k": 40,
        "presence_penalty": 0,
        "frequency_penalty": 0,
        "temperature": 0.6,
        "messages": [
            {
                "role": "user",
                "content": prompt
            }
        ]
    }
    headers = {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": f"Bearer {FIREWORKS_API_TOKEN}"  # Replace with your actual API key
    }
    
    response = post(url, headers=headers, data=dumps(payload))
    response.raise_for_status()
    raw_response_content =\
            response.json()["choices"][0]["message"]["content"]
    print(f"Content with reasoning: {raw_response_content}")
    content_split = raw_response_content.split("</think>")
    if len(content_split) > 1:
        think = content_split[0]
        content = "".join(content_split[1:])
    else:
        think = content_split[0]
        content = "No data found."
    
    return content

def smol_writing_task(prompt: str) -> str:

    API_TOKEN_SMOLLM = getenv("HF_TOKEN")

    client = InferenceClient(
        provider="hf-inference",
        api_key=API_TOKEN_SMOLLM,
    )

    completion = client.chat.completions.create(
        model="HuggingFaceTB/SmolLM3-3B",
        messages=[
            {
                "role": "user",
                "content": prompt
            }
        ],
    )

    raw_content = completion.choices[0].message['content']
    print(f"Raw content: {raw_content}")

    content_split = raw_content.split("</think>")

    if len(content_split) > 1:
        think = content_split[0]
        content = "".join(content_split[1:])
    else:
        think = content_split[0]
        content = "No data found."
    
    return content

def smol_lm_jd_process(job_description, system_prompt, max_new_tokens=512):
    prompt = f"""<|im_start|>system
{system_prompt}<|im_end|>
<|im_start|>user
{job_description}<|im_end|>
<|im_start|>assistant
"""
    # inputs = tokenizer(prompt, return_tensors="pt").to(device)   
    # output = write(inputs, max_new_tokens=max_new_tokens)
    # response = tokenizer.decode(output[0], skip_special_tokens=False)
    # start_idx = response.find("<|im_start|>assistant")
    # end_idx = response.find("<|im_end|>", start_idx)
    # response = response[start_idx + len("<|im_start|>assistant\n"):end_idx].strip()
    # response = writing_task(prompt)
    response = smol_writing_task(prompt)
    return response

def process_job_description(company_name, company_url, job_description, resume):
    
    # Step 2: Extract key qualifications, skills, and requirements
    system_prompt_requirements = "Extract key qualifications, skills, and requirements from this job description. Output as bullet points. Remove benefits/salary, bragging about the company, and other fluff not relevant to the skills, qualifications, and job requirements. ONLY INCLUDE INFORMATION THAT TELLS THE USER WHAT SKILLS THE EMPLOYER SEEKS."
    role_requirements = smol_lm_jd_process(job_description, system_prompt_requirements)

    # Step 3: Create a concise summary of the job description
    system_prompt_summary = "Create a concise 150-200 word summary of this job description. Remove company bragging bragging about the company, and other fluff not relevant to the position and what is desired from the candidate. FOCUS ON ASPECTS THAT POINT THE USER IN WHAT THE EMPLOYER WANTS FROM A CANDIDATE IN TERMS OF SKILLS, ACCOMPLISHMENTS, AND SUCH"
    clean_job_description = smol_lm_jd_process(job_description, system_prompt_summary)

    system_prompt_get_job_title = "Extract only the job title from the following job description. Respond with nothing but the job title—no labels, no comments, no summaries, no locations, or extra text. If the title is unusually long or nonstandard, replace it with the most common, concise, and widely recognized job title for the role in plain text with only letters and numbers, remove any special characters nor punctuation no '\n' and no tabs, because any of these cause problems with downstream automated steps. Your answer must be 7 words or fewer. Acceptable examples may look like: 'Systems Analyst', 'marketing director', 'patient advocate III', ...\n\nThis is an excerpt of the job description:\n"
    extracted_job_title = clean_string(smol_lm_jd_process(job_description[:350], system_prompt_get_job_title, max_new_tokens=150)[:50].lower().replace("job","").replace("title","").replace("\n",""))

    role = Role(company_name, company_url, job_description, clean_job_description, extracted_job_title)

    # Step 4: Company Research
    searches = {
        "company_values": f"{role.company_name} company values",
        "corporate_culture": f"{role.company_name} corporate culture",
        "leadership_team": f"{role.company_name} leadership team members relevant to the role {role.job_title} role",
        "recent_news": f"{role.company_name} recent news relevant to the role {role.job_title} role",
        "competitive_advantages": f"{role.company_name} competitive advantages relevant to the role {role.job_title}",
        "company_history":f"History of {role.company_name}", 
        "mission_statement":f"{role.company_name} mission statement",
        "investor_relations":f"{role.company_name} investor relations info",
        "social_media":f"{role.company_name} social media"
    }
    search_client = DDGSSearchClient()
    search_results = {}
    for key, query in searches.items():
        print(f"searching {query}")
        results = search_client.search(query)
        search_results[key] = results
        sleep(3)


    # Summarize search results using SmolLM
    summaries = {}
    system_prompt_summary_search = "Summarize the following search results in 150 tokens or less."
    for key, results in search_results.items():
        print(f"Summarizing {key}")
        search_result_text = "\n".join([result['body'] for result in results])
        summary = smol_lm_jd_process(search_result_text, system_prompt_summary_search, max_new_tokens=250)
        summaries[key] = summary
        print(f"Summarizing {key} successful")

    job_and_company_info = {
        "Company Name": company_name,
        "Company URL": company_url,
        "job_title": extracted_job_title,
        "Original Job Description": job_description,
        "Role Requirements": role_requirements,
        "Clean Job Description": clean_job_description,
        "Company Research": {
            "Company Values Search Results": search_results['company_values'],
            "Company Values Summary": summaries['company_values'],
            "Corporate Culture Search Results": search_results['corporate_culture'],
            "Corporate Culture Summary": summaries['corporate_culture'],
            "Leadership Team Search Results": search_results['leadership_team'],
            "Leadership Team Summary": summaries['leadership_team'],
            "Recent News Search Results": search_results['recent_news'],
            "Recent News Summary": summaries['recent_news'],
            "Competitive Advantages Search Results": search_results['competitive_advantages'],
            "Competitive Advantages Summary": summaries['competitive_advantages'],
            "Company History Search Results":search_results['company_history'],
          "Company History Summary":summaries['company_history'],
          "Mission Statement Search Results":search_results['mission_statement'],
          "Mission Statement Summary":summaries['mission_statement'],
          "Investor Relations Search Results": search_results['investor_relations'],
          "Investor Relations Summary":summaries['investor_relations'],
          "Social Media Search Results":search_results['social_media'],
          "Social Media Summary":summaries['social_media']
        }
    }

    gap_assessment_prompt = get_gap_assessment_prompt(job_and_company_info, resume)
    return job_and_company_info, role, gap_assessment_prompt

def generate_key_accomplishments_and_skills_prompt(job_and_company_info, resume):
    return get_key_accomplishments_prompt(job_and_company_info, resume)

def generate_resume_prompt(job_and_company_info, resume, gap_assessment_result_output, key_accomplishments_and_skills_result_output):
    return get_resume_prompt(job_and_company_info, resume, gap_assessment_result_output, key_accomplishments_and_skills_result_output)

# Create the Gradio app
demo = gr.Blocks()


with demo:
    gr.Markdown("# Job Description and Resume Input")
    with gr.Row():
        company_name = gr.Textbox(label="Company Name")
        company_url = gr.Textbox(label="Company URL")
        job_description = gr.TextArea(label="Paste Job Description")
        resume = gr.TextArea(label="Paste Resume")

    # Intermediate State Components
    job_info_state = gr.State()
    gap_prompt_state = gr.State()
    gap_result_state = gr.State()
    key_prompt_state = gr.State()
    key_result_state = gr.State()
    resume_prompt_state = gr.State()

    gr.Markdown("## Generated Resume and Cover Letter")
    customized_resume = gr.TextArea(label="Generated Resume")
    cover_letter_prompt = gr.TextArea(label="Cover Letter Prompt")

    submit_btn = gr.Button("Submit")

    # Step 1: Process job description and generate gap assessment prompt
    submit_btn.click(
        fn=process_job_description,
        inputs=[company_name, company_url, job_description, resume],
        outputs=[job_info_state, gr.State(), gap_prompt_state]
    ).then(
        # Step 2: Send gap assessment prompt to Smollm3-3B
        fn=smol_writing_task,
        inputs=gap_prompt_state,
        outputs=gap_result_state
    ).then(
        # Step 3: Generate key accomplishments prompt
        fn=generate_key_accomplishments_and_skills_prompt,
        inputs=[job_info_state, resume],
        outputs=key_prompt_state
    ).then(
        # Step 4: Send key accomplishments prompt to Smollm3-3B
        fn=smol_writing_task,
        inputs=key_prompt_state,
        outputs=key_result_state
    ).then(
        # Step 5: Generate resume prompt using results and send to Qwen3
        fn=get_resume_prompt,
        inputs=[job_info_state, resume, gap_result_state, key_result_state],
        outputs=resume_prompt_state
    ).then(
        fn=writing_task,
        inputs=resume_prompt_state,
        outputs=customized_resume
    ).then(
        # Step 6: Generate cover letter prompt (user still needs to run this manually)
        fn=lambda job_info, customized_resume: f"""
# Write a 3-paragraph cover letter with:
1. Personalized greeting (use {job_info['Company Research']['Leadership Team Summary']} if available)
2. Opening paragraph: Role you're applying for and excitement
3. Body: Match a few of the top qualifications to job requirements
4. Consider this company research and company metadata as you are writing this: 
    Company Name: {job_info['Company Name']}
    Company URL: {job_info['Company URL']}
    Job Title: {job_info['job_title']}
    Role Requirements: {job_info['Role Requirements']}
    Clean Job Description: {job_info['Clean Job Description']}
    Company Research:
        Company Values: {job_info['Company Research']['Company Values Summary']}
        Corporate Culture: {job_info['Company Research']['Corporate Culture Summary']}
        Leadership Team and Possible Key Opinion Leaders: {job_info['Company Research']['Leadership Team Summary']}
        Recent Company News: {job_info['Company Research']['Recent News Summary']}
        Company Competitive Advantages: {job_info['Company Research']['Competitive Advantages Summary']}
        Company History: {job_info['Company Research']['Company History Summary']}
        Company Mission Statement: {job_info['Company Research']['Mission Statement Summary']}
        Company Investor Relations Info: {job_info['Company Research']['Investor Relations Summary']}
        Summary of Info Gathered From Company Social Media: {job_info['Company Research']["Social Media Summary"]}
5. Closing: Express reasonable enthusiasm and request discussion. Don't overdo it and make the enthusiasm not sound feigned or disingenuous.
## This is the resume for this job to consider in writing this:
{customized_resume}
""",
        inputs=[job_info_state, customized_resume],
        outputs=cover_letter_prompt
    )





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