""" SMS Spam Clusters - Hugging Face Space. Single map of 5,159 SMS messages, arranged by what they mean. The same embedding model that produced the cached vectors runs on user input, a weighted vote of the five nearest cached messages places the new point on the same map and reads off its cluster. Companion to the classifier (jngb-labs/sms-spam-classifier) and the dataset (jngb-labs/sms-spam). """ from __future__ import annotations import json import time from pathlib import Path import gradio as gr import numpy as np import pandas as pd import plotly.graph_objects as go import plotly.io as pio # Plotly chart config. Drives the modebar (zoom, pan, reset axes, # download-as-PNG) and enables scroll-wheel zoom. Gradio's gr.Plot # strips these, which is why we render the chart as HTML via gr.HTML # instead — that path passes config through. PLOT_CONFIG = { "displayModeBar": True, "displaylogo": False, "scrollZoom": True, "modeBarButtonsToRemove": [ # Selection tools — useless here "lasso2d", "select2d", # Mode toggles the user doesn't need: pan is the default drag # mode and zoom2d (drag-to-zoom-box) is redundant with scroll # zoom + the + / - buttons. "pan2d", "zoom2d", # Spike lines, compare tooltips, autoscale — clutter "autoScale2d", "toggleSpikelines", "hoverClosestCartesian", "hoverCompareCartesian", # Screenshot — nobody asked for it "toImage", ], "responsive": True, } PLOT_HEIGHT_PX = 780 def fig_to_html(fig: go.Figure) -> str: """Render a plotly figure inside an iframe so the Plotly.newPlot script actually executes. Why an iframe: Gradio's gr.HTML inserts markup via innerHTML, and browsers do not execute