ChevalierJoseph commited on
Commit
9bd085b
·
verified ·
1 Parent(s): bd6b488

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -121
app.py CHANGED
@@ -43,7 +43,6 @@ def generate_glyphs_html(glyphs, cols=5, width=100, height=100):
43
  </svg>
44
  """
45
  html_parts.append(f"<div style='display: inline-block; margin: 10px; text-align: center;'><h3>{lettre}</h3>{svg_content}</div>")
46
-
47
  grid_style = f"display: grid; grid-template-columns: repeat({cols}, 1fr); gap: 20px;"
48
  return f'<div style="{grid_style}">{"".join(html_parts)}</div>'
49
 
@@ -61,98 +60,82 @@ def generate_svg_files(glyphs, width=100, height=100):
61
  return svg_files
62
 
63
  def draw_svg_path(pen, path_string):
64
- """
65
- Parse SVG path string and draw it using the given pen.
66
- Args:
67
- pen: The drawing pen
68
- path_string (str): SVG path string
69
- """
70
- # Simple SVG path parser - handles basic commands
71
- # This is a simplified version - you might need to extend it for complex paths
72
  commands = re.findall(r'[MLHVCSQTAZmlhvcsqtaz][^MLHVCSQTAZmlhvcsqtaz]*', path_string)
73
  current_x, current_y = 0, 0
74
  for command in commands:
75
  cmd = command[0]
76
  coords = re.findall(r'-?\d+(?:\.\d+)?', command[1:])
77
  coords = [float(c) for c in coords]
78
- if cmd == 'M': # Move to (absolute)
79
  if len(coords) >= 2:
80
- current_x, current_y = coords[0], -coords[1] # Inverser l'axe Y
81
  pen.moveTo((current_x, current_y))
82
- elif cmd == 'm': # Move to (relative)
83
  if len(coords) >= 2:
84
  current_x += coords[0]
85
- current_y -= coords[1] # Inverser l'axe Y
86
  pen.moveTo((current_x, current_y))
87
- elif cmd == 'L': # Line to (absolute)
88
  for i in range(0, len(coords), 2):
89
  if i + 1 < len(coords):
90
- current_x, current_y = coords[i], -coords[i + 1] # Inverser l'axe Y
91
  pen.lineTo((current_x, current_y))
92
- elif cmd == 'l': # Line to (relative)
93
  for i in range(0, len(coords), 2):
94
  if i + 1 < len(coords):
95
  current_x += coords[i]
96
- current_y -= coords[i + 1] # Inverser l'axe Y
97
  pen.lineTo((current_x, current_y))
98
- elif cmd == 'H': # Horizontal line to (absolute)
99
  for x in coords:
100
  current_x = x
101
  pen.lineTo((current_x, current_y))
102
- elif cmd == 'h': # Horizontal line to (relative)
103
  for dx in coords:
104
  current_x += dx
105
  pen.lineTo((current_x, current_y))
106
- elif cmd == 'V': # Vertical line to (absolute)
107
  for y in coords:
108
- current_y = -y # Inverser l'axe Y
109
  pen.lineTo((current_x, current_y))
110
- elif cmd == 'v': # Vertical line to (relative)
111
  for dy in coords:
112
- current_y -= dy # Inverser l'axe Y
113
  pen.lineTo((current_x, current_y))
114
- elif cmd == 'C': # Cubic bezier curve (absolute)
115
  for i in range(0, len(coords), 6):
116
  if i + 5 < len(coords):
117
- x1, y1 = coords[i], -coords[i + 1] # Inverser l'axe Y
118
- x2, y2 = coords[i + 2], -coords[i + 3] # Inverser l'axe Y
119
- current_x, current_y = coords[i + 4], -coords[i + 5] # Inverser l'axe Y
120
  pen.curveTo((x1, y1), (x2, y2), (current_x, current_y))
121
- elif cmd == 'c': # Cubic bezier curve (relative)
122
  for i in range(0, len(coords), 6):
123
  if i + 5 < len(coords):
124
  x1 = current_x + coords[i]
125
- y1 = current_y - coords[i + 1] # Inverser l'axe Y
126
  x2 = current_x + coords[i + 2]
127
- y2 = current_y - coords[i + 3] # Inverser l'axe Y
128
  current_x += coords[i + 4]
129
- current_y -= coords[i + 5] # Inverser l'axe Y
130
  pen.curveTo((x1, y1), (x2, y2), (current_x, current_y))
131
- elif cmd in ['Z', 'z']: # Close path
132
  pen.closePath()
133
  pen.endPath()
134
 
135
  def create_otf_font(glyphs, output_path="font.otf"):
136
  glyph_paths = {lettre: path for lettre, path in glyphs}
137
-
138
- # Add default .notdef if not present (simple rectangle)
139
- default_notdef_path = "M50 700H450V0H50Z M100 600L400 100 M400 600L100 100" # Box with X, Y down for SVG
140
  if '.notdef' not in glyph_paths:
141
  glyph_paths['.notdef'] = default_notdef_path
142
-
143
- # Prepare glyph order
144
  glyph_names = list(glyph_paths.keys())
145
  if glyph_names[0] != '.notdef':
146
- glyph_names.insert(0, '.notdef') # Ensure .notdef is first
147
-
148
- # Prepare Unicode mapping
149
  unicode_map = {}
150
  for glyph_name in glyph_paths:
151
  if len(glyph_name) == 1 and glyph_name.isalpha():
152
  unicode_value = ord(glyph_name.upper())
153
  unicode_map[unicode_value] = glyph_name
154
-
155
- # Prepare PrivateDict with defaults
156
  private_dict = PrivateDict()
157
  private_dict.nominalWidthX = 600
158
  private_dict.defaultWidthX = 600
@@ -165,20 +148,16 @@ def create_otf_font(glyphs, output_path="font.otf"):
165
  private_dict.BlueFuzz = 1
166
  private_dict.StdHW = [100]
167
  private_dict.StdVW = [100]
168
-
169
- # Create charstrings
170
  char_strings = {}
171
  for glyph_name, svg_path in glyph_paths.items():
172
  try:
173
- pen = T2CharStringPen(600, None) # Width 600, no glyphSet yet
174
  draw_svg_path(pen, svg_path)
175
  charstring = pen.getCharString(private=private_dict)
176
  char_strings[glyph_name] = charstring
177
  except Exception as e:
178
  print(f"Error converting glyph {glyph_name}: {e}")
179
  traceback.print_exc()
180
-
181
- # Prepare font names
182
  new_font_name = "Custom Glyph Font"
183
  family_name = new_font_name
184
  style_name = "Regular"
@@ -189,20 +168,13 @@ def create_otf_font(glyphs, output_path="font.otf"):
189
  "styleName": style_name,
190
  "fullName": full_name,
191
  "psName": ps_name,
192
- # Add more localized if needed
193
  }
194
-
195
- # Setup FontBuilder
196
  try:
197
- fb = FontBuilder(1000, isTTF=False) # unitsPerEm=1000, OTF (CFF)
198
  fb.setupGlyphOrder(glyph_names)
199
  fb.setupCharacterMap(unicode_map)
200
-
201
- # Setup CFF table with privateDicts
202
  top_dict_data = {"FullName": full_name}
203
  fb.setupCFF(ps_name, top_dict_data, char_strings, {ps_name: private_dict})
204
-
205
- # Calculate metrics with debug
206
  advance_widths = {gn: 600 for gn in glyph_names}
207
  metrics = {}
208
  max_ascent = 0
@@ -218,10 +190,8 @@ def create_otf_font(glyphs, output_path="font.otf"):
218
  except Exception as e:
219
  print(f"Error calculating bounds for {gn}: {e}")
220
  traceback.print_exc()
221
- metrics[gn] = (advance_widths[gn], 0) # Fallback
222
-
223
  fb.setupHorizontalMetrics(metrics)
224
- # Adjust ascent/descent based on glyphs if possible
225
  ascent = max(800, max_ascent)
226
  descent = min(-200, min_descent)
227
  fb.setupHorizontalHeader(ascent=ascent, descent=descent)
@@ -232,11 +202,8 @@ def create_otf_font(glyphs, output_path="font.otf"):
232
  print(f"Error building font: {e}")
233
  traceback.print_exc()
234
  return None
235
-
236
- # Save the font
237
  try:
238
  fb.save(output_path)
239
- # Basic validation
240
  test_font = TTFont(output_path)
241
  test_font.close()
242
  return output_path
@@ -259,15 +226,12 @@ def respond(message: str, system_message: str, max_tokens: int, temperature: flo
259
  if torch.cuda.is_available():
260
  model = model.to('cuda')
261
  model_device = next(model.parameters()).device
262
-
263
  messages = [{"role": "system", "content": system_message}]
264
  messages.append({"role": "user", "content": message})
265
  inputs = tokenizer.apply_chat_template(
266
  messages, tokenize=True, add_generation_prompt=True, return_tensors="pt",
267
  ).to(model_device)
268
-
269
  streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
270
-
271
  do_sample_effective = True
272
  if temperature == 0.0:
273
  pass
@@ -286,57 +250,29 @@ def respond(message: str, system_message: str, max_tokens: int, temperature: flo
286
  del generation_kwargs["temperature"]
287
  if "top_p" in generation_kwargs:
288
  del generation_kwargs["top_p"]
289
-
290
  thread = Thread(target=model.generate, kwargs=generation_kwargs)
291
  thread.start()
292
-
293
  partial_response = ""
294
  for new_text in streamer:
295
  partial_response += new_text
296
  glyphs = extract_glyphs(partial_response)
297
  yield partial_response, glyphs
298
-
299
  thread.join()
300
 
301
  def create_demo():
302
- theme = gr.themes.Soft(
303
- primary_hue="blue",
304
- secondary_hue="gray",
305
- neutral_hue="slate",
306
- spacing_size="md",
307
- radius_size="lg",
308
- font=[gr.themes.GoogleFont("Montserrat"), "sans-serif"],
309
- )
310
-
311
- with gr.Blocks(theme=theme, css="""
312
- .gradio-container { max-width: 1200px; margin: auto; }
313
- #preview-html { border: 1px solid var(--border-color-primary); border-radius: var(--radius-lg); padding: 20px; background: white; overflow-y: auto; max-height: 600px; }
314
- .glyph-div { box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-radius: var(--radius-md); padding: 10px; background: var(--color-background-secondary); }
315
- h1 { text-align: center; margin-bottom: 20px; }
316
- .input-textbox { font-size: 16px; }
317
- .download-btn { margin-top: 10px; }
318
- """) as demo:
319
- gr.Markdown("<h1 style='font-family: Montserrat;'>🖋️ TypTopType: AI-Powered Glyph Generator</h1>")
320
- gr.Markdown("Describe a font style, and let the AI generate custom SVG glyphs from A to Z. Preview, customize, and export!")
321
 
322
- glyphs_state = gr.State([])
323
- message_history = gr.State([])
324
-
325
- with gr.Row(variant="panel"):
326
  with gr.Column(scale=1):
327
- gr.Markdown("### Input Your Font Description")
328
- msg = gr.Textbox(
329
- label="Describe the font (e.g., 'a bold serif font with elegant curves')",
330
- placeholder="Enter your font style description here...",
331
- lines=3,
332
- elem_classes="input-textbox"
333
- )
334
- system_message = gr.Textbox(
335
- value="Based on the following text, give me the svgpath of the glyphs from A to Z.",
336
- visible=False
337
- )
338
-
339
- with gr.Accordion("Advanced Generation Settings", open=False):
340
  max_tokens = gr.Slider(
341
  minimum=1, maximum=9048, value=9048, step=1, label="Max Tokens"
342
  )
@@ -346,27 +282,34 @@ def create_demo():
346
  top_p = gr.Slider(
347
  minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top P"
348
  )
349
-
350
- with gr.Accordion("Preview Customization", open=False):
351
  cols = gr.Slider(
352
- minimum=1, maximum=10, value=5, step=1, label="Columns in Grid"
353
  )
354
  width = gr.Slider(
355
- minimum=50, maximum=200, value=100, step=10, label="Glyph Width"
356
  )
357
  height = gr.Slider(
358
- minimum=50, maximum=200, value=100, step=10, label="Glyph Height"
359
  )
360
-
361
- with gr.Row():
362
- download_btn = gr.Button("Download SVG ZIP", variant="primary", elem_classes="download-btn")
363
- otf_export_btn = gr.Button("Export OTF Font", variant="secondary", elem_classes="download-btn")
364
-
365
- download_output = gr.File(label="Your Exported File", interactive=False)
366
-
367
  with gr.Column(scale=3):
368
- gr.Markdown("### Glyph Preview")
369
- svg_preview = gr.HTML(label="SVG Preview", elem_id="preview-html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
  def user(user_message, history):
372
  return "", history + [[user_message, None]]
@@ -380,9 +323,8 @@ def create_demo():
380
  if glyphs:
381
  glyphs_list = glyphs
382
  svg_html = generate_glyphs_html(glyphs_list, cols=cols, width=width, height=height)
383
- svg_html = svg_html.replace("<div style='display: inline-block; margin: 10px; text-align: center;'>", "<div class='glyph-div' style='display: inline-block; margin: 10px; text-align: center;'>")
384
  else:
385
- svg_html = "<p>No glyphs generated yet. Try a different description!</p>"
386
  yield svg_html, glyphs_list
387
 
388
  def download_svg(glyphs, width, height):
@@ -403,6 +345,14 @@ def create_demo():
403
  else:
404
  return None
405
 
 
 
 
 
 
 
 
 
406
  msg.submit(
407
  user, [msg, message_history], [msg, message_history], queue=False
408
  ).then(
@@ -416,7 +366,6 @@ def create_demo():
416
  inputs=[glyphs_state, width, height],
417
  outputs=download_output
418
  )
419
-
420
  otf_export_btn.click(
421
  export_to_otf,
422
  inputs=[glyphs_state],
@@ -428,4 +377,4 @@ def create_demo():
428
  demo = create_demo()
429
 
430
  if __name__ == "__main__":
431
- demo.launch()
 
43
  </svg>
44
  """
