TutuAwad commited on
Commit
eb69c9b
·
verified ·
1 Parent(s): 71924d4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +421 -33
app.py CHANGED
@@ -18,6 +18,8 @@ from huggingface_hub import InferenceClient
18
  import spotipy
19
  from spotipy.oauth2 import SpotifyClientCredentials
20
  from difflib import SequenceMatcher
 
 
21
 
22
  # ---------- Load data ----------
23
  CLEAN_CSV_PATH = "df_combined_clean.csv"
@@ -168,54 +170,431 @@ def make_bg_style_html(query=None):
168
  return f"<style>:root {{--hf-bg-top:{base_top};--hf-bg-bottom:{base_bottom};}}</style>"
169
 
170
  def results_to_lux_html(results, query):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  if results is None or results.empty:
172
- return "<div class='lux-empty'>🎧 Describe a vibe to start.</div>"
173
- cards = []
174
- for _, r in results.iterrows():
175
- cover = f"<img src='{r['album_image']}' class='lux-cover'/>" if isinstance(r.get("album_image"), str) else "♪"
176
- btn = f"<a href='{r['spotify_url']}' target='_blank' class='lux-btn'>▶ Play on Spotify</a>" if isinstance(r.get("spotify_url"), str) else ""
177
- sim = f"{int(r['similarity_pct'])}%" if pd.notnull(r['similarity_pct']) else ""
178
- vibe = r['vibes']
179
- chip = "🎲 random pick" if r['is_random'] else ""
180
- cards.append(
181
- f"<div class='lux-card'>{cover}<div class='lux-meta'><h3>{r['song']}</h3><p>{r['artist']}</p><span>{vibe}</span> {sim} {chip}{btn}</div></div>"
182
- )
183
- return "<div id='lux-wrapper'>" + "".join(cards) + "</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
- def get_random_vibe():
186
- topics = ["late-night drives","dog bloopers","breakups","sunset beaches","college nostalgia"]
187
- perspectives = ["first-person","third-person","group","inner monologue"]
188
- contexts = ["dreamy","chaotic","romantic","melancholic"]
189
- return f"Lyrics about {random.choice(topics)}, told in {random.choice(perspectives)}, {random.choice(contexts)}."
 
 
 
 
190
 
191
- def clear_all():
192
- return "", "<div class='lux-empty'>🎧 Describe a vibe to start.</div>", make_bg_style_html()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  def search_with_bg(query, k, random_extra):
195
- res = search_pipeline(query, int(k), int(random_extra), use_llama=True)
196
- return results_to_lux_html(res, query), make_bg_style_html(query)
 
 
 
 
 
 
 
 
 
 
197
 
198
  # ---------- CSS ----------
 
199
  app_css = """
200
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;800;900&display=swap');
201
- body,.gradio-container{
202
- background:radial-gradient(circle at 50% 0%,var(--hf-bg-top,#1e293b),var(--hf-bg-bottom,#020617) 80%)!important;
203
- color:#e5e7eb;font-family:'Inter',system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif!important;
204
- }
205
- .lux-card{background:rgba(255,255,255,0.05);border-radius:1rem;padding:1rem;margin:0.5rem;}
206
- .lux-cover{width:100%;border-radius:1rem;}
207
- .lux-btn{display:inline-block;margin-top:0.5rem;padding:0.4rem 0.8rem;background:#1db954;border-radius:9999px;color:white;text-decoration:none;}
208
- .primary-btn{background:#1db954;color:white;border-radius:1rem;}
209
- .secondary-btn{background:#334155;color:white;border-radius:1rem;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  """
211
 
212
  # ---------- Gradio UI ----------
 
213
  with gr.Blocks(title="HarmoniFind") as demo:
214
- # inject CSS manually (Gradio version on Spaces doesn't support css=...)
215
  gr.HTML(f"<style>{app_css}</style>")
216
 
 
217
  bg_style = gr.HTML(make_bg_style_html())
