lighteternal commited on
Commit
cb90f09
·
verified ·
1 Parent(s): 03bb491

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +89 -107
app.py CHANGED
@@ -126,16 +126,16 @@ CSS = """
126
 
127
  .metric-strip {
128
  display: grid;
129
- grid-template-columns: repeat(3, minmax(0, 1fr));
130
  gap: 0.8rem;
131
  }
132
 
133
  .metric-card {
134
  border: 1px solid var(--line);
135
  background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(248,244,236,0.9));
136
- padding: 0.8rem 0.9rem;
137
  border-radius: 18px;
138
- min-height: 6.4rem;
139
  box-shadow: 0 8px 24px rgba(23,37,45,0.04);
140
  }
141
 
@@ -146,7 +146,7 @@ CSS = """
146
 
147
  .metric-card strong {
148
  display: block;
149
- font-size: 1.15rem;
150
  margin-top: 0.15rem;
151
  color: var(--ink);
152
  }
@@ -161,23 +161,15 @@ CSS = """
161
  text-decoration: underline;
162
  }
163
 
164
- .guide-grid {
165
- display: grid;
166
- grid-template-columns: repeat(3, minmax(0, 1fr));
167
- gap: 0.95rem;
168
- }
169
-
170
- .guide-card {
171
  border: 1px solid var(--line);
172
- background: var(--card-strong);
173
- padding: 1rem 1.05rem;
174
  border-radius: 18px;
175
- box-shadow: 0 10px 24px rgba(23,37,45,0.05);
176
- }
177
-
178
- .guide-card strong {
179
- display: block;
180
- margin-bottom: 0.28rem;
181
  }
182
 
183
  .workspace {
@@ -246,8 +238,8 @@ CSS = """
246
 
247
  .section-note {
248
  color: var(--ink-soft);
249
- font-size: 0.9rem;
250
- margin: 0.2rem 0 0.4rem 0;
251
  }
252
 
