lostfound-hack / app.py
hackerloi45's picture
fixed+improved UI
7a8dd54
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)