Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

#config related to logging must have prefix LOG_
LOG_LEVEL = 'ERROR'
LOG_SELENIUM_LEVEL = ERROR
LOG_TO_FILE = False
LOG_TO_CONSOLE = False

Expand All @@ -17,6 +16,6 @@
JOB_MIN_APPLICATIONS = 1

LLM_MODEL_TYPE = 'openai'
LLM_MODEL = 'gpt-4o-mini'
LLM_MODEL = 'gpt-5-mini'
# Only required for OLLAMA models
LLM_API_URL = ''
6 changes: 2 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
import click
import inquirer
import yaml
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
import re
from src.utils.patch_lib_resume_builder import apply_patch
apply_patch()
from src.libs.resume_and_cover_builder import ResumeFacade, ResumeGenerator, StyleManager
from src.resume_schemas.job_application_profile import JobApplicationProfile
from src.resume_schemas.resume import Resume
Expand Down
8 changes: 3 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ langchain-ollama==0.1.3
langchain-openai==0.1.17
langchain-text-splitters==0.2.2
langsmith==0.1.93
Levenshtein==0.25.1
Levenshtein>=0.26.0
loguru==0.7.2
openai==1.37.1
pdfminer.six==20221105
Expand All @@ -23,9 +23,7 @@ python-dotenv~=1.0.1
PyYAML~=6.0.2
regex==2024.7.24
reportlab==4.2.2
selenium==4.9.1
webdriver-manager==4.0.2
playwright>=1.40.0
pytest
pytest-mock
pytest-cov
undetected-chromedriver==3.5.5
pytest-cov
16 changes: 7 additions & 9 deletions src/libs/resume_and_cover_builder/resume_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def __init__(self, api_key, style_manager, resume_generator, resume_object, outp
self.resume_generator.set_resume_object(resume_object)
self.selected_style = None # Property to store the selected style

def set_driver(self, driver):
self.driver = driver
def set_driver(self, page):
self.driver = page

def prompt_user(self, choices: list[str], message: str) -> str:
"""
Expand Down Expand Up @@ -69,10 +69,8 @@ def prompt_for_text(self, message: str) -> str:


def link_to_job(self, job_url):
self.driver.get(job_url)
self.driver.implicitly_wait(10)
body_element = self.driver.find_element("tag name", "body")
body_element = body_element.get_attribute("outerHTML")
self.driver.goto(job_url, wait_until="networkidle")
body_element = self.driver.locator("body").inner_html()
self.llm_job_parser = LLMParser(openai_api_key=global_config.API_KEY)
self.llm_job_parser.set_body_html(body_element)

Expand Down Expand Up @@ -105,7 +103,7 @@ def create_resume_pdf_job_tailored(self) -> tuple[bytes, str]:
suggested_name = hashlib.md5(self.job.link.encode()).hexdigest()[:10]

result = HTML_to_PDF(html_resume, self.driver)
self.driver.quit()
self.driver.close()
return result, suggested_name


Expand All @@ -125,7 +123,7 @@ def create_resume_pdf(self) -> tuple[bytes, str]:

html_resume = self.resume_generator.create_resume(style_path)
result = HTML_to_PDF(html_resume, self.driver)
self.driver.quit()
self.driver.close()
return result

def create_cover_letter(self) -> tuple[bytes, str]:
Expand All @@ -149,5 +147,5 @@ def create_cover_letter(self) -> tuple[bytes, str]:


result = HTML_to_PDF(cover_letter_html, self.driver)
self.driver.quit()
self.driver.close()
return result, suggested_name
27 changes: 1 addition & 26 deletions src/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import sys
import logging
from loguru import logger
from selenium.webdriver.remote.remote_connection import LOGGER as selenium_logger

from config import LOG_LEVEL, LOG_SELENIUM_LEVEL, LOG_TO_CONSOLE, LOG_TO_FILE
from config import LOG_LEVEL, LOG_TO_CONSOLE, LOG_TO_FILE


def remove_default_loggers():
Expand Down Expand Up @@ -52,29 +51,5 @@ def get_log_filename():
)


def init_selenium_logger():
"""Initialize and configure selenium logger to write to selenium.log."""
log_file = "log/selenium.log"
os.makedirs(os.path.dirname(log_file), exist_ok=True)

selenium_logger.handlers.clear()

selenium_logger.setLevel(LOG_SELENIUM_LEVEL)

# Create file handler for selenium logger
file_handler = logging.handlers.TimedRotatingFileHandler(
log_file, when="D", interval=1, backupCount=5
)
file_handler.setLevel(LOG_SELENIUM_LEVEL)

# Define a simplified format for selenium logger entries
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)

# Add the file handler to selenium_logger
selenium_logger.addHandler(file_handler)


remove_default_loggers()
init_loguru_logger()
init_selenium_logger()
117 changes: 45 additions & 72 deletions src/utils/chrome_utils.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,66 @@
import os
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager # Import webdriver_manager
import urllib
from playwright.sync_api import sync_playwright
from src.logging import logger

def chrome_browser_options():
logger.debug("Setting Chrome browser options")
options = Options()
options.add_argument("--start-maximized")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--ignore-certificate-errors")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu") # Opzionale, utile in alcuni ambienti
options.add_argument("window-size=1200x800")
options.add_argument("--disable-background-timer-throttling")
options.add_argument("--disable-backgrounding-occluded-windows")
options.add_argument("--disable-translate")
options.add_argument("--disable-popup-blocking")
options.add_argument("--no-first-run")
options.add_argument("--no-default-browser-check")
options.add_argument("--disable-logging")
options.add_argument("--disable-autofill")
options.add_argument("--disable-plugins")
options.add_argument("--disable-animations")
options.add_argument("--disable-cache")
options.add_argument("--incognito")
options.add_argument("--allow-file-access-from-files") # Consente l'accesso ai file locali
options.add_argument("--disable-web-security") # Disabilita la sicurezza web
logger.debug("Using Chrome in incognito mode")

return options
_playwright = None
_browser = None

def init_browser() -> webdriver.Chrome:

def _get_browser():
"""Get or create a shared Playwright browser instance."""
global _playwright, _browser
if _browser is None or not _browser.is_connected():
logger.debug("Launching Playwright Chromium browser")
_playwright = sync_playwright().start()
_browser = _playwright.chromium.launch(headless=True)
return _browser


def init_browser():
"""Initialize and return a new Playwright browser page."""
try:
options = chrome_browser_options()
# Use webdriver_manager to handle ChromeDriver
driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options)
logger.debug("Chrome browser initialized successfully.")
return driver
browser = _get_browser()
page = browser.new_page(viewport={"width": 1200, "height": 800})
logger.debug("Playwright browser page initialized successfully.")
return page
except Exception as e:
logger.error(f"Failed to initialize browser: {str(e)}")
raise RuntimeError(f"Failed to initialize browser: {str(e)}")



def HTML_to_PDF(html_content, driver):
def HTML_to_PDF(html_content, page):
"""
Converte una stringa HTML in un PDF e restituisce il PDF come stringa base64.
Convert an HTML string to PDF and return it as a base64 string.

:param html_content: Stringa contenente il codice HTML da convertire.
:param driver: Istanza del WebDriver di Selenium.
:return: Stringa base64 del PDF generato.
:raises ValueError: Se l'input HTML non è una stringa valida.
:raises RuntimeError: Se si verifica un'eccezione nel WebDriver.
:param html_content: HTML string to convert.
:param page: Playwright page instance.
:return: Base64 string of the generated PDF.
"""
# Validazione del contenuto HTML
import base64

if not isinstance(html_content, str) or not html_content.strip():
raise ValueError("Il contenuto HTML deve essere una stringa non vuota.")
raise ValueError("HTML content must be a non-empty string.")

# Codifica l'HTML in un URL di tipo data
encoded_html = urllib.parse.quote(html_content)
data_url = f"data:text/html;charset=utf-8,{encoded_html}"

try:
driver.get(data_url)
# Attendi che la pagina si carichi completamente
time.sleep(2) # Potrebbe essere necessario aumentare questo tempo per HTML complessi
page.goto(data_url, wait_until="networkidle")

# Esegue il comando CDP per stampare la pagina in PDF
pdf_base64 = driver.execute_cdp_cmd("Page.printToPDF", {
"printBackground": True, # Includi lo sfondo nella stampa
"landscape": False, # Stampa in verticale (False per ritratto)
"paperWidth": 8.27, # Larghezza del foglio in pollici (A4)
"paperHeight": 11.69, # Altezza del foglio in pollici (A4)
"marginTop": 0.8, # Margine superiore in pollici (circa 2 cm)
"marginBottom": 0.8, # Margine inferiore in pollici (circa 2 cm)
"marginLeft": 0.5, # Margine sinistro in pollici (circa 1.27 cm)
"marginRight": 0.5, # Margine destro in pollici (circa 1.27 cm)
"displayHeaderFooter": False, # Non visualizzare intestazioni e piè di pagina
"preferCSSPageSize": True, # Preferire le dimensioni della pagina CSS
"generateDocumentOutline": False, # Non generare un sommario del documento
"generateTaggedPDF": False, # Non generare PDF taggato
"transferMode": "ReturnAsBase64" # Restituire il PDF come stringa base64
})
return pdf_base64['data']
pdf_bytes = page.pdf(
print_background=True,
landscape=False,
width="8.27in",
height="11.69in",
margin={
"top": "0.8in",
"bottom": "0.8in",
"left": "0.5in",
"right": "0.5in",
},
prefer_css_page_size=True,
)
return base64.b64encode(pdf_bytes).decode("utf-8")
except Exception as e:
logger.error(f"Si è verificata un'eccezione WebDriver: {e}")
raise RuntimeError(f"Si è verificata un'eccezione WebDriver: {e}")
logger.error(f"Playwright PDF generation error: {e}")
raise RuntimeError(f"Playwright PDF generation error: {e}")
Loading