| @@ -1,6 +1,13 @@ | |||
| import PyPDF2 | |||
| import requests | |||
| import json | |||
| from reportlab.lib.pagesizes import letter | |||
| from reportlab.lib.units import inch | |||
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer | |||
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |||
| from reportlab.lib.enums import TA_JUSTIFY | |||
| from reportlab.pdfbase import pdfmetrics | |||
| from reportlab.pdfbase.ttfonts import TTFont | |||
| import os | |||
| from datetime import datetime | |||
| @@ -14,6 +21,149 @@ TEMP_OUTPUT_TXT = "output_temp.txt" | |||
| FINAL_OUTPUT_PDF = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_V2.pdf") | |||
| FINAL_OUTPUT_TXT = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_V2.txt") | |||
| def extract_parameters_from_template(template_str): | |||
| """Extrait les paramètres du modèle à partir du template.""" | |||
| import re | |||
| parameters = {} | |||
| if not template_str or not isinstance(template_str, str): | |||
| return parameters | |||
| # Si la chaîne contient "parameters:", récupère ce qui suit | |||
| if 'parameters:' in template_str: | |||
| params_section = template_str.split('parameters:', 1)[1] | |||
| else: | |||
| # Sinon, utilise la chaîne directement (elle contient déjà les paramètres) | |||
| params_section = template_str | |||
| # Parse les lignes de paramètres | |||
| # Format: "stop "<end_of_turn>"" | |||
| # "temperature 0.1" | |||
| lines = params_section.split('\n') | |||
| for line in lines: | |||
| if not line.strip(): | |||
| continue | |||
| # Divise par le premier groupe d'espaces blancs | |||
| # Cela sépare la clé des valeurs avec leurs espaces | |||
| parts = line.split(None, 1) # split() avec maxsplit=1 divise sur les espaces | |||
| if len(parts) == 2: | |||
| param_name = parts[0].strip() | |||
| param_value = parts[1].strip() | |||
| parameters[param_name] = param_value | |||
| return parameters | |||
| def get_llm_model_info(model=OLLAMA_MODEL): | |||
| """ | |||
| Extrait les informations du modèle LLM depuis Ollama, y compris le nom depuis la ligne FROM du Modelfile. | |||
| @param model: Nom du modèle à interroger. | |||
| @type model: str | |||
| @return: Dictionnaire contenant les informations du modèle, ou None en cas d'erreur. | |||
| @rtype: dict | None | |||
| """ | |||
| try: | |||
| # Chemin vers le fichier Modelfile (supposé être dans le même répertoire que le script) | |||
| modelfile_path = os.path.join(os.path.dirname(__file__), "Modelfile") | |||
| # Initialisation de model_name | |||
| model_name = "none" | |||
| # Lecture du fichier Modelfile pour extraire le nom du modèle | |||
| if os.path.exists(modelfile_path): | |||
| with open(modelfile_path, 'r', encoding='utf-8') as file: | |||
| for line in file: | |||
| if line.strip().startswith('FROM '): | |||
| model_name = line.strip().split('FROM ')[1].strip() | |||
| break | |||
| # URL pour obtenir les informations du modèle | |||
| info_url = OLLAMA_URL.replace("/api/generate", "/api/show") | |||
| payload = {"name": model} | |||
| response = requests.post(info_url, json=payload) | |||
| if response.status_code == 200: | |||
| model_data = response.json() | |||
| # Gère le cas où model_data est une chaîne | |||
| if isinstance(model_data, str): | |||
| model_data = json.loads(model_data) | |||
| # Extrait les paramètres du template | |||
| parameters = model_data.get('parameters', '') | |||
| parsed_params = extract_parameters_from_template(parameters) | |||
| # Extraction du nom depuis la ligne FROM | |||
| modelfile_content = model_data.get('Modelfile', '') | |||
| # Extraction des informations principales | |||
| info = { | |||
| "temperature": parsed_params.get('temperature', model_data.get("temperature", "Not available")), | |||
| "name": model_name, | |||
| "num_ctx": parsed_params.get('num_ctx', "Not available"), | |||
| "top_k": parsed_params.get('top_k', "Not available"), | |||
| "top_p": parsed_params.get('top_p', "Not available"), | |||
| "system": model_data.get("system", "Not available"), | |||
| "modified_at": model_data.get("modified_at", "Not available"), | |||
| } | |||
| return info | |||
| else: | |||
| print(f"Erreur lors de la récupération du modèle : {response.text}") | |||
| return None | |||
| except Exception as e: | |||
| print(f"Erreur lors de l'accès aux informations du modèle : {e}") | |||
| return None | |||
| def display_llm_info(): | |||
| """Retourne les informations du modèle LLM formatées.""" | |||
| from datetime import datetime | |||
| info = get_llm_model_info(OLLAMA_MODEL) | |||
| if info: | |||
| # Formate la date en jj/mm/AAAA | |||
| modified_at = info.get('modified_at', 'Not available') | |||
| if modified_at and modified_at != 'Not available': | |||
| try: | |||
| # Parse la date ISO | |||
| date_obj = datetime.fromisoformat(modified_at.replace('Z', '+00:00')) | |||
| # Formate en jj/mm/AAAA | |||
| formatted_date = date_obj.strftime("%d/%m/%Y") | |||
| except: | |||
| formatted_date = modified_at | |||
| else: | |||
| formatted_date = 'Not available' | |||
| return f"LLM Modèle: {info['name']}<br//>\nDate de modification: {formatted_date}<br//>\nSystem: {info['system']}<br//>\nTemperature: {info['temperature']}" | |||
| else: | |||
| return "Informations du modèle non disponibles" | |||
| def register_unicode_font(): | |||
| """Enregistre une police TrueType qui supporte le cyrilique.""" | |||
| # Recherche une police système qui supporte le cyrilique | |||
| font_paths = [ | |||
| r"C:\Windows\Fonts\DejaVuSans.ttf", | |||
| r"C:\Windows\Fonts\Calibri.ttf", | |||
| r"C:\Windows\Fonts\arial.ttf", | |||
| ] | |||
| for font_path in font_paths: | |||
| if os.path.exists(font_path): | |||
| try: | |||
| pdfmetrics.registerFont(TTFont('UnicodeFont', font_path)) | |||
| return 'UnicodeFont' | |||
| except Exception as e: | |||
| print(f"Erreur lors de l'enregistrement de {font_path}: {e}") | |||
| # Si aucune police spéciale trouvée, utilise Helvetica par défaut | |||
| print("Aucune police Unicode trouvée, utilisation d'Helvetica") | |||
| return 'Helvetica' | |||
| # Charge ou initialise le checkpoint | |||
| def load_checkpoint(): | |||
| if os.path.exists(CHECKPOINT_FILE): | |||
| @@ -63,24 +213,81 @@ def send_to_ollama(text, target_lang=TARGET_LANGUAGE, model=OLLAMA_MODEL): | |||
| # Création du PDF final (inchangée) | |||
| def create_pdf_from_results(results, output_path): | |||
| from reportlab.lib.pagesizes import letter | |||
| from reportlab.lib.units import inch | |||
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer | |||
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |||
| from reportlab.lib.enums import TA_JUSTIFY | |||
| from reportlab.pdfbase import pdfmetrics | |||
| from reportlab.pdfbase.ttfonts import TTFont | |||
| doc = SimpleDocTemplate(output_path, pagesize=letter) | |||
| """Crée un PDF à partir des résultats de traduction.""" | |||
| doc = SimpleDocTemplate(output_path, pagesize=letter, topMargin=inch, bottomMargin=inch) | |||
| story = [] | |||
| # Enregistre une police qui supporte le cyrilique | |||
| font_name = register_unicode_font() | |||
| # Style personnalisé | |||
| styles = getSampleStyleSheet() | |||
| body_style = styles["BodyText"] | |||
| for idx, translation in results.items(): | |||
| story.append(Paragraph(translation, body_style)) | |||
| title_style = ParagraphStyle( | |||
| 'CustomTitle', | |||
| parent=styles['Heading1'], | |||
| fontSize=16, | |||
| textColor='#1f4788', | |||
| spaceAfter=0.3*inch, | |||
| alignment=TA_JUSTIFY, | |||
| fontName=font_name | |||
| ) | |||
| page_style = ParagraphStyle( | |||
| 'PageHeading', | |||
| parent=styles['Heading2'], | |||
| fontSize=12, | |||
| textColor='#1f4788', | |||
| spaceAfter=0.2*inch, | |||
| spaceBefore=0.2*inch, | |||
| fontName=font_name | |||
| ) | |||
| body_style = ParagraphStyle( | |||
| 'CustomBody', | |||
| parent=styles['BodyText'], | |||
| fontSize=11, | |||
| alignment=TA_JUSTIFY, | |||
| spaceAfter=0.2*inch, | |||
| fontName=font_name | |||
| ) | |||
| # Titre avec la langue cible | |||
| story.append(Paragraph(f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}", title_style)) | |||
| story.append(Paragraph(f"Document : {PDF_PATH}", title_style)) | |||
| story.append(Spacer(1, 0.2*inch)) | |||
| # Contenu | |||
| for page_num, translation in results.items(): | |||
| # Préserver la mise en page en convertissant les sauts de ligne | |||
| formatted_text = translation.replace("\n", "<br/>") | |||
| story.append(Paragraph(formatted_text, body_style)) | |||
| # story.append(Spacer(1, 0.1*inch)) | |||
| # Infos sur le LLM | |||
| story.append(Spacer(1, 0.2*inch)) | |||
| story.append(Paragraph(display_llm_info(), page_style)) | |||
| # Construction du PDF | |||
| doc.build(story) | |||
| print(f"PDF final généré : {output_path}") | |||
| print(f"PDF généré avec succès : {output_path}") | |||
| def create_txt_from_results(results, output_path): | |||
| """Crée un fichier TXT à partir des résultats de traduction.""" | |||
| OUTPUT_TXT_PATH = output_path.replace(".pdf", f".txt") # Chemin du fichier TXT de sortie | |||
| # Titre avec la langue cible | |||
| title_text = f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}" | |||
| with open(OUTPUT_TXT_PATH, 'w', encoding='utf-8') as txt_file: | |||
| txt_file.write(title_text + "\n\n") | |||
| # Contenu | |||
| for page_num, translation in results.items(): | |||
| # Préserver la mise en page en convertissant les sauts de ligne | |||
| txt_file.write(translation + "\n\n") | |||
| # Infos sur le LLM | |||
| txt_file.write("\n") | |||
| txt_file.write(display_llm_info() + "\n") | |||
| # Fonction principale | |||
| def main(): | |||
| @@ -114,7 +321,7 @@ def main(): | |||
| # Génération des fichiers finaux | |||
| save_temp_results(results) | |||
| create_pdf_from_results(results, FINAL_OUTPUT_PDF) | |||
| os.rename(TEMP_OUTPUT_TXT, FINAL_OUTPUT_TXT) | |||
| create_txt_from_results(results, FINAL_OUTPUT_TXT) | |||
| print("Traduction terminée !") | |||
| if __name__ == "__main__": | |||