45
  html_parts.append(f"<div style='display: inline-block; margin: 10px; text-align: center;'><h3>{lettre}</h3>{svg_content}</div>")
 
46
  grid_style = f"display: grid; grid-template-columns: repeat({cols}, 1fr); gap: 20px;"
47
  return f'<div style="{grid_style}">{"".join(html_parts)}</div>'
48
 
 
60
  return svg_files
61
 
62
  def draw_svg_path(pen, path_string):
 
 
 
 
 
 
 
 
63
  commands = re.findall(r'[MLHVCSQTAZmlhvcsqtaz][^MLHVCSQTAZmlhvcsqtaz]*', path_string)
64
  current_x, current_y = 0, 0
65
  for command in commands:
66
  cmd = command[0]
67
  coords = re.findall(r'-?\d+(?:\.\d+)?', command[1:])
68
  coords = [float(c) for c in coords]
69
+ if cmd == 'M':
70
  if len(coords) >= 2:
71
+ current_x, current_y = coords[0], -coords[1]
72
  pen.moveTo((current_x, current_y))
73
+ elif cmd == 'm':
74
  if len(coords) >= 2:
75
  current_x += coords[0]
76
+ current_y -= coords[1]
77
  pen.moveTo((current_x, current_y))
78
+ elif cmd == 'L':
79
  for i in range(0, len(coords), 2):
