Equityone commited on
Commit
d6bf8a7
·
verified ·
1 Parent(s): 9516b33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -374
app.py CHANGED
@@ -1,403 +1,185 @@
1
  import gradio as gr
2
- import os
3
  from PIL import Image
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
  from prompt_enhancer import PromptEnhancer
12
 
13
- # Configuration du logging
14
- logging.basicConfig(
15
- level=logging.DEBUG,
16
- format='%(asctime)s - %(levelname)s - %(message)s'
17
- )
18
- logger = logging.getLogger(__name__)
 
 
19
 
20
- # Chargement des variables d'environnement
21
- load_dotenv()
22
-
23
- # Styles artistiques complets
24
- ART_STYLES = {
25
- # Styles Classiques
26
- "Ultra Réaliste": {
27
- "prompt_prefix": "ultra realistic photograph, stunning photorealistic quality, unreal engine 5 quality, octane render, ray tracing, volumetric lighting, subsurface scattering, 8k UHD, cinema quality, masterpiece, perfect composition, award winning photography",
28
- "negative_prompt": "artificial, digital art, illustration, painting, drawing, artistic, cartoon, anime, unreal, fake, low quality, blurry, soft, deformed"
29
- },
30
- "Photoréaliste": {
31
- "prompt_prefix": "hyperrealistic photograph, extremely detailed, studio quality, professional photography, 8k uhd",
32
- "negative_prompt": "artistic, painterly, abstract, cartoon, illustration, low quality"
33
- },
34
- "Expressionniste": {
35
- "prompt_prefix": "expressive painting style, intense emotional art, bold brushstrokes, vibrant colors, van gogh inspired",
36
- "negative_prompt": "realistic, subtle, photographic, clean lines, digital art"
37
- },
38
- "Impressionniste": {
39
- "prompt_prefix": "impressionist painting style, soft light, visible brushstrokes, outdoor scene, monet inspired",
40
- "negative_prompt": "sharp details, high contrast, digital, modern"
41
- },
42
- "Art Abstrait": {
43
- "prompt_prefix": "abstract art, geometric shapes, non-representational, kandinsky style, pure artistic expression",
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
- "negative_prompt": "traditional, cluttered, busy design, vintage"
53
- },
54
- "Pop Art": {
55
- "prompt_prefix": "pop art style poster, bold colors, repeated patterns, screen print effect, warhol inspired",
56
- "negative_prompt": "subtle, realistic, traditional, painterly"
57
- },
58
- "Minimaliste": {
59
- "prompt_prefix": "minimalist design poster, clean composition, elegant simplicity",
60
- "negative_prompt": "complex, detailed, ornate, busy, cluttered"
61
- },
62
- "Cubiste": {
63
- "prompt_prefix": "cubist art style, geometric fragmentation, multiple perspectives, picasso inspired",
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 poster, neon lights, futuristic design, high-tech aesthetic",
76
- "negative_prompt": "vintage, natural, rustic, traditional"
77
- },
78
- "Japonais": {
79
- "prompt_prefix": "japanese art style poster, ukiyo-e inspired design, traditional japanese aesthetic",
80
- "negative_prompt": "western, modern, photographic"
81
- },
82
- "Art Déco": {
83
- "prompt_prefix": "art deco style poster, geometric patterns, luxury design, 1920s aesthetic",
84
- "negative_prompt": "modern, minimalist, casual, contemporary"
85
- },
86
- "Symboliste": {
87
- "prompt_prefix": "symbolic art, decorative patterns, gold elements, mystical atmosphere, klimt inspired",
88
- "negative_prompt": "realistic, simple, plain, literal"
89
- }
90
- }
91
-
92
- # Paramètres de composition enrichis
93
- COMPOSITION_PARAMS = {
94
- "Layouts": {
95
- "Centré": "centered composition, balanced layout, harmonious arrangement",
96
- "Asymétrique": "dynamic asymmetrical composition, creative balance",
97
- "Grille": "grid-based layout, structured composition, organized design",
98
- "Diagonal": "diagonal dynamic composition, energetic flow",
99
- "Minimaliste": "minimal composition, lots of whitespace, elegant spacing"
100
- },
101
- "Ambiances": {
102
- "Dramatique": "dramatic lighting, high contrast, intense mood",
103
- "Doux": "soft lighting, gentle atmosphere, subtle mood",
104
- "Vibrant": "vibrant colors, energetic mood, dynamic atmosphere",
105
- "Mystérieux": "mysterious atmosphere, moody lighting, enigmatic feel",
106
- "Serein": "peaceful atmosphere, calm mood, tranquil setting"
107
- },
108
- "Palette": {
109
- "Monochrome": "monochromatic color scheme, sophisticated tones",
110
- "Contrasté": "high contrast color palette, bold color combinations",
111
- "Pastel": "soft pastel color palette, gentle colors",
112
- "Terre": "earthy color palette, natural tones",
113
- "Néon": "neon color palette, vibrant glowing colors"
114
- }
115
- }
116
-
117
- class ImageGenerator:
118
  def __init__(self):