218
 
 
219
  gr.HTML("""
220
  <div id="hf-shell">
221
  <div id="lux-header">
@@ -227,6 +606,7 @@ with gr.Blocks(title="HarmoniFind") as demo:
227
  """)
228
 
229
  with gr.Column():
 
230
  with gr.Row(variant="compact"):
231
  input_box = gr.Textbox(
232
  placeholder="Lyrics about a carefree road trip with too many snack stops",
@@ -239,6 +619,7 @@ with gr.Blocks(title="HarmoniFind") as demo:
239
  surprise_btn = gr.Button("🎲 Surprise me", elem_classes=["secondary-btn"])
240
  clear_btn = gr.Button("Clear", elem_classes=["secondary-btn"])
241
 
 
242
  with gr.Accordion("Search settings", open=False):
243
  with gr.Row():
244
  k_slider = gr.Slider(5, 50, value=10, step=1, label="# semantic matches")
@@ -246,6 +627,7 @@ with gr.Blocks(title="HarmoniFind") as demo:
246
 
247
  output_html = gr.HTML()
248
 
 
249
  input_box.submit(
250
  search_with_bg,
251
  [input_box, k_slider, rand_slider],
@@ -256,14 +638,18 @@ with gr.Blocks(title="HarmoniFind") as demo:
256
  [input_box, k_slider, rand_slider],
257
  [output_html, bg_style],
258
  )
 
 
259
  surprise_btn.click(
260
- get_random_vibe,
261
  outputs=input_box,
262
  ).then(
263
  search_with_bg,
264
  [input_box, k_slider, rand_slider],
265
  [output_html, bg_style],
266
  )
 
 
267
  clear_btn.click(
268
  clear_all,
269
  None,
@@ -271,4 +657,6 @@ with gr.Blocks(title="HarmoniFind") as demo:
271
  )
272
 
273
  if __name__ == "__main__":
274
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
18
  import spotipy
19
  from spotipy.oauth2 import SpotifyClientCredentials
20
  from difflib import SequenceMatcher
21
+ import html as html_lib
22
+
23
 
24
  # ---------- Load data ----------
25
  CLEAN_CSV_PATH = "df_combined_clean.csv"
 
170
  return f"<style>:root {{--hf-bg-top:{base_top};--hf-bg-bottom:{base_bottom};}}</style>"
171
 
172
  def results_to_lux_html(results, query):
173
+ ...
174
+ # ---------- CSS ----------
175
+ app_css = """
176
+ @import url('https://fonts.googleapis.com/css2?family=Inter...
177
+ ...
178
+ # ---------- Background palette + helper ----------
179
+
180
+ BG_PALETTE = [
181
+ ("#1e293b", "#020617"),
182
+ ("#0f172a", "#020617"),
183
+ ("#0b1120", "#020617"),
184
+ ("#111827", "#020617"),
185
+ ("#1f2937", "#020617"),
186
+ ]
187
+
188
+ def make_bg_style_html():
189
+ """Pick a gradient pair from the palette and emit a <style> that sets CSS vars."""
190
+ top, bottom = random.choice(BG_PALETTE)
191
+ return f"<style>:root {{ --hf-bg-top: {top}; --hf-bg-bottom: {bottom}; }}</style>"
192
+
193
+ # ---------- Theming + helpers ----------
194
+
195
+ def infer_theme(query: str):
196
+ q = (query or "").lower()
197
+ if any(w in q for w in ["night", "drive", "highway", "city", "neon"]):
198
+ return {"name": "Midnight Drive", "emoji": "🌃"}
199
+ if any(w in q for w in ["party", "dance", "club", "crowd", "festival"]):
200
+ return {"name": "Nightclub Neon", "emoji": "🎉"}
201
+ if any(w in q for w in ["shower", "bathroom", "mirror", "getting ready"]):
202
+ return {"name": "Mirror Concert", "emoji": "🚿"}
203
+ if any(w in q for w in ["dog", "pet", "cat", "bloopers"]):
204
+ return {"name": "Pet Bloopers", "emoji": "🐶"}
205
+ # default
206
+ return {"name": "", "emoji": "🎧"}
207
+
208
+ # ---------- DataFrame -> HTML ----------
209
+
210
+ def results_to_lux_html(results: pd.DataFrame, query: str) -> str:
211
  if results is None or results.empty:
212
+ return """
213
+ <div id="lux-wrapper">
214
+ <div id="lux-header">
215
+ <div class="lux-subline">HarmoniFind Semantic playlist</div>
216
+ <h1>🎧 Describe a vibe to start</h1>
217
+ <p style="font-size:0.9rem;color:rgba(156,163,175,0.95);margin-top:8px;">
218
+ Type a brief above, or click <strong>🎲</strong> for a fun prompt.
219
+ </p>
220
+ </div>
221
+ </div>
222
+ """
223
+
224
+ theme = infer_theme(query)
225
+ query_safe = html_lib.escape(query or "")
226
+ emoji = theme["emoji"]
227
+
228
+ cards_html = ""
229
+ tracks_plain = []
230
+
231
+ for _, row in results.iterrows():
232
+ raw_artist = str(row.get("artist", ""))
233
+ raw_song = str(row.get("song", ""))
234
+
235
+ artist = html_lib.escape(raw_artist)
236
+ song = html_lib.escape(raw_song)
237
+
238
+ # for clipboard list
239
+ tracks_plain.append(f"{raw_song} — {raw_artist}")
240
+
241
+ is_random = bool(row.get("is_random", False))
242
+
243
+ sim_pct = row.get("similarity_pct", None)
244
+ if pd.isna(sim_pct) or is_random:
245
+ sim_display = "—"
246
+ score_bg = "rgba(148,163,184,0.2)"
247
+ vibes = "pure random"
248
+ else:
249
+ sim_display = f"{float(sim_pct):.1f}%"
250
+ score_bg = "rgba(34,197,94,0.14)"
251
+ vibes = html_lib.escape(str(row.get("vibes", "")))
252
+
253
+ url = row.get("spotify_url", None)
254
+ img = row.get("album_image", None)
255
+
256
+ if isinstance(img, str) and img:
257
+ cover = f'<div class="lux-cover"><img src="{html_lib.escape(img)}"></div>'
258
+ else:
259
+ cover = '<div class="lux-cover">♪</div>'
260
+
261
+ if isinstance(url, str) and url:
262
+ play_btn = f'<a class="lux-play-btn" href="{html_lib.escape(url)}" target="_blank">▶︎ Play on Spotify</a>'
263
+ else:
264
+ play_btn = ""
265
+
266
+ random_chip = ""
267
+ if is_random:
268
+ random_chip = '<span class="lux-chip">🎲 random pick</span>'
269
+
270
+ cards_html += f"""
271
+ <div class="lux-card">
272
+ {cover}
273
+ <div class="lux-main">
274
+ <div class="lux-title-row">
275
+ <div>
276
+ <div class="lux-title">{song}</div>
277
+ <div class="lux-artist">{artist}</div>
278
+ </div>
279
+ <div class="lux-score">
280
+ <div class="lux-score-badge" style="background:{score_bg};">{sim_display}</div>
281
+ <div class="lux-vibes">{vibes}</div>
282
+ </div>
283
+ </div>
284
+ <div class="lux-bottom-row">
285
+ {play_btn}
286
+ {random_chip}
287
+ </div>
288
+ </div>
289
+ </div>
290
+ """
291
+
292
+ # Build track list text for clipboard
293
+ header_line = f"HarmoniFind results for: {query or ''}".strip()
294
+ if not header_line:
295
+ header_line = "HarmoniFind results"
296
+ list_text = header_line + "\n\n" + "\n".join(tracks_plain)
297
+ # escape for JS string
298
+ js_text = (
299
+ list_text
300
+ .replace("\\", "\\\\")
301
+ .replace("'", "\\'")
302
+ .replace("\n", "\\n")
303
+ )
304
 
305
+ meta_html = f"""
306
+ <p>Semantic matches first, plus optional 🎲 discovery if you enabled it.</p>
307
+ <div class="lux-meta">
308
+ <span class="lux-badge">Tracks: {len(results)}</span>
309
+ <a class="lux-pill" href="javascript:void(0);" onclick="navigator.clipboard.writeText('{js_text}');">
310
+ 🔗 Copy Your HarmoniFinds
311
+ </a>
312
+ </div>
313
+ """
314
 
315
+ html = f"""
316
+ <div id="lux-wrapper">
317
+ <div id="lux-header">
318
+ <div class="lux-subline">HarmoniFind • Semantic playlist</div>
319
+ <h1>{emoji} {query_safe or "Untitled vibe"}</h1>
320
+ {meta_html}
321
+ </div>
322
+ <div class="lux-playlist-wrapper">
323
+ {cards_html}
324
+ </div>
325
+ </div>
326
+ """
327
+ return html
328
+
329
+ # ---------- Search + bg wrapper ----------
330
+
331
+ def core_search_html(query, k, random_extra):
332
+ # LLaMA expansion always ON
333
+ results = search_pipeline(
334
+ query=query or "",
335
+ k=int(k),
336
+ random_extra=int(random_extra),
337
+ use_llama=True,
338
+ )
339
+ return results_to_lux_html(results, query or "")
340
 
341
  def search_with_bg(query, k, random_extra):
342
+ """Return playlist HTML + a new background style snippet."""
343
+ playlist_html = core_search_html(query, k, random_extra)
344
+ bg_style_html = make_bg_style_html()
345
+ return playlist_html, bg_style_html
346
+
347
+ def surprise_brief():
348
+ return get_random_vibe()
349
+
350
+ def clear_all():
351
+ # reset query, results (empty state), and bg
352
+ empty_html = results_to_lux_html(None, "")
353
+ return "", empty_html, make_bg_style_html()
354
 
355
  # ---------- CSS ----------
356
+
357
  app_css = """
358
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;800;900&display=swap');
359
+
360
+ /* Shell + base uses CSS variables so we can change bg from Python */
361
+ body, .gradio-container {
362
+ background: radial-gradient(
363
+ circle at 50% 0%,
364
+ var(--hf-bg-top, #1e293b),
365
+ var(--hf-bg-bottom, #020617) 80%
366
+ ) !important;
367
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
368
+ color: #e5e7eb;
369
+ }
370
+ .gradio-container .block {
371
+ background: transparent !important;
372
+ border: none !important;
373
+ box-shadow: none !important;
374
+ }
375
+
376
+ /* Inputs */
377
+ .gradio-container input,
378
+ .gradio-container textarea {
379
+ background: rgba(15,23,42,0.8) !important;
380
+ border: 1px solid rgba(148,163,184,0.6) !important;
381
+ color: #f9fafb !important;
382
+ border-radius: 12px !important;
383
+ font-size: 0.95rem !important;
384
+ transition: all 0.18s ease;
385
+ }
386
+ .gradio-container input:focus,
387
+ .gradio-container textarea:focus {
388
+ border-color: #10b981 !important;
389
+ box-shadow: 0 0 0 2px rgba(16,185,129,0.3) !important;
390
+ }
391
+
392
+ /* Buttons */
393
+ button.primary-btn {
394
+ background: linear-gradient(135deg,#10b981,#059669) !important;
395
+ border: none !important;
396
+ color: #ecfdf5 !important;
397
+ font-weight: 700 !important;
398
+ border-radius: 999px !important;
399
+ padding-inline: 18px !important;
400
+ }
401
+ button.primary-btn:hover {
402
+ transform: translateY(-1px);
403
+ box-shadow: 0 10px 22px -8px rgba(16,185,129,0.6);
404
+ }
405
+ button.secondary-btn {
406
+ background: rgba(15,23,42,0.9) !important;
407
+ color: #cbd5f5 !important;
408
+ border-radius: 999px !important;
409
+ border: 1px solid rgba(148,163,184,0.8) !important;
410
+ padding-inline: 14px !important;
411
+ }
412
+ button.secondary-btn:hover {
413
+ background: rgba(30,64,175,0.9) !important;
414
+ }
415
+
416
+ /* Top shell + header */
417
+ #hf-shell {
418
+ max-width: 960px;
419
+ margin: 0 auto;
420
+ padding: 24px 12px 40px;
421
+ }
422
+ #lux-header {
423
+ text-align: left;
424
+ padding: 14px 4px 8px;
425
+ }
426
+ #lux-header h1 {
427
+ font-size: 2.4rem;
428
+ font-weight: 900;
429
+ background: linear-gradient(to right,#f9fafb,#9ca3af);
430
+ -webkit-background-clip: text;
431
+ -webkit-text-fill-color: transparent;
432
+ margin: 0;
433
+ letter-spacing: -0.06em;
434
+ }
435
+ .lux-subline {
436
+ text-transform: uppercase;
437
+ letter-spacing: 0.20em;
438
+ font-size: 0.75rem;
439
+ color: #10b981;
440
+ margin-bottom: 6px;
441
+ font-weight: 600;
442
+ }
443
+ #lux-header p {
444
+ color: #9ca3af;
445
+ font-size: 0.9rem;
446
+ margin-top: 8px;
447
+ }
448
+
449
+ /* Meta row for tracks + copy-link */
450
+ .lux-meta {
451
+ display:flex;
452
+ flex-wrap:wrap;
453
+ gap:8px;
454
+ margin-top:8px;
455
+ align-items:center;
456
+ font-size:0.8rem;
457
+ color:#e5e7eb;
458
+ }
459
+ .lux-badge {
460
+ font-size: 0.75rem;
461
+ padding: 6px 12px;
462
+ border-radius: 999px;
463
+ border: 1px solid rgba(148,163,184,0.6);
464
+ text-transform: uppercase;
465
+ letter-spacing: 0.12em;
466
+ }
467
+ .lux-pill {
468
+ font-size: 0.75rem;
469
+ padding: 6px 12px;
470
+ border-radius: 999px;
471
+ border: 1px solid rgba(148,163,184,0.6);
472
+ background: rgba(255,255,255,0.04);
473
+ text-decoration:none;
474
+ color:#e5e7eb;
475
+ }
476
+ .lux-pill:hover {
477
+ background: rgba(255,255,255,0.08);
478
+ }
479
+
480
+ /* Playlist wrapper + cards */
481
+ #lux-wrapper {
482
+ max-width: 960px;
483
+ margin: 0 auto;
484
+ padding: 24px 12px 40px;
485
+ }
486
+ .lux-playlist-wrapper {
487
+ margin-top: 12px;
488
+ display: flex;
489
+ flex-direction: column;
490
+ gap: 10px;
491
+ }
492
+ .lux-card {
493
+ display: flex;
494
+ gap: 14px;
495
+ padding: 12px 14px;
496
+ border-radius: 18px;
497
+ background: rgba(15,23,42,0.94);
498
+ border: 1px solid rgba(148,163,184,0.22);
499
+ }
500
+ .lux-cover {
501
+ width: 72px;
502
+ height: 72px;
503
+ border-radius: 14px;
504
+ overflow: hidden;
505
+ background: #020617;
506
+ flex-shrink: 0;
507
+ display:flex;
508
+ align-items:center;
509
+ justify-content:center;
510
+ color:#6b7280;
511
+ font-size: 20px;
512
+ }
513
+ .lux-cover img {
514
+ width: 100%;
515
+ height: 100%;
516
+ object-fit: cover;
517
+ }
518
+ .lux-main {
519
+ flex: 1;
520
+ display: flex;
521
+ flex-direction: column;
522
+ gap: 4px;
523
+ min-width: 0;
524
+ }
525
+ .lux-title-row {
526
+ display: flex;
527
+ justify-content: space-between;
528
+ gap: 8px;
529
+ align-items: flex-start;
530
+ }
531
+ .lux-title {
532
+ font-size: 0.95rem;
533
+ font-weight: 600;
534
+ color: #e5e7eb;
535
+ white-space: nowrap;
536
+ overflow: hidden;
537
+ text-overflow: ellipsis;
538
+ }
539
+ .lux-artist {
540
+ font-size: 0.8rem;
541
+ color: #9ca3af;
542
+ }
543
+ .lux-score {
544
+ display: flex;
545
+ flex-direction: column;
546
+ align-items: flex-end;
547
+ gap: 4px;
548
+ }
549
+ .lux-score-badge {
550
+ font-size: 0.7rem;
551
+ padding: 3px 8px;
552
+ border-radius: 999px;
553
+ background: rgba(34,197,94,0.14);
554
+ color: #bbf7d0;
555
+ }
556
+ .lux-vibes {
557
+ font-size: 0.7rem;
558
+ color: #9ca3af;
559
+ }
560
+ .lux-bottom-row {
561
+ display: flex;
562
+ justify-content: space-between;
563
+ align-items: center;
564
+ gap: 8px;
565
+ margin-top: 2px;
566
+ }
567
+ .lux-play-btn {
568
+ display:inline-flex;
569
+ align-items:center;
570
+ gap:6px;
571
+ padding:7px 12px;
572
+ border-radius:999px;
573
+ background:#22c55e;
574
+ color:#022c22;
575
+ font-size:0.8rem;
576
+ font-weight:600;
577
+ text-decoration:none;
578
+ }
579
+ .lux-chip {
580
+ font-size:0.65rem;
581
+ border-radius:999px;
582
+ padding:3px 7px;
583
+ background:rgba(148,163,184,0.18);
584
+ color:#e5e7eb;
585
+ }
586
  """
587
 
588
  # ---------- Gradio UI ----------
589
+
590
  with gr.Blocks(title="HarmoniFind") as demo:
591
+ # inject CSS manually (HF version of Gradio may not support css=...)
592
  gr.HTML(f"<style>{app_css}</style>")
593
 
594
+ # dynamic bg style holder (updated on each search)
595
  bg_style = gr.HTML(make_bg_style_html())
596
 
597
+ # Header
598
  gr.HTML("""