80
  if i + 1 < len(coords):
81
+ current_x, current_y = coords[i], -coords[i + 1]
82
  pen.lineTo((current_x, current_y))
83
+ elif cmd == 'l':
84
  for i in range(0, len(coords), 2):
85
  if i + 1 < len(coords):
86
  current_x += coords[i]
87
+ current_y -= coords[i + 1]
88
  pen.lineTo((current_x, current_y))
89
+ elif cmd == 'H':
90
  for x in coords:
91
  current_x = x
92
  pen.lineTo((current_x, current_y))
93
+ elif cmd == 'h':
94
  for dx in coords:
95
  current_x += dx
96
  pen.lineTo((current_x, current_y))
97
+ elif cmd == 'V':
98
  for y in coords:
99
+ current_y = -y
100
  pen.lineTo((current_x, current_y))
101
+ elif cmd == 'v':
102
  for dy in coords:
103
+ current_y -= dy
104
  pen.lineTo((current_x, current_y))
105
+ elif cmd == 'C':
106
  for i in range(0, len(coords), 6):
107
  if i + 5 < len(coords):
108
+ x1, y1 = coords[i], -coords[i + 1]
109
+ x2, y2 = coords[i + 2], -coords[i + 3]
110
+ current_x, current_y = coords[i + 4], -coords[i + 5]
111
  pen.curveTo((x1, y1), (x2, y2), (current_x, current_y))
