import pandas as pd from sentence_transformers import SentenceTransformer, util import gradio as gr # ─── Load catalog & compute embeddings ───────────────────────── PRODUCTS_CSV = "products.csv" df = pd.read_csv(PRODUCTS_CSV) descriptions = df["description"].tolist() model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") embeddings = model.encode(descriptions, convert_to_tensor=True, normalize_embeddings=True) # ─── Semantic-search function ───────────────────────────────── def search_products(query: str, top_k: int = 5): if not query.strip(): return pd.DataFrame(columns=df.columns.tolist() + ["score"]) q_emb = model.encode(query, convert_to_tensor=True, normalize_embeddings=True) hits = util.cos_sim(q_emb, embeddings)[0].topk(k=top_k) indices = hits.indices.cpu().tolist() scores = [round(float(s), 3) for s in hits.values.cpu().tolist()] results = df.iloc[indices].copy() results["score"] = scores return results.reset_index(drop=True) # ─── Gradio UI ──────────────────────────────────────────────── with gr.Blocks(title="🛍️ Salon Catalog Semantic Search") as demo: gr.Markdown(""" # 🛍️ Salon Product Search **Natural-language queries** → **top matching products** via MiniLM embeddings (Runs entirely on free CPU; no paid APIs) """) with gr.Row(): query_in = gr.Textbox(placeholder="e.g. sulfate-free shampoo under $15", label="Search query") topk = gr.Slider(1, 10, value=5, step=1, label="Number of results") search_btn = gr.Button("Search 🔍", variant="primary") table = gr.Dataframe( headers=list(df.columns) + ["score"], datatype=["number","str","str","str","number","number"], interactive=False, row_count= topk.value, label="Results" ) search_btn.click(search_products, [query_in, topk], table) if __name__ == "__main__": demo.launch(server_name="0.0.0.0")