Spaces:
Runtime error
Runtime error
| import os | |
| import gradio as gr | |
| import base64 | |
| import io | |
| import re | |
| from PIL import Image | |
| import fitz # PyMuPDF | |
| # --- CONFIGURATION --- | |
| # Récupère la clé HF depuis les secrets du Space (sécurisé) | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| if not HF_TOKEN: | |
| raise ValueError( | |
| "❌ Token HF manquant. Ajoutez HF_TOKEN dans les 'Repository Secrets' de votre Space." | |
| ) | |
| MODEL_ID = "google/gemma-4-E4B-it" | |
| API_URL = f"https://api-inference.huggingface.co/models/{MODEL_ID}/v1/chat/completions" | |
| # --- FONCTIONS AUXILIAIRES --- | |
| def encode_image(image: Image.Image) -> str: | |
| """Convertit une image PIL en base64 JPEG.""" | |
| buffer = io.BytesIO() | |
| image.save(buffer, format="JPEG", quality=85) | |
| return base64.b64encode(buffer.getvalue()).decode() | |
| def pdf_to_image(pdf_path: str) -> Image.Image: | |
| """Convertit la première page d'un PDF en image PIL.""" | |
| doc = fitz.open(pdf_path) | |
| page = doc.load_page(0) | |
| mat = fitz.Matrix(150 / 72, 150 / 72) # 150 DPI — lisible et léger | |
| pix = page.get_pixmap(matrix=mat) | |
| img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) | |
| doc.close() | |
| return img | |
| def sanitize_filename(name: str) -> str: | |
| """Nettoie le nom retourné par le modèle pour en faire un nom de fichier valide.""" | |
| name = name.strip().strip('"').strip("'") | |
| name = re.sub(r"[^\w\s\-]", "", name) | |
| name = re.sub(r"\s+", "_", name) | |
| return name[:80] if name else "document_sans_nom" | |
| # --- FONCTION PRINCIPALE --- | |
| def generate_filename(file, custom_prompt: str): | |
| """ | |
| Génère un nom de fichier à partir d'une image ou d'un PDF (première page). | |
| Args: | |
| file : Fichier uploadé (image ou PDF). | |
| custom_prompt : Prompt/format personnalisé pour le nom. | |
| Returns: | |
| tuple: (aperçu PIL Image, nom généré str) | |
| """ | |
| # 1. Vérification du fichier | |
| if file is None: | |
| return None, "❌ Veuillez uploader un fichier (image ou PDF)." | |
| try: | |
| # 2. Traitement selon le type de fichier | |
| if file.name.lower().endswith(".pdf"): | |
| image = pdf_to_image(file.name) | |
| else: | |
| image = Image.open(file.name).convert("RGB") | |
| # 3. Encodage base64 | |
| image_base64 = encode_image(image) | |
| # 4. Prompt | |
| if custom_prompt and custom_prompt.strip(): | |
| prompt = custom_prompt.strip() | |
| else: | |
| prompt = ( | |
| "Analyse ce document. Génère un nom de fichier court et descriptif " | |
| "en snake_case (3 à 6 mots max).\n" | |
| "Réponds UNIQUEMENT par le nom de fichier, sans extension, sans explication." | |
| ) | |
| # 5. Appel à l'Inference API HF (même structure que ton ancien script Mistral) | |
| import requests | |
| response = requests.post( | |
| API_URL, | |
| headers={ | |
| "Authorization": f"Bearer {HF_TOKEN}", | |
| "Content-Type": "application/json", | |
| }, | |
| json={ | |
| "model": MODEL_ID, | |
| "messages": [ | |
| { | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": prompt}, | |
| { | |
| "type": "image_url", | |
| "image_url": { | |
| "url": f"data:image/jpeg;base64,{image_base64}" | |
| }, | |
| }, | |
| ], | |
| } | |
| ], | |
| "max_tokens": 60, | |
| "temperature": 0.1, | |
| }, | |
| timeout=60, | |
| ) | |
| # 6. Traitement de la réponse | |
| if response.status_code == 200: | |
| raw = response.json()["choices"][0]["message"]["content"].strip() | |
| clean = sanitize_filename(raw) | |
| return image, f"{clean}.pdf" | |
| else: | |
| return image, f"❌ Erreur API HF : {response.status_code} — {response.text}" | |
| except Exception as e: | |
| return None, f"❌ Erreur : {str(e)}" | |
| # --- INTERFACE GRADIO --- | |
| with gr.Blocks(title="PDF Auto-Namer") as demo: | |
| gr.Markdown(""" | |
| # 📄 PDF Auto-Namer | |
| **Basé sur Gemma 4 E4B (multimodal)** via Hugging Face Inference API | |
| Upload un **fichier image ou PDF** → la première page est analysée → un nom est généré. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| file_input = gr.File( | |
| label="Document (Image ou PDF)", | |
| file_types=[".jpg", ".jpeg", ".png", ".pdf"], | |
| height=200, | |
| ) | |
| prompt_input = gr.Textbox( | |
| label="Format personnalisé (optionnel)", | |
| placeholder='Ex: "Format: Contrat_[TYPE]_[CLIENT]_[JJMMAAAA]"', | |
| lines=3, | |
| ) | |
| btn = gr.Button("🚀 Générer le nom", variant="primary") | |
| with gr.Column(): | |
| preview = gr.Image(label="👁️ Aperçu — Page 1", type="pil") | |
| output = gr.Textbox(label="📝 Nom de fichier généré", lines=2) | |
| btn.click( | |
| fn=generate_filename, | |
| inputs=[file_input, prompt_input], | |
| outputs=[preview, output], | |
| ) | |
| gr.Markdown( | |
| "---\n" | |
| "ℹ️ *Modèle : `google/gemma-4-E4B-it` · " | |
| "La clé `HF_TOKEN` doit être définie dans les Secrets du Space.*" | |
| ) | |
| demo.launch(show_error=True) |