112
+ elif cmd == 'c':
113
  for i in range(0, len(coords), 6):
114
  if i + 5 < len(coords):
115
  x1 = current_x + coords[i]
116
+ y1 = current_y - coords[i + 1]
117
  x2 = current_x + coords[i + 2]
118
+ y2 = current_y - coords[i + 3]
119
  current_x += coords[i + 4]
120
+ current_y -= coords[i + 5]
121
  pen.curveTo((x1, y1), (x2, y2), (current_x, current_y))
122
+ elif cmd in ['Z', 'z']:
123
  pen.closePath()
124
  pen.endPath()
125
 
126
  def create_otf_font(glyphs, output_path="font.otf"):
127
  glyph_paths = {lettre: path for lettre, path in glyphs}
128
+ default_notdef_path = "M50 700H450V0H50Z M100 600L400 100 M400 600L100 100"
 
 
129
  if '.notdef' not in glyph_paths:
130
  glyph_paths['.notdef'] = default_notdef_path
 
 
131
  glyph_names = list(glyph_paths.keys())
132
  if glyph_names[0] != '.notdef':
133
+ glyph_names.insert(0, '.notdef')
 
 
134
  unicode_map = {}
135
  for glyph_name in glyph_paths:
136
  if len(glyph_name) == 1 and glyph_name.isalpha():