253
  .action-row {
@@ -261,7 +253,7 @@ CSS = """
261
  background: linear-gradient(180deg, rgba(255,255,255,0.94), rgba(248,244,236,0.92));
262
  border: 1px solid var(--line);
263
  border-radius: 24px;
264
- padding: 1rem 1.05rem;
265
  box-shadow: var(--shadow);
266
  }
267
 
@@ -294,7 +286,7 @@ CSS = """
294
  .results-callout h3 {
295
  margin: 0 0 0.55rem 0;
296
  font-family: "Fraunces", serif;
297
- font-size: 1.25rem;
298
  }
299
 
300
  .results-callout ul {
@@ -312,22 +304,23 @@ CSS = """
312
  line-height: 1.5;
313
  }
314
 
315
- .explain-grid {
316
  display: grid;
317
  grid-template-columns: repeat(3, minmax(0, 1fr));
318
- gap: 0.9rem;
 
319
  }
320
 
321
- .explain-card {
322
  border: 1px solid var(--line);
323
- border-radius: 16px;
324
- background: rgba(255,255,255,0.84);
325
- padding: 0.95rem 1rem;
326
  }
327
 
328
- .explain-card strong {
329
  display: block;
330
- margin-bottom: 0.25rem;
331
  }
332
 
333
  .examples-note {
@@ -449,10 +442,17 @@ CSS = """
449
  .gradio-container .gradio-dataframe .wrap,
450
  .gradio-container .gradio-dataframe .table-wrap,
451
  .gradio-container .gradio-dataframe .scrollable,
 
452
  .gradio-container [data-testid="dataframe"],
453
  .gradio-container [data-testid="dataframe"] .wrap,
454
  .gradio-container [data-testid="dataframe"] .table-wrap,
455
- .gradio-container [data-testid="dataframe"] .scrollable {
 
 
 
 
 
 
456
  background: #fffdf7 !important;
457
  border-color: var(--line) !important;
458
  color: var(--ink) !important;
@@ -472,7 +472,39 @@ CSS = """
472
  .gradio-container [data-testid="accordion"],
473
  .gradio-container [data-testid="accordion"] * {
474
  color: var(--ink) !important;
475
- background-color: transparent !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  }
477
 
478
  .gradio-container .gr-box,
@@ -484,8 +516,7 @@ CSS = """
484
  @media (max-width: 980px) {
485
  .hero-grid,
486
  .helper-row,
487
- .explain-grid,
488
- .guide-grid,
489
  .metric-strip {
490
  grid-template-columns: 1fr;
491
  }
@@ -652,9 +683,9 @@ def _decorate_valid_rows(valid_rows: list[dict[str, Any]]) -> list[dict[str, Any
652
 
653
  def _build_summary(query_text: str, valid_rows: list[dict[str, Any]], invalid_rows: list[dict[str, Any]], warning: str | None) -> str:
654
  best = valid_rows[0] if valid_rows else None
655
- bullets = [f"<li><strong>Ranked candidates:</strong> {len(valid_rows)}</li>"]
656
  if invalid_rows:
657
- bullets.append(f"<li><strong>Rejected inputs:</strong> {len(invalid_rows)}</li>")
658
  if best is not None:
659
  bullets.append(
660
  f"<li><strong>Top candidate:</strong> <code>{best['canonical_smiles']}</code> · {best['priority_band']} · relative score {best['relative_score']:.1f}/100</li>"
@@ -663,10 +694,13 @@ def _build_summary(query_text: str, valid_rows: list[dict[str, Any]], invalid_ro
663
  bullets.append(f"<li><strong>Warning:</strong> {warning}</li>")
664
  return (
665
  "<div class='results-callout'>"
666
- "<h3>Ranked shortlist</h3>"
667
  f"<ul>{''.join(bullets)}</ul>"
668
- "<p>Use <strong>priority</strong> and <strong>relative score</strong> first. "
669
- "<code>model_score</code> is an internal compatibility logit and should only be compared within this submitted list.</p>"
 
 
 
670
  "</div>"
671
  )
672
 
@@ -812,16 +846,15 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
812
  <div class="eyebrow">BioAssayAlign · assay-conditioned compound ranking</div>
813
  <div class="hero-title">Rank a compound list against one assay definition</div>
814
  <div class="hero-copy">
815
- Define an assay in structured scientific terms, submit a candidate molecule set, and obtain a within-list ranking from the current BioAssayAlign compatibility model.
816
- The output is intended for shortlist prioritization, retrospective analysis, and assay-conditioned compound triage.
817
  </div>
818
  </div>
819
  <div class="hero-side">
820
  <div class="hero-side-title">Operational scope</div>
821
  <ul class="hero-list">
822
- <li>Single-assay compound ranking with explicit assay metadata</li>
823
- <li>Relative prioritization within the submitted molecule set</li>
824
- <li>Immediate rejection of malformed or non-parseable SMILES</li>
825
  </ul>
826
  </div>
827
  </div>
@@ -834,22 +867,12 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
834
  gr.HTML(
835
  f"""
836
  <div class="metric-strip">
837
- <div class="metric-card"><span>Model</span><strong><a href="https://huggingface.co/{MODEL_REPO_ID}" target="_blank" rel="noopener">Qwen3-Embedding-0.6B compatibility scorer</a></strong></div>
838
- <div class="metric-card"><span>Input contract</span><strong>1 assay definition + up to {MAX_INPUT_SMILES} candidate SMILES</strong></div>
839
- <div class="metric-card"><span>Score semantics</span><strong>Use rank and relative score within the submitted list</strong></div>
840
  </div>
841
  """
842
  )
843
-
844
- gr.HTML(
845
- """
846
- <div class="guide-grid">
847
- <div class="guide-card"><strong>1. Specify the assay</strong>Include target, readout, organism, and format when known. Missing fields are allowed but reduce context.</div>
848
- <div class="guide-card"><strong>2. Provide a candidate set</strong>Paste one SMILES per line or upload a CSV with a <code>smiles</code> column.</div>
849
- <div class="guide-card"><strong>3. Read the output scientifically</strong>Use rank and relative score for shortlist decisions. Raw model score is an internal compatibility logit.</div>
850
- </div>
851
- """
852
- )
853
 
854
  with gr.Tab("Rank Compounds"):
855
  with gr.Row(elem_classes="workspace"):
@@ -862,7 +885,7 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
862
  <div class="pane-title">Define the assay context</div>
863
  </div>
864
  </div>
865
- <div class="pane-copy">The model performs best when the assay description is concrete and metadata fields are explicit. This form is preloaded with a live example.</div>
866
  <div class="helper-row">
867
  <div class="helper-chip"><strong>Describe the protocol signal</strong>State the readout, assay system, target biology, and measurement context.</div>
868
  <div class="helper-chip"><strong>Use target identifiers</strong>UniProt IDs often improve separation between plausible and implausible candidates.</div>
@@ -871,8 +894,8 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
871
  """
872
  )
873
  example_name = gr.Dropdown(choices=list(EXAMPLES.keys()), value=DEFAULT_EXAMPLE_NAME, label="Example assay")
874
- gr.HTML("<div class='examples-note'>The form starts with a live JAK2 example. Edit any field directly, paste your own assay and SMILES, or replace everything with another example.</div>")
875
- load_example_btn = gr.Button("Replace with selected example", variant="secondary")
876
  assay_title = gr.Textbox(label="Assay title", value=DEFAULT_EXAMPLE["title"])
877
  description = gr.Textbox(label="Description", value=DEFAULT_EXAMPLE["description"], lines=6, placeholder="Describe the assay in practical lab language.")
878
  with gr.Row():
@@ -892,7 +915,7 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
892
  <div class="pane-title">Submit the candidate molecules</div>
893
  </div>
894
  </div>
895
- <div class="pane-copy">The model ranks compounds relative to this exact submitted set. Use parent or cleaned molecules when possible.</div>
896
  """
897
  )
898
  smiles_text = gr.Textbox(
@@ -908,31 +931,14 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
908
  run_btn = gr.Button("Run assay-conditioned ranking", variant="primary")
909
  clear_btn = gr.ClearButton(value="Clear inputs", components=[assay_title, description, organism, readout, assay_format, assay_type, target_uniprot, smiles_text, upload_file])
910
 
911
- gr.HTML(
912
- """
913
- <div class="summary-shell">
914
- <div class="pane-header">
915
- <div>
916
- <div class="pane-kicker">Output</div>
917
- <div class="pane-title">Interpret the ranked shortlist</div>
918
- </div>
919
- </div>
920
- <div class="explain-grid">
921
- <div class="explain-card"><strong>Priority band</strong>Use this as the first-pass triage signal when scanning the ranked list.</div>
922
- <div class="explain-card"><strong>Relative score</strong>0–100 scale computed within the submitted list only. It is not calibrated across separate runs.</div>
923
- <div class="explain-card"><strong>Model score</strong>Internal compatibility logit from the scorer head. Useful for export or debugging, not for direct biological interpretation.</div>
924
- </div>
925
- </div>
926
- """
927
- )
928
  summary = gr.HTML(elem_classes="results-shell")
929
- with gr.Accordion("Serialized assay text used by the model", open=False):
930
  assay_preview = gr.Textbox(lines=12, label="Model-facing assay text")
931
  gr.HTML("<div class='section-note'><strong>Ranked candidates</strong></div>")
932
- ranked_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True)
933
  gr.HTML("<div class='section-note'><strong>Rejected inputs</strong></div>")
934
- invalid_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True)
935
- download_file = gr.File(label="Results CSV")
936
 
937
  load_example_btn.click(
938
  load_example,
@@ -945,30 +951,6 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
945
  outputs=[summary, assay_preview, ranked_df, invalid_df, download_file],
946
  )
947
 
948
- with gr.Tab("How To Use This"):
949
- gr.HTML(
950
- """
951
- <div class="summary-shell">
952
- <div class="pane-header">
953
- <div>
954
- <div class="pane-kicker">Practical guidance</div>
955
- <div class="pane-title">Operating guidance</div>
956
- </div>
957
- </div>
958
- <div class="explain-grid">
959
- <div class="explain-card"><strong>Encode the assay explicitly</strong>Use full scientific language rather than keywords alone. Include target and readout when available.</div>
960
- <div class="explain-card"><strong>Provide chemically reasonable candidates</strong>Prefer parent or neutralized structures. Upload a CSV with a <code>smiles</code> column for larger lists.</div>
961
- <div class="explain-card"><strong>Use the output as a ranking signal</strong>Priority and relative score are the intended decision aids. Model score is not a calibrated success probability.</div>
962
- </div>
963
- <div class="footer-note" style="margin-top: 1rem;">
964
- This Space is intended for assay-conditioned shortlist ranking. It is not a generative chemistry system and it does not replace confirmatory screening.
965
- Export the ranked CSV if you want to continue downstream analysis in your own workflow.
966
- </div>
967
- </div>
968
- """
969
- )
970
-
971
-
972
  if __name__ == "__main__":
973
  threading.Thread(target=_warm_model_background, daemon=True).start()
974
  demo.queue(default_concurrency_limit=4).launch(
 
126
 
127
  .metric-strip {
128
  display: grid;
129
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
130
  gap: 0.8rem;
131
  }
132
 
133
  .metric-card {
134
  border: 1px solid var(--line);
135
  background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(248,244,236,0.9));
136
+ padding: 0.72rem 0.85rem;
137
  border-radius: 18px;
138
+ min-height: 4.9rem;
139
  box-shadow: 0 8px 24px rgba(23,37,45,0.04);
140
  }
141
 
 
146
 
147
  .metric-card strong {
148
  display: block;
149
+ font-size: 1rem;
150
  margin-top: 0.15rem;
151
  color: var(--ink);
152
  }
 
161
  text-decoration: underline;
162
  }
163
 
164
+ .compact-spec {
165
+ margin: 0.7rem 0 1rem 0;
 
 
 
 
 
166
  border: 1px solid var(--line);
 
 
167
  border-radius: 18px;
168
+ background: rgba(255,255,255,0.82);
169
+ padding: 0.78rem 0.9rem;
170
+ color: var(--ink-soft);
171
+ font-size: 0.92rem;
172
+ line-height: 1.45;
 
173
  }
174
 
175
  .workspace {
 
238
 
239
  .section-note {
240
  color: var(--ink-soft);
241
+ font-size: 0.88rem;
242
+ margin: 0.1rem 0 0.32rem 0;
243
  }
244
 
245
  .action-row {
 
253
  background: linear-gradient(180deg, rgba(255,255,255,0.94), rgba(248,244,236,0.92));
254
  border: 1px solid var(--line);
255
  border-radius: 24px;
256
+ padding: 0.9rem 1rem;
257
  box-shadow: var(--shadow);
258
  }
259
 
 
286
  .results-callout h3 {
287
  margin: 0 0 0.55rem 0;
288
  font-family: "Fraunces", serif;
289
+ font-size: 1.1rem;
290
  }
291
 
292
  .results-callout ul {
 
304
  line-height: 1.5;
305
  }
306
 
307
+ .results-metrics {
308
  display: grid;
309
  grid-template-columns: repeat(3, minmax(0, 1fr));
310
+ gap: 0.65rem;
311
+ margin-top: 0.55rem;
312
  }
313
 
314
+ .results-metric {
315
  border: 1px solid var(--line);
316
+ border-radius: 14px;
317
+ background: rgba(255,255,255,0.9);
318
+ padding: 0.65rem 0.75rem;
319
  }
320
 
321
+ .results-metric strong {
322
  display: block;
323
+ margin-bottom: 0.16rem;
324
  }
325
 
326
  .examples-note {
 
442
  .gradio-container .gradio-dataframe .wrap,
443
  .gradio-container .gradio-dataframe .table-wrap,
444
  .gradio-container .gradio-dataframe .scrollable,
445
+ .gradio-container .gradio-dataframe .table-container,
446
  .gradio-container [data-testid="dataframe"],
447
  .gradio-container [data-testid="dataframe"] .wrap,
448
  .gradio-container [data-testid="dataframe"] .table-wrap,
449
+ .gradio-container [data-testid="dataframe"] .scrollable,
450
+ .result-frame,
451
+ .result-frame *,
452
+ .result-frame .wrap,
453
+ .result-frame .table-wrap,
454
+ .result-frame .table-container,
455
+ .result-frame .scrollable {
456
  background: #fffdf7 !important;
457
  border-color: var(--line) !important;
458
  color: var(--ink) !important;
 
472
  .gradio-container [data-testid="accordion"],
473
  .gradio-container [data-testid="accordion"] * {
474
  color: var(--ink) !important;
475
+ }
476
+
477
+ .result-accordion,
478
+ .result-accordion *,
479
+ .result-accordion [data-testid="accordion"],
480
+ .result-accordion [data-testid="accordion"] * {
481
+ color: var(--ink) !important;
482
+ background: #fffdf7 !important;
483
+ }
484
+
485
+ .result-file,
486
+ .result-file *,
487
+ .result-file .wrap,
488
+ .result-file .file-preview,
489
+ .result-file .file-preview-holder,
490
+ .result-file [data-testid="file"] {
491
+ background: #fffdf7 !important;
492
+ color: var(--ink) !important;
493
+ border-color: var(--line) !important;
494
+ }
495
+
496
+ .result-file button {
497
+ background: linear-gradient(180deg, #f6e7da, #f0ddcd) !important;
498
+ color: var(--ink) !important;
499
+ border: 1px solid #dbc4b4 !important;
500
+ }
501
+
502
+ .gradio-container .choices__list--dropdown,
503
+ .gradio-container .choices__list[aria-expanded],
504
+ .gradio-container .choices__list--single,
505
+ .gradio-container .choices__list--multiple {
506
+ background: #fffdf8 !important;
507
+ color: var(--ink) !important;
508
  }
509
 
510
  .gradio-container .gr-box,
 
516
  @media (max-width: 980px) {
517
  .hero-grid,
518
  .helper-row,
519
+ .results-metrics,
 
520
  .metric-strip {
521
  grid-template-columns: 1fr;
522
  }
 
683
 
684
  def _build_summary(query_text: str, valid_rows: list[dict[str, Any]], invalid_rows: list[dict[str, Any]], warning: str | None) -> str:
685
  best = valid_rows[0] if valid_rows else None
686
+ bullets = [f"<li><strong>Ranked:</strong> {len(valid_rows)}</li>"]
687
  if invalid_rows:
688
+ bullets.append(f"<li><strong>Rejected:</strong> {len(invalid_rows)}</li>")
689
  if best is not None:
690
  bullets.append(
691
  f"<li><strong>Top candidate:</strong> <code>{best['canonical_smiles']}</code> · {best['priority_band']} · relative score {best['relative_score']:.1f}/100</li>"
 
694
  bullets.append(f"<li><strong>Warning:</strong> {warning}</li>")
695
  return (
696
  "<div class='results-callout'>"
697
+ "<h3>Ranking summary</h3>"
698
  f"<ul>{''.join(bullets)}</ul>"
699
+ "<div class='results-metrics'>"
700
+ "<div class='results-metric'><strong>Priority</strong>Shortlist cue derived from the within-list ranking.</div>"
701
+ "<div class='results-metric'><strong>Relative score</strong>0–100 rescaling inside this submitted list only.</div>"
702
+ "<div class='results-metric'><strong>Model score</strong>Internal compatibility logit. Do not compare across unrelated runs.</div>"
703
+ "</div>"
704
  "</div>"
705
  )
706
 
 
846
  <div class="eyebrow">BioAssayAlign · assay-conditioned compound ranking</div>
847
  <div class="hero-title">Rank a compound list against one assay definition</div>
848
  <div class="hero-copy">
849
+ Define one assay, submit a candidate molecule list, and obtain an assay-conditioned ranking for that specific list.
 
850
  </div>
851
  </div>
852
  <div class="hero-side">
853
  <div class="hero-side-title">Operational scope</div>
854
  <ul class="hero-list">
855
+ <li>One assay at a time</li>
856
+ <li>Relative ranking within the submitted candidate set</li>
857
+ <li>Immediate rejection of malformed SMILES</li>
858
  </ul>
859
  </div>
860
  </div>
 
867
  gr.HTML(
868
  f"""
869
  <div class="metric-strip">
870
+ <div class="metric-card"><span>Model</span><strong><a href="https://huggingface.co/{MODEL_REPO_ID}" target="_blank" rel="noopener">{MODEL_REPO_ID}</a></strong></div>
871
+ <div class="metric-card"><span>Use</span><strong>One assay definition and up to {MAX_INPUT_SMILES} candidate SMILES. Interpret rank and relative score within the submitted list.</strong></div>
 
872
  </div>
873
  """
874
  )
875
+ gr.HTML("<div class='compact-spec'>You can write your own assay directly in the form below or load one of the live examples. Include target, readout, organism, and format when known. Paste one SMILES per line or upload a CSV with a <code>smiles</code> column.</div>")
 
 
 
 
 
 
 
 
 
876
 
877
  with gr.Tab("Rank Compounds"):
878
  with gr.Row(elem_classes="workspace"):
 
885
  <div class="pane-title">Define the assay context</div>
886
  </div>
887
  </div>
888
+ <div class="pane-copy">Edit the assay fields directly, or load one of the live examples and modify it.</div>
889
  <div class="helper-row">
890
  <div class="helper-chip"><strong>Describe the protocol signal</strong>State the readout, assay system, target biology, and measurement context.</div>
891
  <div class="helper-chip"><strong>Use target identifiers</strong>UniProt IDs often improve separation between plausible and implausible candidates.</div>
 
894
  """
895
  )
896
  example_name = gr.Dropdown(choices=list(EXAMPLES.keys()), value=DEFAULT_EXAMPLE_NAME, label="Example assay")
897
+ gr.HTML("<div class='examples-note'>The form starts with a live JAK2 example. You can overwrite every field with your own assay or load another example.</div>")
898
+ load_example_btn = gr.Button("Load selected example or overwrite below with your own assay", variant="secondary")
899
  assay_title = gr.Textbox(label="Assay title", value=DEFAULT_EXAMPLE["title"])
900
  description = gr.Textbox(label="Description", value=DEFAULT_EXAMPLE["description"], lines=6, placeholder="Describe the assay in practical lab language.")
901
  with gr.Row():
 
915
  <div class="pane-title">Submit the candidate molecules</div>
916
  </div>
917
  </div>
918
+ <div class="pane-copy">The ranking is computed only within this submitted candidate set.</div>
919
  """
920
  )
921
  smiles_text = gr.Textbox(
 
931
  run_btn = gr.Button("Run assay-conditioned ranking", variant="primary")
932
  clear_btn = gr.ClearButton(value="Clear inputs", components=[assay_title, description, organism, readout, assay_format, assay_type, target_uniprot, smiles_text, upload_file])
933
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
934
  summary = gr.HTML(elem_classes="results-shell")
935
+ with gr.Accordion("Serialized assay text used by the model", open=False, elem_classes="result-accordion"):
936
  assay_preview = gr.Textbox(lines=12, label="Model-facing assay text")
937
  gr.HTML("<div class='section-note'><strong>Ranked candidates</strong></div>")
938
+ ranked_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True, elem_classes="result-frame")
939
  gr.HTML("<div class='section-note'><strong>Rejected inputs</strong></div>")
940
+ invalid_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True, elem_classes="result-frame")
941
+ download_file = gr.File(label="Export CSV", elem_classes="result-file")
942
 
943
  load_example_btn.click(
944
  load_example,
 
951
  outputs=[summary, assay_preview, ranked_df, invalid_df, download_file],
952
  )
953
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
954
  if __name__ == "__main__":
955
  threading.Thread(target=_warm_model_background, daemon=True).start()
956
  demo.queue(default_concurrency_limit=4).launch(