119
- self.API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
120
- token = os.getenv('HUGGINGFACE_TOKEN')
121
- if not token:
122
- logger.error("HUGGINGFACE_TOKEN non trouvé!")
123
- self.headers = {"Authorization": f"Bearer {token}"}
124
- logger.info("ImageGenerator initialisé")
125
-
126
- def _build_prompt(self, params: Dict[str, Any]) -> str:
127
- """Construction de prompt améliorée avec le PromptEnhancer"""
128
- try:
129
- # Initialisation du PromptEnhancer
130
- enhancer = PromptEnhancer()
131
-
132
- # Récupération du style
133
- style_info = ART_STYLES.get(params["style"], ART_STYLES["Art Moderne"])
134
-
135
- # Construction du contexte de style
136
- style_context = {
137
- "prompt_prefix": style_info['prompt_prefix'],
138
- "layout": COMPOSITION_PARAMS['Layouts'][params['layout']],
139
- "ambiance": COMPOSITION_PARAMS['Ambiances'][params['ambiance']],
140
- "palette": COMPOSITION_PARAMS['Palette'][params['palette']]
141
- }
142
-
143
- # Préparation du prompt initial
144
- base_prompt = f"{params['subject']}"
145
- if params.get('title'):
146
- base_prompt += f", with text '{params['title']}'"
147
-
148
- # Amélioration du prompt
149
- enhanced_prompt = enhancer.enhance_prompt(base_prompt, style_context)
150
-
151
- # Analyse de l'efficacité du prompt
152
- prompt_analysis = enhancer.analyze_prompt_effectiveness(enhanced_prompt)
153
- logger.debug(f"Analyse du prompt: {json.dumps(prompt_analysis, indent=2)}")
154
-
155
- return enhanced_prompt
156
-
157
- except Exception as e:
158
- logger.error(f"Erreur dans la construction du prompt: {str(e)}")
159
- # Fallback sur le prompt basique en cas d'erreur
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
- prompt = self._build_prompt(params)
170
-
171
- payload = {
172
- "inputs": prompt,
173
- "parameters": {
174
- "negative_prompt": ART_STYLES[params["style"]]["negative_prompt"],
175
- "num_inference_steps": min(int(35 * (params["quality"]/100)), 40),
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=30
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}"
196
- logger.error(error_msg)
197
- return None, error_msg
198
-
199
  except Exception as e:
200
- error_msg = f"⚠️ Erreur: {str(e)}"
201
- logger.exception("Erreur pendant la génération:")
202
- return None, error_msg
203
- finally:
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 { text-align: center; margin: 20px 0; padding: 20px; background: #1e293b; border-radius: 10px; }
212
- .controls-group { background: #2d3748; padding: 15px; border-radius: 5px; margin: 10px 0; }
213
- .advanced-controls { background: #374151; padding: 12px; border-radius: 5px; margin: 8px 0; }
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
- # Zone d'introduction et guide
228
- gr.Markdown("""
229
- ### 🎯 Guide d'utilisation
230
- 1. Choisissez le format et l'orientation de votre affiche
231
- 2. Sélectionnez un style artistique et une composition
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():
240
- format_size = gr.Dropdown(
241
- choices=["A4", "A3", "A2", "A1", "A0"],
242
- value="A4",
243
- label="Format",
244
- info="Choisissez la taille de votre affiche. A4 est le plus petit, A0 le plus grand."
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=list(COMPOSITION_PARAMS["Palette"].keys()),
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("""### 📝 Contenu
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
- info="Décrivez en détail ce que vous souhaitez voir dans votre affiche"
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("""### 🎯 Ajustements Fins
299
- *Ces paramètres permettent d'affiner le résultat final*""")
300
  with gr.Row():
301
- detail_level = gr.Slider(
302
- minimum=1,
303
- maximum=10,
304
- value=7,
305
- step=1,
306
- label="Niveau de Détail",
307
- info="Plus la valeur est élevée, plus l'image sera détaillée"
308
  )
309
- contrast = gr.Slider(
310
  minimum=1,
311
- maximum=10,
312
- value=5,
313
- step=1,
314
- label="Contraste",
315
- info="Influence l'intensité des couleurs et la différence entre les zones claires et sombres"
316
- )
317
- saturation = gr.Slider(
318
- minimum=1,
319
- maximum=10,
320
- value=5,
321
- step=1,
322
- label="Saturation",
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():
329
- quality = gr.Slider(
330
- minimum=30,
331
- maximum=50,
332
- value=35,
333
- label="Qualité",
334
- info="Influence la qualité finale de l'image. Une valeur plus élevée prend plus de temps"
335
  )
336
- creativity = gr.Slider(
337
- minimum=5,
338
- maximum=15,
339
- value=7.5,
340
- label="Créativité",
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
- image_output = gr.Image(label="Aperçu")
349
- status = gr.Textbox(label="Statut", interactive=False)
350
-
351
- def generate(*args):
352
- logger.info("Démarrage d'une nouvelle génération")
 
 
 
 
 
 
 
 
 
353
  params = {
354
- "format_size": args[0],
355
- "orientation": args[1],
356
- "style": args[2],
357
- "layout": args[3],
358
- "ambiance": args[4],
359
- "palette": args[5],
360
- "subject": args[6],
361
- "title": args[7],
362
- "detail_level": args[8],
363
- "contrast": args[9],
364
- "saturation": args[10],
365
- "quality": args[11],
366
- "creativity": args[12]
367
  }
368
- result = generator.generate(params)
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=[
375
- format_size,
376
- orientation,
377
- style,
378
- layout,
379
- ambiance,
380
- palette,
381
- subject,
382
- title,
383
- detail_level,
384
- contrast,
385
- saturation,
386
- quality,
387
- creativity
388
- ],
389
- outputs=[image_output, status]
390
- )
391
-
392
- clear_btn.click(
393
- lambda: (None, "🗑️ Image effacée"),
394
- outputs=[image_output, status]
395
  )
396
 
397
- logger.info("Interface créée avec succès")
398
- return app
399
 
400
  if __name__ == "__main__":
401
- app = create_interface()
402
- logger.info("Démarrage de l'application")
403
- app.launch()
 
1
  import gradio as gr
2
+ from typing import Dict, Any, Tuple, Optional
3
  from PIL import Image
4
+ import torch
5
+ from diffusers import StableDiffusionXLPipeline, DPMSolverMultistepScheduler
 
 
 
6
  import logging
7
+ from dataclasses import dataclass
8
  from prompt_enhancer import PromptEnhancer
9
 
10
+ @dataclass
11
+ class GenerationConfig:
12
+ width: int = 1024
13
+ height: int = 1024
14
+ num_inference_steps: int = 50
15
+ guidance_scale: float = 7.5
16
+ high_noise_frac: float = 0.8
17
+ negative_prompt: str = ""
18
 
19
+ class EnhancedImageGenerator:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  def __init__(self):
21
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
22
+ self.pipeline = self._initialize_pipeline()
23
+ self.prompt_enhancer = PromptEnhancer()
24
+
25
+ def _initialize_pipeline(self) -> StableDiffusionXLPipeline:
26
+ pipeline = StableDiffusionXLPipeline.from_pretrained(
27
+ "stabilityai/stable-diffusion-xl-base-1.0",
28
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
29
+ use_safetensors=True,
30
+ variant="fp16" if self.device == "cuda" else None
31
+ )
32
+
33
+ # Optimisations cruciales pour la qualité
34
+ pipeline.scheduler = DPMSolverMultistepScheduler.from_config(
35
+ pipeline.scheduler.config,
36
+ algorithm_type="dpmsolver++",
37
+ use_karras_sigmas=True
38
+ )
39
+
40
+ if self.device == "cuda":
41
+ pipeline.enable_xformers_memory_efficient_attention()
42
+ pipeline.enable_model_cpu_offload()
43
+
44
+ return pipeline
45
+
46
+ def _enhance_prompt(self, base_prompt: str, style_params: Dict[str, Any]) -> str:
47
+ """Amélioration avancée des prompts avec analyse contextuelle"""
48
+ enhanced = self.prompt_enhancer.enhance(
49
+ base_prompt,
50
+ style=style_params["style"],
51
+ composition=style_params["composition"],
52
+ mood=style_params["mood"]
53
+ )
54
+
55
+ # Ajout d'optimisations de qualité spécifiques
56
+ quality_terms = [
57
+ "masterpiece",
58
+ "highly detailed",
59
+ "professional",
60
+ "award winning",
61
+ "stunning",
62
+ f"resolution {style_params.get('resolution', '8k')}",
63
+ "perfect composition"
64
+ ]
65
+
66
+ return f"{enhanced}, {', '.join(quality_terms)}"
67
+
68
+ def generate(
69
+ self,
70
+ params: Dict[str, Any],
71
+ config: GenerationConfig
72
+ ) -> Tuple[Optional[Image.Image], str]:
73
  try:
74
+ # Optimisation du prompt
75
+ enhanced_prompt = self._enhance_prompt(params["prompt"], params)
76
+
77
+ # Configuration avancée de la génération
78
+ with torch.inference_mode():
79
+ image = self.pipeline(
80
+ prompt=enhanced_prompt,
81
+ negative_prompt=config.negative_prompt,
82
+ width=config.width,
83
+ height=config.height,
84
+ num_inference_steps=config.num_inference_steps,
85
+ guidance_scale=config.guidance_scale,
86
+ high_noise_frac=config.high_noise_frac,
87
+ ).images[0]
88
+
89
+ return image, "Génération réussie!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  except Exception as e:
91
+ logging.error(f"Erreur lors de la génération: {str(e)}")
92
+ return None, f"Erreur: {str(e)}"
93
+
94
+ def create_enhanced_interface():
95
+ # Styles enrichis avec paramètres optimisés
96
+ ENHANCED_STYLES = {
97
+ "Ultra Réaliste": {
98
+ "prompt_enhancement": "ultra photorealistic, 8k UHD, hyperdetailed",
99
+ "quality_boost": 1.2,
100
+ "steps_multiplier": 1.3
101
+ },
102
+ "Artistique Pro": {
103
+ "prompt_enhancement": "professional artistic composition, perfect lighting",
104
+ "quality_boost": 1.1,
105
+ "steps_multiplier": 1.2
106
+ }
107
+ # ... autres styles
108
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
+ with gr.Blocks(theme=gr.themes.Soft()) as interface:
111
+ with gr.Row():
112
+ with gr.Column(scale=2):
113
+ # Interface utilisateur améliorée
114
+ prompt_input = gr.Textbox(
115
  label="Description",
116
+ placeholder="Décrivez votre vision en détail...",
117
+ lines=3
118
  )
119
+
 
 
 
 
 
 
 
 
120
  with gr.Row():
121
+ style_selector = gr.Dropdown(
122
+ choices=list(ENHANCED_STYLES.keys()),
123
+ label="Style",
124
+ value="Ultra Réaliste"
 
 
 
125
  )
126
+ quality_slider = gr.Slider(
127
  minimum=1,
128
+ maximum=5,
129
+ value=4,
130
+ label="Niveau de Qualité",
131
+ info="Impact la finesse des détails"
 
 
 
 
 
 
 
 
 
132
  )
133
 
 
 
134
  with gr.Row():
135
+ resolution_selector = gr.Radio(
136
+ choices=["4K", "8K"],
137
+ value="8K",
138
+ label="Résolution"
 
 
139
  )
140
+
141
+ aspect_ratio = gr.Radio(
142
+ choices=["1:1", "16:9", "9:16"],
143
+ value="1:1",
144
+ label="Format"
 
145
  )
146
 
147
+ with gr.Column(scale=3):
148
+ output_image = gr.Image(label="Résultat")
149
+ status = gr.Textbox(label="Status")
150
+
151
+ generate_btn = gr.Button("Générer", variant="primary")
152
+
153
+ # Logique de génération optimisée
154
+ def generate_optimized(prompt, style, quality, resolution, aspect):
155
+ generator = EnhancedImageGenerator()
156
+
157
+ # Configuration adaptative
158
+ config = GenerationConfig(
159
+ num_inference_steps=int(50 * ENHANCED_STYLES[style]["steps_multiplier"]),
160
+ guidance_scale=7.5 * ENHANCED_STYLES[style]["quality_boost"],
161
+ width=3840 if resolution == "4K" else 7680,
162
+ height=2160 if resolution == "4K" else 4320
163
+ )
164
+
165
  params = {
166
+ "prompt": prompt,
167
+ "style": style,
168
+ "resolution": resolution,
169
+ "quality_level": quality
 
 
 
 
 
 
 
 
 
170
  }
171
+
172
+ return generator.generate(params, config)
 
173
 
174
  generate_btn.click(
175
+ generate_optimized,
176
+ inputs=[prompt_input, style_selector, quality_slider,
177
+ resolution_selector, aspect_ratio],
178
+ outputs=[output_image, status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  )
180
 
181
+ return interface
 
182
 
183
  if __name__ == "__main__":
184
+ app = create_enhanced_interface()
185
+ app.launch()