137
  unicode_value = ord(glyph_name.upper())
138
  unicode_map[unicode_value] = glyph_name
 
 
139
  private_dict = PrivateDict()
140
  private_dict.nominalWidthX = 600
141
  private_dict.defaultWidthX = 600
 
148
  private_dict.BlueFuzz = 1
149
  private_dict.StdHW = [100]
150
  private_dict.StdVW = [100]
 
 
151
  char_strings = {}
152
  for glyph_name, svg_path in glyph_paths.items():
153
  try:
154
+ pen = T2CharStringPen(600, None)
155
  draw_svg_path(pen, svg_path)
156
  charstring = pen.getCharString(private=private_dict)
157
  char_strings[glyph_name] = charstring
158
  except Exception as e:
159
  print(f"Error converting glyph {glyph_name}: {e}")
160
  traceback.print_exc()
 
 
161
  new_font_name = "Custom Glyph Font"
162
  family_name = new_font_name
163
  style_name = "Regular"
 
168
  "styleName": style_name,
169
  "fullName": full_name,
170
  "psName": ps_name,
 
171
  }
 
 
172
  try:
173
+ fb = FontBuilder(1000, isTTF=False)
174
  fb.setupGlyphOrder(glyph_names)
175
  fb.setupCharacterMap(unicode_map)
 
 
176
  top_dict_data = {"FullName": full_name}
177
  fb.setupCFF(ps_name, top_dict_data, char_strings, {ps_name: private_dict})
 
 
178
  advance_widths = {gn: 600 for gn in glyph_names}
179
  metrics = {}
180
  max_ascent = 0
 
190
  except Exception as e:
191
  print(f"Error calculating bounds for {gn}: {e}")
192
  traceback.print_exc()
193
+ metrics[gn] = (advance_widths[gn], 0)
 
194
  fb.setupHorizontalMetrics(metrics)
 
195
  ascent = max(800, max_ascent)
196
  descent = min(-200, min_descent)
197
  fb.setupHorizontalHeader(ascent=ascent, descent=descent)
 
202
  print(f"Error building font: {e}")
203
  traceback.print_exc()
204
  return None
 
 
205
  try:
206
  fb.save(output_path)
 
207
  test_font = TTFont(output_path)
208
  test_font.close()
209
  return output_path
 
226
  if torch.cuda.is_available():
227
  model = model.to('cuda')
228
  model_device = next(model.parameters()).device
 
