import gradio as gr import uuid import base64 import io from qdrant_client import QdrantClient from qdrant_client.models import PointStruct, VectorParams, Distance from sentence_transformers import SentenceTransformer from PIL import Image # -------------------------- # Qdrant Cloud Connection # -------------------------- QDRANT_URL = "https://ff4da494-27b1-413c-ba58-d5ea14932fe1.europe-west3-0.gcp.cloud.qdrant.io:6333" QDRANT_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.jjeB1JgnUSlb1hOOKMdRpVvMrUER57-udT-X1AWXT1E" COLLECTION_NAME = "lost_and_found" # CLIP model (text + image embeddings) MODEL_NAME = "sentence-transformers/clip-ViT-B-32" embedder = SentenceTransformer(MODEL_NAME) # CLIP ViT-B/32 always gives 512-dimensional embeddings VECTOR_SIZE = 512 # Qdrant Client (Cloud) qclient = QdrantClient( url=QDRANT_URL, api_key=QDRANT_API_KEY ) # Ensure collection exists qclient.recreate_collection( collection_name=COLLECTION_NAME, vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE), ) # -------------------------- # Helper Functions # -------------------------- def image_to_base64(img: Image.Image) -> str: buf = io.BytesIO() img.save(buf, format="PNG") return base64.b64encode(buf.getvalue()).decode("utf-8") def base64_to_image(b64_str: str) -> Image.Image: img_bytes = base64.b64decode(b64_str) return Image.open(io.BytesIO(img_bytes)) def embed_text(text: str): return embedder.encode(text).tolist() def embed_image(img: Image.Image): return embedder.encode(img).tolist() def add_item(image, description, finder_name, finder_phone): if image is None or description.strip() == "": return "Please provide both an image and a description." embedding = embed_image(image) img_b64 = image_to_base64(image) metadata = { "description": description, "finder_name": finder_name if finder_name.strip() else "NA", "finder_phone": finder_phone if finder_phone.strip() else "NA", "image_b64": img_b64 } qclient.upsert( collection_name=COLLECTION_NAME, points=[ PointStruct( id=str(uuid.uuid4()), vector=embedding, payload=metadata ) ] ) return "Item successfully added!" def search_items(query_text, query_image): if not query_text and query_image is None: return "Please enter text or upload an image to search.", [] if query_image: query_vector = embed_image(query_image) else: query_vector = embed_text(query_text) results = qclient.search( collection_name=COLLECTION_NAME, query_vector=query_vector, limit=5 ) if not results: return "No matches found.", [] gallery = [] output_text = "### Matches Found\n\n" for r in results: desc = r.payload.get("description", "No description") name = r.payload.get("finder_name", "NA") phone = r.payload.get("finder_phone", "NA") output_text += f"- **{desc}** — Finder: {name}, Phone: {phone}\n" if "image_b64" in r.payload: try: img = base64_to_image(r.payload["image_b64"]) gallery.append(img) except Exception: pass return output_text, gallery def clear_database(): qclient.delete_collection(COLLECTION_NAME) qclient.recreate_collection( collection_name=COLLECTION_NAME, vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE), ) return "Database cleared successfully." # -------------------------- # Gradio UI # -------------------------- with gr.Blocks() as demo: gr.Markdown( """ # Lost & Found System A simple platform to add and search lost items. """ ) with gr.Tab("Add Item"): gr.Markdown("### Add a Found Item") with gr.Row(): image_in = gr.Image(type="pil", label="Upload Image") desc_in = gr.Textbox(label="Item Description") with gr.Row(): finder_name = gr.Textbox(label="Finder's Name (optional)") finder_phone = gr.Textbox(label="Finder's Phone Number (optional)") add_btn = gr.Button("Submit Item", variant="primary") add_output = gr.Textbox(label="Status", interactive=False) with gr.Tab("Search"): gr.Markdown("### Search Lost Items") with gr.Row(): search_text = gr.Textbox(label="Search by Text") search_image = gr.Image(type="pil", label="Or Upload Image") search_btn = gr.Button("Search", variant="primary") search_output = gr.Markdown() gallery = gr.Gallery(label="Matched Items", show_label=True, elem_id="gallery") with gr.Tab("Admin"): gr.Markdown("### Admin Controls") clear_btn = gr.Button("Clear Database", variant="stop") clear_output = gr.Textbox(label="Status", interactive=False) # Button actions add_btn.click(add_item, inputs=[image_in, desc_in, finder_name, finder_phone], outputs=add_output) search_btn.click(search_items, inputs=[search_text, search_image], outputs=[search_output, gallery]) clear_btn.click(clear_database, outputs=clear_output) # -------------------------- # Launch App # -------------------------- if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)