devalender's picture
Update app.py
2f1c164 verified
Raw
History Blame Contribute Delete
5.46 kB
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)