josefchen commited on
Commit
3015aab
·
verified ·
1 Parent(s): fbdf299

Rebuild passport as native HTML cards (light theme, no more alien dark-teal block)

Browse files
Files changed (2) hide show
  1. __pycache__/app.cpython-310.pyc +0 -0
  2. app.py +158 -6
__pycache__/app.cpython-310.pyc CHANGED
Binary files a/__pycache__/app.cpython-310.pyc and b/__pycache__/app.cpython-310.pyc differ
 
app.py CHANGED
@@ -647,7 +647,152 @@ def _sensory_profile(name):
647
  out[axis] = float(np.mean(vals)) if vals else 0.0
648
  return out
649
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
  def render_passport(name):
 
 
 
 
 
 
 
 
651
  fig = plt.figure(figsize=(12, 10.5), facecolor=KAIKAKU_DARK)
652
  fig.patch.set_facecolor(KAIKAKU_DARK)
653
  gs = gridspec.GridSpec(4, 6, figure=fig,
@@ -1815,15 +1960,22 @@ Three sibling ingredient embeddings from [arXiv:2605.22391](https://arxiv.org/ab
1815
  with gr.Tab("Inspect"):
1816
  with gr.Tabs():
1817
  with gr.Tab("Ingredient passport"):
1818
- gr.Markdown("Single-page dossier for one ingredient. Tweetable PNG.")
1819
  with gr.Row():
1820
  pp_pick = gr.Dropdown(choices=ALL_INGREDIENTS, label="Ingredient", value="basil")
1821
  pp_btn = gr.Button("Generate passport", variant="primary")
1822
- pp_plot = gr.Plot(label="")
1823
- pp_btn.click(render_passport, inputs=[pp_pick], outputs=[pp_plot], show_progress="full")
1824
- pp_pick.change(render_passport, inputs=[pp_pick], outputs=[pp_plot], show_progress="minimal")
1825
- # render initial value
1826
- demo.load(render_passport, inputs=[pp_pick], outputs=[pp_plot])
 
 
 
 
 
 
 
1827
 
1828
  with gr.Tab("Mode wiki"):
1829
  gr.Markdown("Click into any of the ~500 modes for a per-mode wiki page.")
 
647
  out[axis] = float(np.mean(vals)) if vals else 0.0
648
  return out
649
 
650
+ def render_passport_html(name):
651
+ """Native HTML/CSS passport that lives inside the white Gradio page.
652
+ Returns (html_str, sensory_radar_png_figure)."""
653
+ if not name:
654
+ return "<p style='color:#888'>Pick an ingredient.</p>", None
655
+ pretty = name.replace("_", " ").title()
656
+ group = _NAME_TO_GROUP.get(name, "Other")
657
+ sens = _sensory_profile(name)
658
+ # Build sensory radar as a small standalone figure
659
+ fig, ax = plt.subplots(figsize=(4.5, 4.5), subplot_kw={"projection": "polar"})
660
+ fig.patch.set_facecolor("#ffffff")
661
+ ax.set_facecolor("#ffffff")
662
+ theta = np.linspace(0, 2*np.pi, len(PASSPORT_SENS), endpoint=False)
663
+ r = np.array([max(0.0, sens[a]) for a in PASSPORT_SENS])
664
+ theta_c = np.concatenate([theta, theta[:1]])
665
+ r_c = np.concatenate([r, r[:1]])
666
+ ax.plot(theta_c, r_c, color=KAIKAKU_ACCENT, lw=2.2)
667
+ ax.fill(theta_c, r_c, color=KAIKAKU_ACCENT, alpha=0.22)
668
+ ax.set_xticks(theta)
669
+ ax.set_xticklabels([a.title() for a in PASSPORT_SENS], color="#0f172a", fontsize=11, fontweight="bold")
670
+ ax.set_yticklabels([])
671
+ ax.set_ylim(0, max(0.5, float(r.max()) + 0.08))
672
+ ax.grid(color="#cbd5e1", alpha=0.7, lw=0.5)
673
+ ax.spines["polar"].set_color("#94a3b8")
674
+ plt.tight_layout()
675
+ radar_fig = fig
676
+
677
+ # Compute neighbours per sibling
678
+ nb_blocks = []
679
+ for sib in PASSPORT_SIBS:
680
+ m = MODELS[sib]
681
+ if name not in m.vocab:
682
+ rows_html = "<div style='color:#94a3b8;font-style:italic;padding:8px'>(not in vocab)</div>"
683
+ else:
684
+ q = _unit(m.E[m.vocab[name]])
685
+ items = []
686
+ for nb, sim in _topk(m, q, 5, exclude=[name]):
687
+ items.append(
688
+ f"<div style='display:flex;justify-content:space-between;padding:5px 0;"
689
+ f"border-bottom:1px solid #e2e8f0'>"
690
+ f"<span style='color:#0f172a;font-weight:500'>{nb.replace('_', ' ')}</span>"
691
+ f"<span style='color:{KAIKAKU_ACCENT};font-family:monospace;font-weight:600'>{sim:.3f}</span>"
692
+ f"</div>"
693
+ )
694
+ rows_html = "".join(items)
695
+ nb_blocks.append(
696
+ f"<div style='flex:1;min-width:0;background:#f8fafc;border:1px solid #e2e8f0;"
697
+ f"border-radius:10px;padding:14px 16px'>"
698
+ f"<div style='color:{KAIKAKU_ACCENT};font-family:monospace;font-size:0.78em;"
699
+ f"font-weight:700;margin-bottom:10px;letter-spacing:0.04em'>{sib.upper()} · NEAREST NEIGHBOURS</div>"
700
+ f"{rows_html}</div>"
701
+ )
702
+
703
+ # Cuisine affiliation bars
704
+ m_chem = MODELS["chem"]
705
+ cuisine_html = ""
706
+ if name in m_chem.vocab:
707
+ v = _unit(m_chem.E[m_chem.vocab[name]])
708
+ vals = []
709
+ for cu in _CUISINES:
710
+ key = f"cuisine:{cu}"
711
+ if key in m_chem.supervised_poles:
712
+ vals.append((cu.replace("_", " "), float(v @ _unit(m_chem.supervised_poles[key]))))
713
+ vmax = max((abs(v_) for _, v_ in vals), default=1.0) or 1.0
714
+ cuisine_rows = []
715
+ for label, val in vals:
716
+ pct = abs(val) / vmax * 100
717
+ bar_color = KAIKAKU_ACCENT if val >= 0 else "#f59e0b"
718
+ cuisine_rows.append(
719
+ f"<div style='display:grid;grid-template-columns:140px 1fr 60px;gap:10px;"
720
+ f"align-items:center;padding:5px 0'>"
721
+ f"<span style='color:#0f172a;font-weight:500;font-size:0.92em'>{label}</span>"
722
+ f"<div style='background:#e2e8f0;border-radius:6px;height:14px;position:relative;overflow:hidden'>"
723
+ f"<div style='background:{bar_color};height:100%;width:{pct:.1f}%;border-radius:6px'></div>"
724
+ f"</div>"
725
+ f"<span style='color:{KAIKAKU_ACCENT};font-family:monospace;text-align:right;"
726
+ f"font-weight:600;font-size:0.88em'>{val:+.3f}</span>"
727
+ f"</div>"
728
+ )
729
+ cuisine_html = "".join(cuisine_rows)
730
+ else:
731
+ cuisine_html = "<div style='color:#94a3b8;font-style:italic'>(not in chem vocab)</div>"
732
+
733
+ # Closest factor modes
734
+ scored = []
735
+ for sib in PASSPORT_SIBS:
736
+ m = MODELS[sib]
737
+ if name not in m.vocab: continue
738
+ v = _unit(m.E[m.vocab[name]])
739
+ for md in m.modes:
740
+ if md.kind != "factor": continue
741
+ scored.append((float(_unit(md.pole) @ v), sib, md))
742
+ scored.sort(key=lambda x: -x[0])
743
+ mode_rows = []
744
+ for sim, sib, md in scored[:3]:
745
+ members = ", ".join(n.replace("_", " ") for n in md.members[:6])
746
+ mode_rows.append(
747
+ f"<div style='background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;"
748
+ f"padding:14px 16px;margin-bottom:10px'>"
749
+ f"<div style='display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px'>"
750
+ f"<div><span style='color:{KAIKAKU_ACCENT};font-family:monospace;font-size:0.78em;"
751
+ f"font-weight:700;margin-right:10px;letter-spacing:0.04em'>{sib.upper()}</span>"
752
+ f"<span style='color:#0f172a;font-weight:600;font-size:1.05em'>{md.label}</span></div>"
753
+ f"<span style='color:{KAIKAKU_ACCENT};font-family:monospace;font-weight:600'>cos {sim:.3f}</span>"
754
+ f"</div>"
755
+ f"<div style='color:#475569;font-size:0.88em;line-height:1.4'>{members}</div>"
756
+ f"</div>"
757
+ )
758
+
759
+ html = f"""
760
+ <div style='max-width:1180px;margin:0 auto'>
761
+ <div style='border-bottom:3px solid {KAIKAKU_ACCENT};padding-bottom:8px;margin-bottom:18px'>
762
+ <div style='font-size:2.2em;font-weight:800;color:#0f172a;letter-spacing:-0.02em;line-height:1.0'>{pretty}</div>
763
+ <div style='color:{KAIKAKU_ACCENT};font-family:monospace;font-size:0.82em;font-weight:600;
764
+ margin-top:6px;letter-spacing:0.04em'>
765
+ INGREDIENT PASSPORT · FOOD GROUP: {group.upper()}
766
+ </div>
767
+ </div>
768
+ <div style='display:flex;gap:14px;margin-bottom:18px;flex-wrap:wrap'>{''.join(nb_blocks)}</div>
769
+ <div style='background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;
770
+ padding:14px 18px;margin-bottom:18px'>
771
+ <div style='color:{KAIKAKU_ACCENT};font-family:monospace;font-size:0.78em;
772
+ font-weight:700;margin-bottom:10px;letter-spacing:0.04em'>CUISINE AFFILIATION (chem)</div>
773
+ {cuisine_html}
774
+ </div>
775
+ <div>
776
+ <div style='color:{KAIKAKU_ACCENT};font-family:monospace;font-size:0.78em;
777
+ font-weight:700;margin-bottom:10px;letter-spacing:0.04em'>
778
+ CLOSEST EMERGENT FACTOR MODES (top 3 across siblings)
779
+ </div>
780
+ {''.join(mode_rows) if mode_rows else "<div style='color:#94a3b8'>(no modes)</div>"}
781
+ </div>
782
+ </div>
783
+ """
784
+ return html, radar_fig
785
+
786
+
787
  def render_passport(name):
788
+ """Legacy entry point - delegate to the HTML version and discard the radar."""
789
+ html, _radar = render_passport_html(name)
790
+ # Return a placeholder matplotlib fig (small) since callers may expect one
791
+ fig, ax = plt.subplots(figsize=(0.1, 0.1))
792
+ ax.axis("off")
793
+ return fig
794
+
795
+ def _render_passport_dummy(name):
796
  fig = plt.figure(figsize=(12, 10.5), facecolor=KAIKAKU_DARK)
797
  fig.patch.set_facecolor(KAIKAKU_DARK)
798
  gs = gridspec.GridSpec(4, 6, figure=fig,
 
1960
  with gr.Tab("Inspect"):
1961
  with gr.Tabs():
1962
  with gr.Tab("Ingredient passport"):
1963
+ gr.Markdown("Single-page dossier for one ingredient.")
1964
  with gr.Row():
1965
  pp_pick = gr.Dropdown(choices=ALL_INGREDIENTS, label="Ingredient", value="basil")
1966
  pp_btn = gr.Button("Generate passport", variant="primary")
1967
+ with gr.Row():
1968
+ with gr.Column(scale=3):
1969
+ pp_html = gr.HTML(value=render_passport_html("basil")[0])
1970
+ with gr.Column(scale=1):
1971
+ gr.Markdown(f"<div style='color:{KAIKAKU_ACCENT};font-family:monospace;"
1972
+ f"font-size:0.78em;font-weight:700;letter-spacing:0.04em'>SENSORY RADAR</div>")
1973
+ pp_radar = gr.Plot(value=render_passport_html("basil")[1], label="")
1974
+ def _passport_outputs(name):
1975
+ h, r = render_passport_html(name)
1976
+ return h, r
1977
+ pp_btn.click(_passport_outputs, inputs=[pp_pick], outputs=[pp_html, pp_radar], show_progress="minimal")
1978
+ pp_pick.change(_passport_outputs, inputs=[pp_pick], outputs=[pp_html, pp_radar], show_progress="minimal")
1979
 
1980
  with gr.Tab("Mode wiki"):
1981
  gr.Markdown("Click into any of the ~500 modes for a per-mode wiki page.")