229
  messages = [{"role": "system", "content": system_message}]
230
  messages.append({"role": "user", "content": message})
231
  inputs = tokenizer.apply_chat_template(
232
  messages, tokenize=True, add_generation_prompt=True, return_tensors="pt",
233
  ).to(model_device)
 
234
  streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
 
235
  do_sample_effective = True
236
  if temperature == 0.0:
237
  pass
 
250
  del generation_kwargs["temperature"]
251
  if "top_p" in generation_kwargs:
252
  del generation_kwargs["top_p"]
 
253
  thread = Thread(target=model.generate, kwargs=generation_kwargs)
254
  thread.start()
 
255
  partial_response = ""
256
  for new_text in streamer:
257
  partial_response += new_text
258
  glyphs = extract_glyphs(partial_response)
259
  yield partial_response, glyphs
 
260
  thread.join()
261
 
262
  def create_demo():
263
+ with gr.Blocks(theme=gr.themes.Default()) as demo:
264
+ gr.Markdown("# TypTopType")
265
+ gr.Markdown("## Générateur de glyphes à partir de texte")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
+ with gr.Row():
 
 
 
268
  with gr.Column(scale=1):
269
+ msg = gr.Textbox(label="Entrée", placeholder="Tapez votre texte ici...")
270
+ send_btn = gr.Button("Envoyer", variant="primary", icon="📤")
271
+ with gr.Accordion("Paramètres avancés", open=False):
272
+ system_message = gr.Textbox(
273
+ value="Based on the following text, give me the svgpath of the glyphs from A to Z.",
274
+ label="Message système"
275
+ )
 
 
 
 
 
 
276
  max_tokens = gr.Slider(
277
  minimum=1, maximum=9048, value=9048, step=1, label="Max Tokens"
278
  )
 
282
  top_p = gr.Slider(
283
  minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top P"
284
  )
 
 
285
  cols = gr.Slider(
286
+ minimum=1, maximum=10, value=5, step=1, label="Colonnes"
287
  )
288
  width = gr.Slider(
289
+ minimum=50, maximum=200, value=100, step=10, label="Largeur"
290
  )
291
  height = gr.Slider(
292
+ minimum=50, maximum=200, value=100, step=10, label="Hauteur"
293
  )
294
+ download_btn = gr.Button("Télécharger SVG", variant="primary", icon="📁")
295
+ otf_export_btn = gr.Button("Exporter OTF", variant="primary", icon="📱")
 
 
 
 
 
296
  with gr.Column(scale=3):
297
+ gr.Markdown("## Aperçu")
298
+ svg_preview = gr.HTML(label="Aperçu SVG")
299
+ download_output = gr.File(label="Télécharger ZIP")
300
+
301
+ demo.css = """
302
+ .gr-box {
303
+ border-radius: 10px;
304
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
305
+ }
306
+ .gr-button {
307
+ border-radius: 5px;
308
+ }
309
+ """
310
+
311
+ glyphs_state = gr.State([])
312
+ message_history = gr.State([])
313
 
314
  def user(user_message, history):
315
  return "", history + [[user_message, None]]
 
323
  if glyphs:
324
  glyphs_list = glyphs
325
  svg_html = generate_glyphs_html(glyphs_list, cols=cols, width=width, height=height)
 
326
  else:
327
+ svg_html = "No glyphs found."
328
  yield svg_html, glyphs_list
329
 
330
  def download_svg(glyphs, width, height):
 
345
  else:
346
  return None
347
 
348
+ send_btn.click(
349
+ user, [msg, message_history], [msg, message_history], queue=False
350
+ ).then(
351
+ bot,
352
+ [message_history, system_message, max_tokens, temperature, top_p, cols, width, height],
353
+ [svg_preview, glyphs_state]
354
+ )
355
+
356
  msg.submit(
357
  user, [msg, message_history], [msg, message_history], queue=False
358
  ).then(
 
366
  inputs=[glyphs_state, width, height],
367
  outputs=download_output
368
  )
 
369
  otf_export_btn.click(
370
  export_to_otf,
371
  inputs=[glyphs_state],
 
377
  demo = create_demo()
378
 
379
  if __name__ == "__main__":
380
+ demo.launch()