File size: 5,456 Bytes
2f1c164
a8584b7
 
 
2f1c164
a8584b7
2f1c164
 
 
 
 
 
 
 
 
a8584b7
2f1c164
 
a8584b7
6de23e9
 
2f1c164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6de23e9
 
2f1c164
 
 
 
 
 
 
 
 
6de23e9
2f1c164
 
6de23e9
2f1c164
6de23e9
2f1c164
 
6de23e9
2f1c164
6de23e9
 
 
2f1c164
6de23e9
a8584b7
2f1c164
 
6de23e9
 
2f1c164
6de23e9
2f1c164
a8584b7
6de23e9
2f1c164
a8584b7
2f1c164
a8584b7
2f1c164
 
 
 
 
6de23e9
2f1c164
 
a8584b7
 
 
2f1c164
 
a8584b7
 
2f1c164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a8584b7
2f1c164
a8584b7
6de23e9
2f1c164
a8584b7
2f1c164
 
 
a8584b7
2f1c164
6de23e9
a8584b7
2f1c164
 
a8584b7
6de23e9
2f1c164
 
a8584b7
2f1c164
 
 
a8584b7
6de23e9
a8584b7
 
2f1c164
6de23e9
 
2f1c164
6de23e9
2f1c164
a8584b7
2f1c164
 
a8584b7
2f1c164
6de23e9
a8584b7
2f1c164
 
a8584b7
6de23e9
 
2f1c164
 
 
 
 
 
 
 
6de23e9
 
3c429f1
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
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)