Update app.py
Browse files
app.py
CHANGED
@@ -1,207 +1,150 @@
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
-
from PIL import Image, ImageEnhance
|
4 |
import requests
|
5 |
import io
|
6 |
import gc
|
7 |
import json
|
|
|
8 |
import logging
|
9 |
from dotenv import load_dotenv
|
10 |
-
from typing import Tuple, Optional, Dict, Any, List
|
11 |
import numpy as np
|
12 |
-
|
13 |
|
14 |
-
# Configuration
|
15 |
logging.basicConfig(
|
16 |
level=logging.DEBUG,
|
17 |
-
format='%(asctime)s - %(
|
|
|
|
|
|
|
|
|
18 |
)
|
19 |
-
logger = logging.getLogger(
|
20 |
-
|
21 |
-
# Chargement des variables d'environnement
|
22 |
load_dotenv()
|
23 |
|
24 |
-
|
25 |
-
class RenderingParams:
|
26 |
-
"""Paramètres de rendu avancés pour le contrôle fin de la génération"""
|
27 |
-
steps: int # Nombre d'étapes d'inférence
|
28 |
-
cfg_scale: float # Échelle de guidance
|
29 |
-
width: int # Largeur
|
30 |
-
height: int # Hauteur
|
31 |
-
sampler: str # Méthode d'échantillonnage
|
32 |
-
seed: Optional[int] # Seed pour la reproductibilité
|
33 |
-
|
34 |
-
@dataclass
|
35 |
-
class StyleConfig:
|
36 |
-
"""Configuration complète d'un style artistique"""
|
37 |
-
prompt_prefix: str
|
38 |
-
negative_prompt: str
|
39 |
-
quality_modifiers: List[str]
|
40 |
-
detail_modifiers: List[str]
|
41 |
-
composition_rules: List[str]
|
42 |
-
|
43 |
-
# Styles artistiques enrichis
|
44 |
ART_STYLES = {
|
45 |
-
"
|
46 |
-
prompt_prefix
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
quality_modifiers=["film grain", "color grading", "bokeh effect"],
|
56 |
-
detail_modifiers=["depth of field", "motion blur", "lens flare"],
|
57 |
-
composition_rules=["wide aspect ratio", "leading lines", "frame within frame"]
|
58 |
-
),
|
59 |
-
# ... [Autres styles similaires] ...
|
60 |
-
}
|
61 |
-
|
62 |
-
# Paramètres de qualité avancés
|
63 |
-
QUALITY_PRESETS = {
|
64 |
-
"Standard": RenderingParams(
|
65 |
-
steps=30,
|
66 |
-
cfg_scale=7.5,
|
67 |
-
width=768,
|
68 |
-
height=768,
|
69 |
-
sampler="Euler a",
|
70 |
-
seed=None
|
71 |
-
),
|
72 |
-
"Haute Qualité": RenderingParams(
|
73 |
-
steps=40,
|
74 |
-
cfg_scale=8.5,
|
75 |
-
width=1024,
|
76 |
-
height=1024,
|
77 |
-
sampler="DPM++ 2M Karras",
|
78 |
-
seed=None
|
79 |
-
),
|
80 |
-
"Ultra Qualité": RenderingParams(
|
81 |
-
steps=50,
|
82 |
-
cfg_scale=9.0,
|
83 |
-
width=1536,
|
84 |
-
height=1536,
|
85 |
-
sampler="DPM++ SDE Karras",
|
86 |
-
seed=None
|
87 |
-
)
|
88 |
-
}
|
89 |
-
|
90 |
-
# Enrichissement des paramètres de composition
|
91 |
-
COMPOSITION_PARAMS = {
|
92 |
-
"Layouts": {
|
93 |
-
"Dynamique": {
|
94 |
-
"description": "Composition dynamique avec lignes de force",
|
95 |
-
"modifiers": ["dynamic composition", "leading lines", "visual flow"],
|
96 |
-
"weights": {"foreground": 1.2, "background": 0.8}
|
97 |
-
},
|
98 |
-
"Équilibré": {
|
99 |
-
"description": "Composition harmonieuse et équilibrée",
|
100 |
-
"modifiers": ["balanced composition", "symmetrical layout", "visual harmony"],
|
101 |
-
"weights": {"foreground": 1.0, "background": 1.0}
|
102 |
}
|
103 |
-
# ... [Autres layouts] ...
|
104 |
},
|
105 |
-
"
|
106 |
-
"
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
"
|
112 |
-
"
|
113 |
-
"
|
114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
}
|
116 |
-
# ... [Autres éclairages] ...
|
117 |
}
|
118 |
}
|
119 |
|
120 |
-
|
|
|
121 |
def __init__(self):
|
122 |
-
self.
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
# Initialisation du cache pour les paramètres optimaux
|
129 |
-
self.optimization_cache = {}
|
130 |
-
logger.info("AdvancedImageGenerator initialisé avec paramètres enrichis")
|
131 |
-
|
132 |
-
def _optimize_prompt(self, base_prompt: str, style_config: StyleConfig, params: Dict[str, Any]) -> str:
|
133 |
-
"""Optimisation avancée du prompt avec analyse sémantique"""
|
134 |
-
components = [
|
135 |
-
style_config.prompt_prefix,
|
136 |
-
base_prompt
|
137 |
-
]
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
components.extend([
|
150 |
-
"perfect facial features",
|
151 |
-
"detailed skin texture",
|
152 |
-
"professional portrait lighting"
|
153 |
-
])
|
154 |
-
elif "paysage" in base_prompt.lower():
|
155 |
-
components.extend([
|
156 |
-
"atmospheric perspective",
|
157 |
-
"perfect weather conditions",
|
158 |
-
"golden hour lighting"
|
159 |
-
])
|
160 |
-
|
161 |
-
return ", ".join(components)
|
162 |
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
image = enhancer.enhance(1.2)
|
174 |
-
|
175 |
-
# Ajustement des couleurs
|
176 |
-
if params.get("color_optimization", True):
|
177 |
-
enhancer = ImageEnhance.Color(image)
|
178 |
-
image = enhancer.enhance(1.1)
|
179 |
-
|
180 |
-
return image
|
181 |
-
except Exception as e:
|
182 |
-
logger.error(f"Erreur lors du post-traitement: {e}")
|
183 |
-
return image
|
184 |
|
185 |
def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
|
186 |
-
"""Génération d'image avec paramètres avancés"""
|
187 |
try:
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
|
|
196 |
payload = {
|
197 |
"inputs": prompt,
|
198 |
"parameters": {
|
199 |
-
"negative_prompt":
|
200 |
-
"num_inference_steps":
|
201 |
-
"guidance_scale":
|
202 |
-
"width":
|
203 |
-
"height":
|
204 |
-
"sampler": quality_preset.sampler
|
205 |
}
|
206 |
}
|
207 |
|
@@ -209,138 +152,138 @@ class AdvancedImageGenerator:
|
|
209 |
self.API_URL,
|
210 |
headers=self.headers,
|
211 |
json=payload,
|
212 |
-
timeout=
|
213 |
)
|
214 |
|
215 |
-
if response.status_code
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
|
|
|
|
|
|
223 |
|
224 |
except Exception as e:
|
225 |
-
|
226 |
-
|
|
|
|
|
|
|
227 |
|
|
|
228 |
def create_interface():
|
229 |
-
|
230 |
-
logger.info("Initialisation de l'interface avancée")
|
231 |
|
|
|
232 |
css = """
|
233 |
-
.container { max-width:
|
234 |
.welcome {
|
235 |
text-align: center;
|
236 |
margin: 20px 0;
|
237 |
-
padding:
|
238 |
background: linear-gradient(135deg, #1e293b, #334155);
|
239 |
-
border-radius:
|
240 |
color: white;
|
241 |
}
|
242 |
.controls-group {
|
243 |
background: #2d3748;
|
244 |
-
padding:
|
245 |
-
border-radius:
|
246 |
-
margin:
|
247 |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
248 |
}
|
249 |
"""
|
250 |
|
251 |
-
generator = AdvancedImageGenerator()
|
252 |
-
|
253 |
with gr.Blocks(css=css) as app:
|
254 |
gr.HTML("""
|
255 |
<div class="welcome">
|
256 |
-
<h1>🎨 Equity Artisan
|
257 |
-
<p>
|
258 |
</div>
|
259 |
""")
|
260 |
|
261 |
with gr.Column(elem_classes="container"):
|
262 |
-
#
|
263 |
with gr.Group(elem_classes="controls-group"):
|
264 |
-
gr.Markdown("### 🎭 Style et Composition")
|
265 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
style = gr.Dropdown(
|
267 |
choices=list(ART_STYLES.keys()),
|
268 |
-
value="
|
269 |
label="Style Artistique"
|
270 |
)
|
271 |
-
quality_preset = gr.Dropdown(
|
272 |
-
choices=list(QUALITY_PRESETS.keys()),
|
273 |
-
value="Haute Qualité",
|
274 |
-
label="Preset de Qualité"
|
275 |
-
)
|
276 |
|
277 |
-
#
|
278 |
with gr.Group(elem_classes="controls-group"):
|
279 |
-
gr.
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
)
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
)
|
291 |
-
|
292 |
-
# Contrôles techniques
|
293 |
with gr.Group(elem_classes="controls-group"):
|
294 |
-
gr.Markdown("### ⚙️ Paramètres Techniques")
|
295 |
with gr.Row():
|
296 |
quality = gr.Slider(
|
297 |
minimum=30,
|
298 |
-
maximum=
|
299 |
-
value=
|
300 |
-
label="
|
301 |
)
|
302 |
detail_level = gr.Slider(
|
303 |
minimum=1,
|
304 |
maximum=10,
|
305 |
value=8,
|
|
|
306 |
label="Niveau de Détail"
|
307 |
)
|
308 |
|
309 |
-
#
|
310 |
with gr.Row():
|
311 |
-
generate_btn = gr.Button("✨ Générer", variant="primary"
|
312 |
-
clear_btn = gr.Button("🗑️
|
313 |
|
314 |
-
#
|
315 |
-
image_output = gr.Image(
|
316 |
-
|
317 |
-
elem_classes="result-image"
|
318 |
-
)
|
319 |
-
status = gr.Textbox(
|
320 |
-
label="Statut",
|
321 |
-
interactive=False
|
322 |
-
)
|
323 |
|
324 |
-
|
325 |
-
def generate_image(*args):
|
326 |
params = {
|
327 |
-
"
|
328 |
-
"
|
329 |
-
"
|
330 |
-
"
|
331 |
-
"
|
332 |
-
"
|
|
|
333 |
}
|
334 |
return generator.generate(params)
|
335 |
|
336 |
-
# Événements
|
337 |
generate_btn.click(
|
338 |
-
|
339 |
inputs=[
|
|
|
|
|
340 |
style,
|
341 |
-
quality_preset,
|
342 |
subject,
|
343 |
-
|
344 |
quality,
|
345 |
detail_level
|
346 |
],
|
@@ -348,11 +291,10 @@ def create_interface():
|
|
348 |
)
|
349 |
|
350 |
clear_btn.click(
|
351 |
-
lambda: (None, "
|
352 |
outputs=[image_output, status]
|
353 |
)
|
354 |
|
355 |
-
logger.info("Interface créée avec succès")
|
356 |
return app
|
357 |
|
358 |
if __name__ == "__main__":
|
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
+
from PIL import Image, ImageEnhance
|
4 |
import requests
|
5 |
import io
|
6 |
import gc
|
7 |
import json
|
8 |
+
from typing import Tuple, Optional, Dict, Any
|
9 |
import logging
|
10 |
from dotenv import load_dotenv
|
|
|
11 |
import numpy as np
|
12 |
+
import cv2
|
13 |
|
14 |
+
# Configuration du logging
|
15 |
logging.basicConfig(
|
16 |
level=logging.DEBUG,
|
17 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
18 |
+
handlers=[
|
19 |
+
logging.FileHandler('equity_space.log'),
|
20 |
+
logging.StreamHandler()
|
21 |
+
]
|
22 |
)
|
23 |
+
logger = logging.getLogger(__name__)
|
|
|
|
|
24 |
load_dotenv()
|
25 |
|
26 |
+
# 1. STYLES EQUITY (basés sur les 8 documents)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
ART_STYLES = {
|
28 |
+
"Renaissance Technologique": {
|
29 |
+
"prompt_prefix": """ultra-detailed technical drawing, da vinci style engineering precision,
|
30 |
+
anatomical accuracy, golden ratio proportions, scientific illustration quality,
|
31 |
+
masterful light and shadow, highest level of detail, museum quality artwork""",
|
32 |
+
"negative_prompt": "simple sketch, imprecise, cartoon, abstract, low detail",
|
33 |
+
"quality_boost": 1.5,
|
34 |
+
"style_params": {
|
35 |
+
"detail_level": "maximum",
|
36 |
+
"technical_precision": True,
|
37 |
+
"historical_accuracy": True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
}
|
|
|
39 |
},
|
40 |
+
"Innovation Moderne": {
|
41 |
+
"prompt_prefix": """cutting-edge technological design, futuristic engineering visual,
|
42 |
+
advanced mechanical detail, precise technical schematic, innovative concept art,
|
43 |
+
professional industrial visualization, high-tech aesthetic""",
|
44 |
+
"negative_prompt": "vintage, retro, simplified, abstract",
|
45 |
+
"quality_boost": 1.4,
|
46 |
+
"style_params": {
|
47 |
+
"tech_detail": "ultra",
|
48 |
+
"innovation_focus": True
|
49 |
+
}
|
50 |
+
},
|
51 |
+
"Photographie Analytique": {
|
52 |
+
"prompt_prefix": """professional analytical photography, ansel adams zone system,
|
53 |
+
ultra sharp detail, perfect exposure, museum grade quality, technical perfection,
|
54 |
+
masterful composition, extreme clarity""",
|
55 |
+
"negative_prompt": "blurry, artistic, painterly, low quality",
|
56 |
+
"quality_boost": 1.5,
|
57 |
+
"style_params": {
|
58 |
+
"photographic_precision": True,
|
59 |
+
"detail_preservation": "maximum"
|
60 |
+
}
|
61 |
+
},
|
62 |
+
"Vision Futuriste": {
|
63 |
+
"prompt_prefix": """advanced technological concept, future engineering visualization,
|
64 |
+
innovative architectural design, sci-fi technical precision, ultra modern aesthetic,
|
65 |
+
professional technical illustration""",
|
66 |
+
"negative_prompt": "historical, vintage, traditional, hand-drawn",
|
67 |
+
"quality_boost": 1.4,
|
68 |
+
"style_params": {
|
69 |
+
"future_tech": True,
|
70 |
+
"concept_clarity": "high"
|
71 |
}
|
|
|
72 |
}
|
73 |
}
|
74 |
|
75 |
+
# 2. OPTIMISATION DE LA QUALITÉ
|
76 |
+
class ImageEnhancer:
|
77 |
def __init__(self):
|
78 |
+
self.enhancement_pipeline = {
|
79 |
+
"technical_detail": self._enhance_technical_detail,
|
80 |
+
"color_refinement": self._refine_colors,
|
81 |
+
"sharpness_boost": self._boost_sharpness,
|
82 |
+
"contrast_optimization": self._optimize_contrast
|
83 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
85 |
+
def _enhance_technical_detail(self, image: np.ndarray) -> np.ndarray:
|
86 |
+
kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
|
87 |
+
return cv2.filter2D(image, -1, kernel)
|
88 |
+
|
89 |
+
def _refine_colors(self, image: np.ndarray) -> np.ndarray:
|
90 |
+
lab = cv2.cvtColor(image, cv2.COLOR_BGR2Lab)
|
91 |
+
l, a, b = cv2.split(lab)
|
92 |
+
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
|
93 |
+
l = clahe.apply(l)
|
94 |
+
lab = cv2.merge((l,a,b))
|
95 |
+
return cv2.cvtColor(lab, cv2.COLOR_Lab2BGR)
|
96 |
+
|
97 |
+
def _boost_sharpness(self, image: np.ndarray) -> np.ndarray:
|
98 |
+
blurred = cv2.GaussianBlur(image, (0, 0), 3)
|
99 |
+
return cv2.addWeighted(image, 1.5, blurred, -0.5, 0)
|
100 |
+
|
101 |
+
def _optimize_contrast(self, image: np.ndarray) -> np.ndarray:
|
102 |
+
lab = cv2.cvtColor(image, cv2.COLOR_BGR2Lab)
|
103 |
+
l, a, b = cv2.split(lab)
|
104 |
+
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
|
105 |
+
l = clahe.apply(l)
|
106 |
+
lab = cv2.merge((l,a,b))
|
107 |
+
return cv2.cvtColor(lab, cv2.COLOR_Lab2BGR)
|
108 |
+
|
109 |
+
def enhance_image(self, image: Image.Image, style_params: Dict) -> Image.Image:
|
110 |
+
cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
111 |
|
112 |
+
for enhancement, should_apply in style_params.items():
|
113 |
+
if should_apply and enhancement in self.enhancement_pipeline:
|
114 |
+
cv_image = self.enhancement_pipeline[enhancement](cv_image)
|
115 |
+
|
116 |
+
return Image.fromarray(cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
|
118 |
+
# 3. GÉNÉRATEUR D'IMAGES PRINCIPAL
|
119 |
+
class ImageGenerator:
|
120 |
+
def __init__(self):
|
121 |
+
self.API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
|
122 |
+
self.enhancer = ImageEnhancer()
|
123 |
+
token = os.getenv('HUGGINGFACE_TOKEN')
|
124 |
+
if not token:
|
125 |
+
logger.error("HUGGINGFACE_TOKEN non trouvé!")
|
126 |
+
self.headers = {"Authorization": f"Bearer {token}"}
|
127 |
+
logger.info("ImageGenerator initialisé")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
|
|
|
130 |
try:
|
131 |
+
style_info = ART_STYLES.get(params["style"])
|
132 |
+
if not style_info:
|
133 |
+
return None, "⚠️ Style non trouvé"
|
134 |
+
|
135 |
+
# Construction du prompt optimisé
|
136 |
+
prompt = f"{params['subject']}, {style_info['prompt_prefix']}"
|
137 |
+
if params.get('title'):
|
138 |
+
prompt += f", with text: {params['title']}"
|
139 |
+
|
140 |
payload = {
|
141 |
"inputs": prompt,
|
142 |
"parameters": {
|
143 |
+
"negative_prompt": style_info["negative_prompt"],
|
144 |
+
"num_inference_steps": int(50 * style_info["quality_boost"]),
|
145 |
+
"guidance_scale": min(9.0 * style_info["quality_boost"], 12.0),
|
146 |
+
"width": 1024 if params.get("quality", 35) > 40 else 768,
|
147 |
+
"height": 1024 if params["orientation"] == "Portrait" else 768
|
|
|
148 |
}
|
149 |
}
|
150 |
|
|
|
152 |
self.API_URL,
|
153 |
headers=self.headers,
|
154 |
json=payload,
|
155 |
+
timeout=45
|
156 |
)
|
157 |
|
158 |
+
if response.status_code == 200:
|
159 |
+
image = Image.open(io.BytesIO(response.content))
|
160 |
+
enhanced_image = self.enhancer.enhance_image(
|
161 |
+
image,
|
162 |
+
style_info["style_params"]
|
163 |
+
)
|
164 |
+
return enhanced_image, "✨ Création réussie!"
|
165 |
+
else:
|
166 |
+
error_msg = f"⚠️ Erreur API {response.status_code}: {response.text}"
|
167 |
+
logger.error(error_msg)
|
168 |
+
return None, error_msg
|
169 |
|
170 |
except Exception as e:
|
171 |
+
error_msg = f"⚠️ Erreur: {str(e)}"
|
172 |
+
logger.exception("Erreur génération:")
|
173 |
+
return None, error_msg
|
174 |
+
finally:
|
175 |
+
gc.collect()
|
176 |
|
177 |
+
# 4. INTERFACE UTILISATEUR
|
178 |
def create_interface():
|
179 |
+
generator = ImageGenerator()
|
|
|
180 |
|
181 |
+
# Style CSS personnalisé
|
182 |
css = """
|
183 |
+
.container { max-width: 1200px; margin: auto; }
|
184 |
.welcome {
|
185 |
text-align: center;
|
186 |
margin: 20px 0;
|
187 |
+
padding: 20px;
|
188 |
background: linear-gradient(135deg, #1e293b, #334155);
|
189 |
+
border-radius: 10px;
|
190 |
color: white;
|
191 |
}
|
192 |
.controls-group {
|
193 |
background: #2d3748;
|
194 |
+
padding: 15px;
|
195 |
+
border-radius: 5px;
|
196 |
+
margin: 10px 0;
|
197 |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
198 |
}
|
199 |
"""
|
200 |
|
|
|
|
|
201 |
with gr.Blocks(css=css) as app:
|
202 |
gr.HTML("""
|
203 |
<div class="welcome">
|
204 |
+
<h1>🎨 Equity Artisan 3.0</h1>
|
205 |
+
<p>Assistant de Création d'Images Professionnelles</p>
|
206 |
</div>
|
207 |
""")
|
208 |
|
209 |
with gr.Column(elem_classes="container"):
|
210 |
+
# Format et Style
|
211 |
with gr.Group(elem_classes="controls-group"):
|
|
|
212 |
with gr.Row():
|
213 |
+
format_size = gr.Dropdown(
|
214 |
+
choices=["A4", "A3", "A2", "A1"],
|
215 |
+
value="A4",
|
216 |
+
label="Format"
|
217 |
+
)
|
218 |
+
orientation = gr.Radio(
|
219 |
+
choices=["Portrait", "Paysage"],
|
220 |
+
value="Portrait",
|
221 |
+
label="Orientation"
|
222 |
+
)
|
223 |
style = gr.Dropdown(
|
224 |
choices=list(ART_STYLES.keys()),
|
225 |
+
value="Renaissance Technologique",
|
226 |
label="Style Artistique"
|
227 |
)
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
+
# Description
|
230 |
with gr.Group(elem_classes="controls-group"):
|
231 |
+
subject = gr.Textbox(
|
232 |
+
label="Description",
|
233 |
+
placeholder="Décrivez votre vision technique ou artistique...",
|
234 |
+
lines=3
|
235 |
+
)
|
236 |
+
title = gr.Textbox(
|
237 |
+
label="Titre (optionnel)",
|
238 |
+
placeholder="Titre à inclure dans l'image..."
|
239 |
+
)
|
240 |
+
|
241 |
+
# Paramètres avancés
|
|
|
|
|
|
|
242 |
with gr.Group(elem_classes="controls-group"):
|
|
|
243 |
with gr.Row():
|
244 |
quality = gr.Slider(
|
245 |
minimum=30,
|
246 |
+
maximum=50,
|
247 |
+
value=40,
|
248 |
+
label="Qualité"
|
249 |
)
|
250 |
detail_level = gr.Slider(
|
251 |
minimum=1,
|
252 |
maximum=10,
|
253 |
value=8,
|
254 |
+
step=1,
|
255 |
label="Niveau de Détail"
|
256 |
)
|
257 |
|
258 |
+
# Boutons
|
259 |
with gr.Row():
|
260 |
+
generate_btn = gr.Button("✨ Générer", variant="primary")
|
261 |
+
clear_btn = gr.Button("🗑️ Effacer")
|
262 |
|
263 |
+
# Résultat
|
264 |
+
image_output = gr.Image(label="Résultat")
|
265 |
+
status = gr.Textbox(label="Status", interactive=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
|
267 |
+
def generate(*args):
|
|
|
268 |
params = {
|
269 |
+
"format_size": args[0],
|
270 |
+
"orientation": args[1],
|
271 |
+
"style": args[2],
|
272 |
+
"subject": args[3],
|
273 |
+
"title": args[4],
|
274 |
+
"quality": args[5],
|
275 |
+
"detail_level": args[6]
|
276 |
}
|
277 |
return generator.generate(params)
|
278 |
|
|
|
279 |
generate_btn.click(
|
280 |
+
generate,
|
281 |
inputs=[
|
282 |
+
format_size,
|
283 |
+
orientation,
|
284 |
style,
|
|
|
285 |
subject,
|
286 |
+
title,
|
287 |
quality,
|
288 |
detail_level
|
289 |
],
|
|
|
291 |
)
|
292 |
|
293 |
clear_btn.click(
|
294 |
+
lambda: (None, "🗑️ Image effacée"),
|
295 |
outputs=[image_output, status]
|
296 |
)
|
297 |
|
|
|
298 |
return app
|
299 |
|
300 |
if __name__ == "__main__":
|