""" clip-embed-space — CLIP ViT-B/32 embedding service. Exposes three API endpoints: /embed_image : base64-encoded image → 512-dim float list /embed_text : text string → 512-dim float list /embed_image_url : image URL → 512-dim float list Runs on CPU (ViT-B/32 is small enough — no GPU needed). Vectors are L2-normalised, matching the local embed_products.py pipeline. """ import base64 import io import gradio as gr import httpx import numpy as np from PIL import Image from sentence_transformers import SentenceTransformer MODEL_NAME = "clip-ViT-B-32" print(f"Loading {MODEL_NAME}...") model = SentenceTransformer(MODEL_NAME) print("Model ready.") def embed_image(image_base64: str) -> list[float]: image = Image.open(io.BytesIO(base64.b64decode(image_base64))).convert("RGB") return model.encode(image, normalize_embeddings=True).tolist() def embed_text(text: str) -> list[float]: return model.encode(text, normalize_embeddings=True).tolist() def embed_image_url(image_url: str) -> list[float]: resp = httpx.get(image_url, timeout=15, follow_redirects=True) resp.raise_for_status() image = Image.open(io.BytesIO(resp.content)).convert("RGB") return model.encode(image, normalize_embeddings=True).tolist() with gr.Blocks(title="CLIP Embed") as demo: gr.Markdown("## CLIP ViT-B/32 Embedding Service — 512-dim L2-normalised vectors") with gr.Row(): with gr.Column(): b64_in = gr.Textbox(label="image_base64", lines=3) b64_btn = gr.Button("Embed image (base64)") b64_out = gr.JSON(label="embedding [512]") b64_btn.click(embed_image, b64_in, b64_out, api_name="embed_image") with gr.Column(): text_in = gr.Textbox(label="text") text_btn = gr.Button("Embed text") text_out = gr.JSON(label="embedding [512]") text_btn.click(embed_text, text_in, text_out, api_name="embed_text") with gr.Column(): url_in = gr.Textbox(label="image_url") url_btn = gr.Button("Embed image (URL)") url_out = gr.JSON(label="embedding [512]") url_btn.click(embed_image_url, url_in, url_out, api_name="embed_image_url") demo.launch()