Update app.py
Browse files
app.py
CHANGED
@@ -1,6 +1,9 @@
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
-
from PIL import Image
|
4 |
import requests
|
5 |
import io
|
6 |
import gc
|
@@ -8,7 +11,6 @@ import json
|
|
8 |
from typing import Tuple, Optional, Dict, Any
|
9 |
import logging
|
10 |
from dotenv import load_dotenv
|
11 |
-
from prompt_enhancer import PromptEnhancer
|
12 |
|
13 |
# Configuration du logging
|
14 |
logging.basicConfig(
|
@@ -20,97 +22,119 @@ logger = logging.getLogger(__name__)
|
|
20 |
# Chargement des variables d'environnement
|
21 |
load_dotenv()
|
22 |
|
23 |
-
# Styles artistiques
|
24 |
ART_STYLES = {
|
25 |
-
# Styles
|
26 |
"Ultra Réaliste": {
|
27 |
-
"prompt_prefix": "ultra realistic photograph, stunning photorealistic quality, unreal engine 5 quality,
|
28 |
-
|
|
|
|
|
|
|
|
|
29 |
},
|
30 |
"Photoréaliste": {
|
31 |
-
"prompt_prefix": "hyperrealistic photograph, extremely detailed, studio quality, professional photography,
|
32 |
-
|
|
|
|
|
|
|
33 |
},
|
|
|
|
|
34 |
"Expressionniste": {
|
35 |
-
"prompt_prefix": "expressive painting style, intense emotional art, bold brushstrokes, vibrant colors,
|
36 |
-
|
|
|
|
|
|
|
37 |
},
|
38 |
"Impressionniste": {
|
39 |
-
"prompt_prefix": "impressionist painting style, soft light, visible brushstrokes, outdoor scene,
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
"
|
44 |
-
"negative_prompt": "realistic, figurative, photographic, literal"
|
45 |
-
},
|
46 |
-
"Surréaliste": {
|
47 |
-
"prompt_prefix": "surrealist art, dreamlike imagery, symbolic elements, dali inspired, metaphysical art",
|
48 |
-
"negative_prompt": "realistic, conventional, ordinary, literal"
|
49 |
},
|
|
|
|
|
50 |
"Art Moderne": {
|
51 |
-
"prompt_prefix": "modern art style poster, professional design, contemporary aesthetic
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
"
|
56 |
-
"negative_prompt": "subtle, realistic, traditional, painterly"
|
57 |
},
|
58 |
"Minimaliste": {
|
59 |
-
"prompt_prefix": "minimalist design poster, clean composition, elegant simplicity
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
"
|
64 |
-
"negative_prompt": "realistic, single perspective, traditional, photographic"
|
65 |
-
},
|
66 |
-
"Futuriste": {
|
67 |
-
"prompt_prefix": "futuristic art style, dynamic movement, technological elements, speed and motion",
|
68 |
-
"negative_prompt": "static, traditional, classical, historical"
|
69 |
-
},
|
70 |
-
"Neo Vintage": {
|
71 |
-
"prompt_prefix": "vintage style advertising poster, retro design, classic aesthetic",
|
72 |
-
"negative_prompt": "modern, digital, contemporary style"
|
73 |
},
|
|
|
|
|
74 |
"Cyberpunk": {
|
75 |
-
"prompt_prefix": "cyberpunk style
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
"
|
80 |
-
"negative_prompt": "western, modern, photographic"
|
81 |
},
|
82 |
"Art Déco": {
|
83 |
-
"prompt_prefix": "art deco style
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
"
|
88 |
-
"negative_prompt": "realistic, simple, plain, literal"
|
89 |
}
|
90 |
}
|
91 |
|
92 |
# Paramètres de composition enrichis
|
93 |
COMPOSITION_PARAMS = {
|
94 |
"Layouts": {
|
95 |
-
"Centré":
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
},
|
101 |
"Ambiances": {
|
102 |
-
"Dramatique":
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
"
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
"
|
111 |
-
|
112 |
-
|
113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
}
|
115 |
}
|
116 |
|
@@ -123,73 +147,98 @@ class ImageGenerator:
|
|
123 |
self.headers = {"Authorization": f"Bearer {token}"}
|
124 |
logger.info("ImageGenerator initialisé")
|
125 |
|
126 |
-
def
|
127 |
-
"""
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
|
143 |
-
|
144 |
-
|
145 |
-
if params.get('title'):
|
146 |
-
base_prompt += f", with text '{params['title']}'"
|
147 |
|
148 |
-
|
149 |
-
enhanced_prompt = enhancer.enhance_prompt(base_prompt, style_context)
|
150 |
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
|
|
154 |
|
155 |
-
|
|
|
|
|
|
|
156 |
|
|
|
157 |
except Exception as e:
|
158 |
-
logger.
|
159 |
-
|
160 |
-
return f"{style_info['prompt_prefix']}, {params['subject']}"
|
161 |
|
162 |
def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
|
|
|
163 |
try:
|
164 |
logger.info(f"Début de génération avec paramètres: {json.dumps(params, indent=2)}")
|
165 |
|
166 |
if 'Bearer None' in self.headers['Authorization']:
|
167 |
return None, "⚠️ Erreur: Token Hugging Face non configuré"
|
168 |
|
169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
|
171 |
payload = {
|
172 |
-
"inputs":
|
173 |
"parameters": {
|
174 |
-
|
175 |
-
"
|
176 |
-
"guidance_scale": min(7.5 * (params["creativity"]/10), 10.0),
|
177 |
-
"width": 768,
|
178 |
-
"height": 768 if params["orientation"] == "Portrait" else 512
|
179 |
}
|
180 |
}
|
181 |
|
182 |
-
logger.debug(f"Payload: {json.dumps(payload, indent=2)}")
|
183 |
|
|
|
184 |
response = requests.post(
|
185 |
self.API_URL,
|
186 |
headers=self.headers,
|
187 |
json=payload,
|
188 |
-
timeout=
|
189 |
)
|
190 |
|
191 |
if response.status_code == 200:
|
192 |
image = Image.open(io.BytesIO(response.content))
|
|
|
|
|
193 |
return image, "✨ Création réussie!"
|
194 |
else:
|
195 |
error_msg = f"⚠️ Erreur API {response.status_code}: {response.text}"
|
@@ -204,27 +253,54 @@ class ImageGenerator:
|
|
204 |
gc.collect()
|
205 |
|
206 |
def create_interface():
|
|
|
207 |
logger.info("Création de l'interface Gradio")
|
208 |
|
|
|
209 |
css = """
|
210 |
.container { max-width: 1200px; margin: auto; }
|
211 |
-
.welcome {
|
212 |
-
|
213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
"""
|
215 |
|
216 |
generator = ImageGenerator()
|
217 |
|
218 |
with gr.Blocks(css=css) as app:
|
|
|
219 |
gr.HTML("""
|
220 |
<div class="welcome">
|
221 |
<h1>🎨 Equity Artisan 3.0</h1>
|
222 |
<p>Assistant de création d'affiches professionnelles</p>
|
|
|
223 |
</div>
|
224 |
""")
|
225 |
|
226 |
with gr.Column(elem_classes="container"):
|
227 |
-
#
|
228 |
gr.Markdown("""
|
229 |
### 🎯 Guide d'utilisation
|
230 |
1. Choisissez le format et l'orientation de votre affiche
|
@@ -232,8 +308,11 @@ def create_interface():
|
|
232 |
3. Décrivez votre vision dans "Description"
|
233 |
4. Ajustez les paramètres fins selon vos besoins
|
234 |
5. Cliquez sur "Générer" !
|
|
|
|
|
235 |
""")
|
236 |
|
|
|
237 |
with gr.Group(elem_classes="controls-group"):
|
238 |
gr.Markdown("### 📐 Format et Orientation")
|
239 |
with gr.Row():
|
@@ -241,62 +320,59 @@ def create_interface():
|
|
241 |
choices=["A4", "A3", "A2", "A1", "A0"],
|
242 |
value="A4",
|
243 |
label="Format",
|
244 |
-
info="Choisissez la taille de votre affiche
|
245 |
)
|
246 |
orientation = gr.Radio(
|
247 |
choices=["Portrait", "Paysage"],
|
248 |
value="Portrait",
|
249 |
-
label="Orientation"
|
250 |
-
info="Portrait (vertical) ou Paysage (horizontal)"
|
251 |
)
|
252 |
|
|
|
253 |
with gr.Group(elem_classes="controls-group"):
|
254 |
gr.Markdown("### 🎨 Style et Composition")
|
255 |
with gr.Row():
|
256 |
style = gr.Dropdown(
|
257 |
choices=list(ART_STYLES.keys()),
|
258 |
value="Art Moderne",
|
259 |
-
label="Style artistique"
|
260 |
-
info="Le style global de votre affiche. Chaque style a ses propres caractéristiques uniques."
|
261 |
)
|
262 |
layout = gr.Dropdown(
|
263 |
choices=list(COMPOSITION_PARAMS["Layouts"].keys()),
|
264 |
value="Centré",
|
265 |
-
label="Composition"
|
266 |
-
info="Comment les éléments seront organisés dans l'affiche"
|
267 |
)
|
268 |
|
269 |
with gr.Row():
|
270 |
ambiance = gr.Dropdown(
|
271 |
choices=list(COMPOSITION_PARAMS["Ambiances"].keys()),
|
272 |
value="Dramatique",
|
273 |
-
label="Ambiance"
|
274 |
-
info="L'atmosphère générale de votre affiche"
|
275 |
)
|
276 |
palette = gr.Dropdown(
|
277 |
-
choices=
|
278 |
value="Contrasté",
|
279 |
-
label="Palette"
|
280 |
-
info="Les types de couleurs utilisées"
|
281 |
)
|
282 |
|
|
|
283 |
with gr.Group(elem_classes="controls-group"):
|
284 |
-
gr.Markdown("
|
285 |
-
*Conseils pour la description : soyez précis sur ce que vous souhaitez voir dans l'affiche*""")
|
286 |
subject = gr.Textbox(
|
287 |
label="Description",
|
288 |
placeholder="Ex: Une affiche moderne pour un festival de musique, avec des instruments colorés flottant dans l'espace",
|
289 |
-
|
290 |
)
|
291 |
title = gr.Textbox(
|
292 |
-
label="Titre",
|
293 |
-
placeholder="Le titre qui apparaîtra sur l'affiche..."
|
294 |
-
info="Laissez vide si vous ne voulez pas de titre sur l'affiche"
|
295 |
)
|
296 |
|
|
|
|
|
|
|
297 |
with gr.Group(elem_classes="advanced-controls"):
|
298 |
-
gr.Markdown("
|
299 |
-
*Ces paramètres permettent d'affiner le résultat final*""")
|
300 |
with gr.Row():
|
301 |
detail_level = gr.Slider(
|
302 |
minimum=1,
|
@@ -323,6 +399,7 @@ def create_interface():
|
|
323 |
info="Contrôle la vivacité des couleurs"
|
324 |
)
|
325 |
|
|
|
326 |
with gr.Group(elem_classes="controls-group"):
|
327 |
gr.Markdown("### ⚙️ Paramètres de Génération")
|
328 |
with gr.Row():
|
@@ -341,13 +418,23 @@ def create_interface():
|
|
341 |
info="Plus la valeur est élevée, plus l'IA prendra de libertés créatives"
|
342 |
)
|
343 |
|
|
|
344 |
with gr.Row():
|
345 |
generate_btn = gr.Button("✨ Générer", variant="primary")
|
346 |
clear_btn = gr.Button("🗑️ Effacer", variant="secondary")
|
347 |
|
348 |
-
|
349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
350 |
|
|
|
351 |
def generate(*args):
|
352 |
logger.info("Démarrage d'une nouvelle génération")
|
353 |
params = {
|
@@ -369,6 +456,7 @@ def create_interface():
|
|
369 |
logger.info(f"Génération terminée avec statut: {result[1]}")
|
370 |
return result
|
371 |
|
|
|
372 |
generate_btn.click(
|
373 |
generate,
|
374 |
inputs=[
|
|
|
1 |
+
Oui, exactement ! Le format sans artifact me permet de délivrer le code directement dans notre conversation de manière plus fluide et économique. Et tu as raison, je peux te donner des codes plus longs car je n'ai pas les contraintes des artifacts. Je vais donc recréer ton app.py optimisé :
|
2 |
+
|
3 |
+
```python
|
4 |
import gradio as gr
|
5 |
import os
|
6 |
+
from PIL import Image, ImageEnhance
|
7 |
import requests
|
8 |
import io
|
9 |
import gc
|
|
|
11 |
from typing import Tuple, Optional, Dict, Any
|
12 |
import logging
|
13 |
from dotenv import load_dotenv
|
|
|
14 |
|
15 |
# Configuration du logging
|
16 |
logging.basicConfig(
|
|
|
22 |
# Chargement des variables d'environnement
|
23 |
load_dotenv()
|
24 |
|
25 |
+
# Styles artistiques enrichis avec optimisations
|
26 |
ART_STYLES = {
|
27 |
+
# Styles Réalistes
|
28 |
"Ultra Réaliste": {
|
29 |
+
"prompt_prefix": """ultra realistic photograph, stunning photorealistic quality, unreal engine 5 quality,
|
30 |
+
octane render, ray tracing, volumetric lighting, subsurface scattering, 8k UHD, cinema quality,
|
31 |
+
masterpiece, perfect composition, award winning photography, professional lighting""",
|
32 |
+
"negative_prompt": """artificial, digital art, illustration, painting, drawing, artistic, cartoon,
|
33 |
+
anime, unreal, fake, low quality, blurry, soft, deformed, noisy, unclear, imperfect, amateur""",
|
34 |
+
"quality_multiplier": 1.2
|
35 |
},
|
36 |
"Photoréaliste": {
|
37 |
+
"prompt_prefix": """hyperrealistic photograph, extremely detailed, studio quality, professional photography,
|
38 |
+
8k uhd, perfect lighting, high-end camera, professional grade lens, expert composition""",
|
39 |
+
"negative_prompt": """artistic, painterly, abstract, cartoon, illustration, low quality,
|
40 |
+
amateur, imperfect, blurry, noise""",
|
41 |
+
"quality_multiplier": 1.1
|
42 |
},
|
43 |
+
|
44 |
+
# Styles Artistiques
|
45 |
"Expressionniste": {
|
46 |
+
"prompt_prefix": """expressive painting style, intense emotional art, bold brushstrokes, vibrant colors,
|
47 |
+
van gogh inspired, artistic masterpiece, deep emotional impact, dynamic composition""",
|
48 |
+
"negative_prompt": """realistic, subtle, photographic, clean lines, digital art, flat, unemotional,
|
49 |
+
generic, weak composition""",
|
50 |
+
"quality_multiplier": 1.0
|
51 |
},
|
52 |
"Impressionniste": {
|
53 |
+
"prompt_prefix": """impressionist painting style, soft light, visible brushstrokes, outdoor scene,
|
54 |
+
monet inspired, artistic mastery, natural lighting, atmospheric effect""",
|
55 |
+
"negative_prompt": """sharp details, high contrast, digital, modern, artificial, harsh,
|
56 |
+
unrealistic colors""",
|
57 |
+
"quality_multiplier": 1.0
|
|
|
|
|
|
|
|
|
|
|
58 |
},
|
59 |
+
|
60 |
+
# Styles Modernes
|
61 |
"Art Moderne": {
|
62 |
+
"prompt_prefix": """modern art style poster, professional design, contemporary aesthetic,
|
63 |
+
trending on artstation, perfect composition, high-end production""",
|
64 |
+
"negative_prompt": """traditional, cluttered, busy design, vintage, amateur, low quality,
|
65 |
+
unprofessional""",
|
66 |
+
"quality_multiplier": 1.1
|
|
|
67 |
},
|
68 |
"Minimaliste": {
|
69 |
+
"prompt_prefix": """minimalist design poster, clean composition, elegant simplicity,
|
70 |
+
perfect balance, sophisticated style, high-end design""",
|
71 |
+
"negative_prompt": """complex, detailed, ornate, busy, cluttered, chaotic, unbalanced,
|
72 |
+
amateur""",
|
73 |
+
"quality_multiplier": 1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
},
|
75 |
+
|
76 |
+
# Styles Spéciaux
|
77 |
"Cyberpunk": {
|
78 |
+
"prompt_prefix": """cyberpunk style, neon lights, futuristic design, high-tech aesthetic,
|
79 |
+
detailed machinery, holographic elements, cinematic lighting""",
|
80 |
+
"negative_prompt": """vintage, natural, rustic, traditional, simple, flat, dull,
|
81 |
+
low-tech""",
|
82 |
+
"quality_multiplier": 1.1
|
|
|
83 |
},
|
84 |
"Art Déco": {
|
85 |
+
"prompt_prefix": """art deco style, geometric patterns, luxury design, 1920s aesthetic,
|
86 |
+
golden age glamour, sophisticated composition""",
|
87 |
+
"negative_prompt": """modern, minimalist, casual, contemporary, simple, rustic,
|
88 |
+
unrefined""",
|
89 |
+
"quality_multiplier": 1.0
|
|
|
90 |
}
|
91 |
}
|
92 |
|
93 |
# Paramètres de composition enrichis
|
94 |
COMPOSITION_PARAMS = {
|
95 |
"Layouts": {
|
96 |
+
"Centré": {
|
97 |
+
"description": "centered composition, balanced layout, harmonious arrangement",
|
98 |
+
"weight": 1.2
|
99 |
+
},
|
100 |
+
"Asymétrique": {
|
101 |
+
"description": "dynamic asymmetrical composition, creative balance, artistic flow",
|
102 |
+
"weight": 1.1
|
103 |
+
},
|
104 |
+
"Grille": {
|
105 |
+
"description": "grid-based layout, structured composition, organized design, perfect alignment",
|
106 |
+
"weight": 1.0
|
107 |
+
},
|
108 |
+
"Diagonal": {
|
109 |
+
"description": "diagonal dynamic composition, energetic flow, dramatic arrangement",
|
110 |
+
"weight": 1.1
|
111 |
+
},
|
112 |
+
"Minimaliste": {
|
113 |
+
"description": "minimal composition, lots of whitespace, elegant spacing, perfect balance",
|
114 |
+
"weight": 1.0
|
115 |
+
}
|
116 |
},
|
117 |
"Ambiances": {
|
118 |
+
"Dramatique": {
|
119 |
+
"description": "dramatic lighting, high contrast, intense mood, cinematic atmosphere",
|
120 |
+
"weight": 1.2
|
121 |
+
},
|
122 |
+
"Doux": {
|
123 |
+
"description": "soft lighting, gentle atmosphere, subtle mood, delicate ambiance",
|
124 |
+
"weight": 1.0
|
125 |
+
},
|
126 |
+
"Vibrant": {
|
127 |
+
"description": "vibrant colors, energetic mood, dynamic atmosphere, bold presence",
|
128 |
+
"weight": 1.1
|
129 |
+
},
|
130 |
+
"Mystérieux": {
|
131 |
+
"description": "mysterious atmosphere, moody lighting, enigmatic feel, intriguing shadows",
|
132 |
+
"weight": 1.1
|
133 |
+
},
|
134 |
+
"Serein": {
|
135 |
+
"description": "peaceful atmosphere, calm mood, tranquil setting, harmonious lighting",
|
136 |
+
"weight": 1.0
|
137 |
+
}
|
138 |
}
|
139 |
}
|
140 |
|
|
|
147 |
self.headers = {"Authorization": f"Bearer {token}"}
|
148 |
logger.info("ImageGenerator initialisé")
|
149 |
|
150 |
+
def _optimize_prompt(self, params: Dict[str, Any]) -> Tuple[str, str]:
|
151 |
+
"""Optimisation avancée des prompts avec gestion contextuelle"""
|
152 |
+
style_info = ART_STYLES.get(params["style"], ART_STYLES["Art Moderne"])
|
153 |
+
layout_info = COMPOSITION_PARAMS["Layouts"].get(params["layout"])
|
154 |
+
ambiance_info = COMPOSITION_PARAMS["Ambiances"].get(params["ambiance"])
|
155 |
+
|
156 |
+
# Construction du prompt principal
|
157 |
+
base_prompt = f"{params['subject']}"
|
158 |
+
if params.get('title'):
|
159 |
+
base_prompt += f", with text '{params['title']}'"
|
160 |
+
|
161 |
+
# Ajout des éléments de composition
|
162 |
+
composition_elements = [
|
163 |
+
style_info["prompt_prefix"],
|
164 |
+
layout_info["description"],
|
165 |
+
ambiance_info["description"]
|
166 |
+
]
|
167 |
+
|
168 |
+
# Calcul du multiplicateur de qualité
|
169 |
+
quality_multiplier = (
|
170 |
+
style_info.get("quality_multiplier", 1.0) *
|
171 |
+
layout_info.get("weight", 1.0) *
|
172 |
+
ambiance_info.get("weight", 1.0)
|
173 |
+
)
|
174 |
+
|
175 |
+
# Construction du prompt final optimisé
|
176 |
+
enhanced_prompt = f"{base_prompt}, {', '.join(composition_elements)}"
|
177 |
|
178 |
+
# Construction du negative prompt optimisé
|
179 |
+
negative_prompt = f"{style_info['negative_prompt']}, low quality, bad anatomy, worst quality, low resolution"
|
|
|
|
|
180 |
|
181 |
+
return enhanced_prompt, negative_prompt, quality_multiplier
|
|
|
182 |
|
183 |
+
def _enhance_image(self, image: Image.Image, params: Dict[str, Any]) -> Image.Image:
|
184 |
+
"""Post-traitement avancé des images"""
|
185 |
+
try:
|
186 |
+
# Amélioration de la netteté basée sur le style
|
187 |
+
sharpness_factor = 1.2 if params["style"] in ["Ultra Réaliste", "Photoréaliste"] else 1.1
|
188 |
+
enhancer = ImageEnhance.Sharpness(image)
|
189 |
+
image = enhancer.enhance(sharpness_factor)
|
190 |
|
191 |
+
# Ajustement du contraste selon l'ambiance
|
192 |
+
contrast_factor = 1.2 if params["ambiance"] == "Dramatique" else 1.1
|
193 |
+
enhancer = ImageEnhance.Contrast(image)
|
194 |
+
image = enhancer.enhance(contrast_factor)
|
195 |
|
196 |
+
return image
|
197 |
except Exception as e:
|
198 |
+
logger.warning(f"Erreur lors de l'amélioration de l'image: {str(e)}")
|
199 |
+
return image
|
|
|
200 |
|
201 |
def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
|
202 |
+
"""Génération d'image avec optimisations avancées"""
|
203 |
try:
|
204 |
logger.info(f"Début de génération avec paramètres: {json.dumps(params, indent=2)}")
|
205 |
|
206 |
if 'Bearer None' in self.headers['Authorization']:
|
207 |
return None, "⚠️ Erreur: Token Hugging Face non configuré"
|
208 |
|
209 |
+
# Optimisation des prompts
|
210 |
+
enhanced_prompt, negative_prompt, quality_multiplier = self._optimize_prompt(params)
|
211 |
+
|
212 |
+
# Configuration des paramètres de génération
|
213 |
+
generation_params = {
|
214 |
+
"num_inference_steps": min(int(50 * quality_multiplier), 60),
|
215 |
+
"guidance_scale": min(8.5 * (params["creativity"]/10), 12.0),
|
216 |
+
"width": 1024 if params.get("quality", 35) > 40 else 768,
|
217 |
+
"height": 1024 if params["orientation"] == "Portrait" else 768
|
218 |
+
}
|
219 |
|
220 |
payload = {
|
221 |
+
"inputs": enhanced_prompt,
|
222 |
"parameters": {
|
223 |
+
**generation_params,
|
224 |
+
"negative_prompt": negative_prompt
|
|
|
|
|
|
|
225 |
}
|
226 |
}
|
227 |
|
228 |
+
logger.debug(f"Payload final: {json.dumps(payload, indent=2)}")
|
229 |
|
230 |
+
# Génération de l'image
|
231 |
response = requests.post(
|
232 |
self.API_URL,
|
233 |
headers=self.headers,
|
234 |
json=payload,
|
235 |
+
timeout=45
|
236 |
)
|
237 |
|
238 |
if response.status_code == 200:
|
239 |
image = Image.open(io.BytesIO(response.content))
|
240 |
+
# Post-traitement de l'image
|
241 |
+
image = self._enhance_image(image, params)
|
242 |
return image, "✨ Création réussie!"
|
243 |
else:
|
244 |
error_msg = f"⚠️ Erreur API {response.status_code}: {response.text}"
|
|
|
253 |
gc.collect()
|
254 |
|
255 |
def create_interface():
|
256 |
+
"""Création de l'interface utilisateur enrichie"""
|
257 |
logger.info("Création de l'interface Gradio")
|
258 |
|
259 |
+
# Styles CSS personnalisés
|
260 |
css = """
|
261 |
.container { max-width: 1200px; margin: auto; }
|
262 |
+
.welcome {
|
263 |
+
text-align: center;
|
264 |
+
margin: 20px 0;
|
265 |
+
padding: 20px;
|
266 |
+
background: linear-gradient(135deg, #1e293b, #334155);
|
267 |
+
border-radius: 10px;
|
268 |
+
color: white;
|
269 |
+
}
|
270 |
+
.controls-group {
|
271 |
+
background: #2d3748;
|
272 |
+
padding: 15px;
|
273 |
+
border-radius: 5px;
|
274 |
+
margin: 10px 0;
|
275 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
276 |
+
}
|
277 |
+
.advanced-controls {
|
278 |
+
background: #374151;
|
279 |
+
padding: 12px;
|
280 |
+
border-radius: 5px;
|
281 |
+
margin: 8px 0;
|
282 |
+
}
|
283 |
+
.info-tooltip {
|
284 |
+
color: #94a3b8;
|
285 |
+
font-size: 0.9em;
|
286 |
+
margin-top: 4px;
|
287 |
+
}
|
288 |
"""
|
289 |
|
290 |
generator = ImageGenerator()
|
291 |
|
292 |
with gr.Blocks(css=css) as app:
|
293 |
+
# En-tête et présentation
|
294 |
gr.HTML("""
|
295 |
<div class="welcome">
|
296 |
<h1>🎨 Equity Artisan 3.0</h1>
|
297 |
<p>Assistant de création d'affiches professionnelles</p>
|
298 |
+
<p style="font-size: 0.9em; opacity: 0.8;">Powered by Stable Diffusion XL</p>
|
299 |
</div>
|
300 |
""")
|
301 |
|
302 |
with gr.Column(elem_classes="container"):
|
303 |
+
# Guide d'utilisation
|
304 |
gr.Markdown("""
|
305 |
### 🎯 Guide d'utilisation
|
306 |
1. Choisissez le format et l'orientation de votre affiche
|
|
|
308 |
3. Décrivez votre vision dans "Description"
|
309 |
4. Ajustez les paramètres fins selon vos besoins
|
310 |
5. Cliquez sur "Générer" !
|
311 |
+
|
312 |
+
*💡 Pro Tip: Pour de meilleurs résultats, soyez précis dans votre description et expérimentez avec différents styles.*
|
313 |
""")
|
314 |
|
315 |
+
# Contrôles principaux
|
316 |
with gr.Group(elem_classes="controls-group"):
|
317 |
gr.Markdown("### 📐 Format et Orientation")
|
318 |
with gr.Row():
|
|
|
320 |
choices=["A4", "A3", "A2", "A1", "A0"],
|
321 |
value="A4",
|
322 |
label="Format",
|
323 |
+
info="Choisissez la taille de votre affiche"
|
324 |
)
|
325 |
orientation = gr.Radio(
|
326 |
choices=["Portrait", "Paysage"],
|
327 |
value="Portrait",
|
328 |
+
label="Orientation"
|
|
|
329 |
)
|
330 |
|
331 |
+
# Style et composition
|
332 |
with gr.Group(elem_classes="controls-group"):
|
333 |
gr.Markdown("### 🎨 Style et Composition")
|
334 |
with gr.Row():
|
335 |
style = gr.Dropdown(
|
336 |
choices=list(ART_STYLES.keys()),
|
337 |
value="Art Moderne",
|
338 |
+
label="Style artistique"
|
|
|
339 |
)
|
340 |
layout = gr.Dropdown(
|
341 |
choices=list(COMPOSITION_PARAMS["Layouts"].keys()),
|
342 |
value="Centré",
|
343 |
+
label="Composition"
|
|
|
344 |
)
|
345 |
|
346 |
with gr.Row():
|
347 |
ambiance = gr.Dropdown(
|
348 |
choices=list(COMPOSITION_PARAMS["Ambiances"].keys()),
|
349 |
value="Dramatique",
|
350 |
+
label="Ambiance"
|
|
|
351 |
)
|
352 |
palette = gr.Dropdown(
|
353 |
+
choices=["Monochrome", "Contrasté", "Pastel", "Terre", "Néon"],
|
354 |
value="Contrasté",
|
355 |
+
label="Palette"
|
|
|
356 |
)
|
357 |
|
358 |
+
# Description et contenu
|
359 |
with gr.Group(elem_classes="controls-group"):
|
360 |
+
gr.Markdown("### 📝 Contenu")
|
|
|
361 |
subject = gr.Textbox(
|
362 |
label="Description",
|
363 |
placeholder="Ex: Une affiche moderne pour un festival de musique, avec des instruments colorés flottant dans l'espace",
|
364 |
+
lines=3
|
365 |
)
|
366 |
title = gr.Textbox(
|
367 |
+
label="Titre (optionnel)",
|
368 |
+
placeholder="Le titre qui apparaîtra sur l'affiche..."
|
|
|
369 |
)
|
370 |
|
371 |
+
# Contrôles avancés
|
372 |
+
with gr.Group(elem_classes="advanced-controls"):
|
373 |
+
gr.Markdown("### 🎯 Paramètres Av# Contrôles avancés
|
374 |
with gr.Group(elem_classes="advanced-controls"):
|
375 |
+
gr.Markdown("### 🎯 Paramètres Avancés")
|
|
|
376 |
with gr.Row():
|
377 |
detail_level = gr.Slider(
|
378 |
minimum=1,
|
|
|
399 |
info="Contrôle la vivacité des couleurs"
|
400 |
)
|
401 |
|
402 |
+
# Paramètres de génération
|
403 |
with gr.Group(elem_classes="controls-group"):
|
404 |
gr.Markdown("### ⚙️ Paramètres de Génération")
|
405 |
with gr.Row():
|
|
|
418 |
info="Plus la valeur est élevée, plus l'IA prendra de libertés créatives"
|
419 |
)
|
420 |
|
421 |
+
# Boutons de contrôle
|
422 |
with gr.Row():
|
423 |
generate_btn = gr.Button("✨ Générer", variant="primary")
|
424 |
clear_btn = gr.Button("🗑️ Effacer", variant="secondary")
|
425 |
|
426 |
+
# Zone de résultat
|
427 |
+
with gr.Group(elem_classes="controls-group"):
|
428 |
+
gr.Markdown("### 🖼️ Résultat")
|
429 |
+
image_output = gr.Image(label="Aperçu", height=512)
|
430 |
+
status = gr.Textbox(label="Statut", interactive=False)
|
431 |
+
|
432 |
+
# Zone d'historique
|
433 |
+
with gr.Group(elem_classes="controls-group"):
|
434 |
+
gr.Markdown("### 📋 Historique des Générations")
|
435 |
+
history = gr.Gallery(label="Générations précédentes", show_label=True, columns=4, height=200)
|
436 |
|
437 |
+
# Fonction de génération
|
438 |
def generate(*args):
|
439 |
logger.info("Démarrage d'une nouvelle génération")
|
440 |
params = {
|
|
|
456 |
logger.info(f"Génération terminée avec statut: {result[1]}")
|
457 |
return result
|
458 |
|
459 |
+
# Connexion des événements
|
460 |
generate_btn.click(
|
461 |
generate,
|
462 |
inputs=[
|