599
  <div id="hf-shell">
600
  <div id="lux-header">
 
606
  """)
607
 
608
  with gr.Column():
609
+ # Textbox + stacked buttons on the right
610
  with gr.Row(variant="compact"):
611
  input_box = gr.Textbox(
612
  placeholder="Lyrics about a carefree road trip with too many snack stops",
 
619
  surprise_btn = gr.Button("🎲 Surprise me", elem_classes=["secondary-btn"])
620
  clear_btn = gr.Button("Clear", elem_classes=["secondary-btn"])
621
 
622
+ # Sliders only (LLaMA always on)
623
  with gr.Accordion("Search settings", open=False):
624
  with gr.Row():
625
  k_slider = gr.Slider(5, 50, value=10, step=1, label="# semantic matches")
 
627
 
628
  output_html = gr.HTML()
629
 
630
+ # Search updates playlist + bg
631
  input_box.submit(
632
  search_with_bg,
633
  [input_box, k_slider, rand_slider],
 
638
  [input_box, k_slider, rand_slider],
639
  [output_html, bg_style],
640
  )
641
+
642
+ # Surprise: fill box, then search + bg
643
  surprise_btn.click(
644
+ surprise_brief,
645
  outputs=input_box,
646
  ).then(
647
  search_with_bg,
648
  [input_box, k_slider, rand_slider],
649
  [output_html, bg_style],
650
  )
651
+
652
+ # Clear
653
  clear_btn.click(
654
  clear_all,
655
  None,
 
657
  )
658
 
659
  if __name__ == "__main__":
660
+ port = int(os.getenv("PORT", 7860))
661
+ demo.launch(server_name="0.0.0.0", server_port=port)
662
+