Spaces:
Runtime error
Runtime error
Upload 3 files
Browse files- README.md +6 -9
- app.py +77 -51
- requirements.txt +3 -4
README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
| 2 |
title: Wonder Finder
|
| 3 |
emoji: π
|
| 4 |
colorFrom: yellow
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 4.44.
|
| 8 |
-
python_version: "3.
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
|
@@ -22,18 +22,15 @@ Visual recommender for the 12 Wonders of the World, powered by CLIP embeddings.
|
|
| 22 |
## How it works
|
| 23 |
1. The catalog (11,544 images across 12 wonder classes) is pre-embedded using CLIP ViT-B/32.
|
| 24 |
2. User input (image or text) is embedded into the same 512-D space.
|
| 25 |
-
3. Cosine similarity ranks the catalog;
|
| 26 |
|
| 27 |
## Dataset
|
| 28 |
[chavajaz/wonders_dataset](https://huggingface.co/datasets/chavajaz/wonders_dataset) β CC0-1.0 licensed, ~960 images per class on average.
|
| 29 |
|
| 30 |
## Model
|
| 31 |
-
[openai/clip-vit-base-patch32](https://huggingface.co/openai/clip-vit-base-patch32) β chosen for its joint image
|
| 32 |
|
| 33 |
## Cluster analysis
|
| 34 |
K-Means at k=12 on the embeddings achieved **ARI = 0.890** and **NMI = 0.927** against ground-truth wonder labels, indicating CLIP's pretrained space already separates the 12 wonders almost perfectly without supervision.
|
| 35 |
|
| 36 |
-
|
| 37 |
-
- `app.py` β the Gradio application
|
| 38 |
-
- `requirements.txt` β pinned dependencies
|
| 39 |
-
- `wonders_embeddings.parquet` β precomputed CLIP embeddings (one row per catalog image, column `embedding`, aligned 1:1 with the dataset in `train β validation β test` order)
|
|
|
|
| 2 |
title: Wonder Finder
|
| 3 |
emoji: π
|
| 4 |
colorFrom: yellow
|
| 5 |
+
colorTo: red
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.1
|
| 8 |
+
python_version: "3.10"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
|
|
|
| 22 |
## How it works
|
| 23 |
1. The catalog (11,544 images across 12 wonder classes) is pre-embedded using CLIP ViT-B/32.
|
| 24 |
2. User input (image or text) is embedded into the same 512-D space.
|
| 25 |
+
3. Cosine similarity ranks the catalog; top 3 results are returned with a diversity filter to avoid duplicates.
|
| 26 |
|
| 27 |
## Dataset
|
| 28 |
[chavajaz/wonders_dataset](https://huggingface.co/datasets/chavajaz/wonders_dataset) β CC0-1.0 licensed, ~960 images per class on average.
|
| 29 |
|
| 30 |
## Model
|
| 31 |
+
[openai/clip-vit-base-patch32](https://huggingface.co/openai/clip-vit-base-patch32) β chosen for its joint imageβtext embedding space, which enables both image and text input through a single model.
|
| 32 |
|
| 33 |
## Cluster analysis
|
| 34 |
K-Means at k=12 on the embeddings achieved **ARI = 0.890** and **NMI = 0.927** against ground-truth wonder labels, indicating CLIP's pretrained space already separates the 12 wonders almost perfectly without supervision.
|
| 35 |
|
| 36 |
+
Built as the final project for Assignment 3.
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -1,12 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import torch
|
| 3 |
import numpy as np
|
| 4 |
import pandas as pd
|
|
|
|
| 5 |
from transformers import CLIPProcessor, CLIPModel
|
| 6 |
from datasets import load_dataset, concatenate_datasets
|
| 7 |
|
| 8 |
# ============================================================
|
| 9 |
-
# LOAD EVERYTHING ON STARTUP
|
| 10 |
# ============================================================
|
| 11 |
|
| 12 |
print("Loading CLIP model...")
|
|
@@ -18,28 +83,13 @@ processor = CLIPProcessor.from_pretrained(MODEL_NAME)
|
|
| 18 |
|
| 19 |
print("Loading dataset...")
|
| 20 |
ds = load_dataset("chavajaz/wonders_dataset")
|
| 21 |
-
|
| 22 |
-
# train/validation/test are all present.
|
| 23 |
-
split_order = [s for s in ["train", "validation", "test"] if s in ds]
|
| 24 |
-
split_order += [s for s in ds.keys() if s not in split_order]
|
| 25 |
-
splits = [ds[s] for s in split_order]
|
| 26 |
-
full_ds = concatenate_datasets(splits) if len(splits) > 1 else splits[0]
|
| 27 |
class_names = full_ds.features["label"].names
|
| 28 |
|
| 29 |
print("Loading precomputed embeddings...")
|
| 30 |
embeddings_df = pd.read_parquet("wonders_embeddings.parquet")
|
| 31 |
image_embeddings = np.array(embeddings_df["embedding"].tolist(), dtype=np.float32)
|
| 32 |
EMBEDDINGS_TENSOR = torch.tensor(image_embeddings, device=device, dtype=torch.float32)
|
| 33 |
-
# Defensively L2-normalize so cosine similarity and the diversity threshold
|
| 34 |
-
# are correct even if the stored vectors weren't normalized.
|
| 35 |
-
EMBEDDINGS_TENSOR = EMBEDDINGS_TENSOR / EMBEDDINGS_TENSOR.norm(dim=-1, keepdim=True).clamp_min(1e-12)
|
| 36 |
-
|
| 37 |
-
if len(full_ds) != EMBEDDINGS_TENSOR.shape[0]:
|
| 38 |
-
print(
|
| 39 |
-
f"WARNING: dataset has {len(full_ds)} images but the embeddings file has "
|
| 40 |
-
f"{EMBEDDINGS_TENSOR.shape[0]} rows. They must line up 1:1 and be in the "
|
| 41 |
-
f"same order for results to be correct."
|
| 42 |
-
)
|
| 43 |
|
| 44 |
print(f"Ready. {len(full_ds)} images, embeddings {image_embeddings.shape}, on {device}")
|
| 45 |
|
|
@@ -106,7 +156,7 @@ def recommend(query_embedding, top_k=3, diversity_threshold=0.98):
|
|
| 106 |
|
| 107 |
def recommend_from_image(input_image):
|
| 108 |
if input_image is None:
|
| 109 |
-
return [], "
|
| 110 |
query_emb = embed_image(input_image)
|
| 111 |
results = recommend(query_emb, top_k=3)
|
| 112 |
gallery_items = [
|
|
@@ -115,14 +165,14 @@ def recommend_from_image(input_image):
|
|
| 115 |
]
|
| 116 |
medals = ["π₯", "π₯", "π₯"]
|
| 117 |
summary = "Your top 3 wonder matches:\n\n" + "\n".join(
|
| 118 |
-
f"{medals[i]} {r['label_name'].replace('_', ' ').title()
|
| 119 |
for i, r in enumerate(results)
|
| 120 |
)
|
| 121 |
return gallery_items, summary
|
| 122 |
|
| 123 |
def recommend_from_text(text_query):
|
| 124 |
if not text_query or not text_query.strip():
|
| 125 |
-
return [], "
|
| 126 |
query_emb = embed_text(text_query)
|
| 127 |
results = recommend(query_emb, top_k=3)
|
| 128 |
gallery_items = [
|
|
@@ -131,7 +181,7 @@ def recommend_from_text(text_query):
|
|
| 131 |
]
|
| 132 |
medals = ["π₯", "π₯", "π₯"]
|
| 133 |
summary = f'Best matches for "{text_query}":\n\n' + "\n".join(
|
| 134 |
-
f"{medals[i]} {r['label_name'].replace('_', ' ').title()
|
| 135 |
for i, r in enumerate(results)
|
| 136 |
)
|
| 137 |
return gallery_items, summary
|
|
@@ -148,13 +198,11 @@ CUSTOM_CSS = """
|
|
| 148 |
font-family: 'Nunito', 'Quicksand', -apple-system, sans-serif !important;
|
| 149 |
}
|
| 150 |
|
| 151 |
-
/* Headings get the rounder, friendlier Quicksand */
|
| 152 |
h1, h2, h3, h4 {
|
| 153 |
font-family: 'Quicksand', sans-serif !important;
|
| 154 |
letter-spacing: 0.3px !important;
|
| 155 |
}
|
| 156 |
|
| 157 |
-
/* ---------- HEADER ---------- */
|
| 158 |
#header-block {
|
| 159 |
background: linear-gradient(135deg, #8B4513 0%, #A0522D 50%, #CD853F 100%);
|
| 160 |
padding: 36px 28px;
|
|
@@ -182,7 +230,6 @@ h1, h2, h3, h4 {
|
|
| 182 |
opacity: 0.95;
|
| 183 |
}
|
| 184 |
|
| 185 |
-
/* ---------- TABS (the big upgrade) ---------- */
|
| 186 |
.tab-nav {
|
| 187 |
background: transparent !important;
|
| 188 |
border-bottom: none !important;
|
|
@@ -221,7 +268,6 @@ h1, h2, h3, h4 {
|
|
| 221 |
transform: translateY(-2px);
|
| 222 |
}
|
| 223 |
|
| 224 |
-
/* ---------- BUTTONS ---------- */
|
| 225 |
button.primary, .gr-button-primary {
|
| 226 |
background: linear-gradient(135deg, #8B4513 0%, #A0522D 100%) !important;
|
| 227 |
border: none !important;
|
|
@@ -239,7 +285,6 @@ button.primary:hover, .gr-button-primary:hover {
|
|
| 239 |
box-shadow: 0 6px 16px rgba(139, 69, 19, 0.45) !important;
|
| 240 |
}
|
| 241 |
|
| 242 |
-
/* ---------- INPUTS / PANELS ---------- */
|
| 243 |
.gr-box, .gr-form, .gr-panel {
|
| 244 |
background: #FFF8E7 !important;
|
| 245 |
border: 2px solid #D2B48C !important;
|
|
@@ -276,7 +321,6 @@ textarea:focus, input[type="text"]:focus {
|
|
| 276 |
padding: 10px !important;
|
| 277 |
}
|
| 278 |
|
| 279 |
-
/* ---------- FOOTER ---------- */
|
| 280 |
#footer-block {
|
| 281 |
margin-top: 28px;
|
| 282 |
padding: 22px 24px;
|
|
@@ -298,9 +342,6 @@ textarea:focus, input[type="text"]:focus {
|
|
| 298 |
}
|
| 299 |
"""
|
| 300 |
|
| 301 |
-
# Build sample image indices defensively so a smaller dataset can't crash startup.
|
| 302 |
-
SAMPLE_IDX = [i for i in [50, 2000, 5000, 7500, 10000] if i < len(full_ds)]
|
| 303 |
-
|
| 304 |
with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(
|
| 305 |
primary_hue="orange", secondary_hue="amber", neutral_hue="stone",
|
| 306 |
), title="Wonder Finder") as demo:
|
|
@@ -324,12 +365,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(
|
|
| 324 |
with gr.Column(scale=2):
|
| 325 |
img_gallery = gr.Gallery(label="Top 3 Matches", columns=3, rows=1, height=320, object_fit="cover")
|
| 326 |
img_summary = gr.Textbox(label="π Match Details", lines=6, show_copy_button=True)
|
| 327 |
-
if SAMPLE_IDX:
|
| 328 |
-
gr.Examples(
|
| 329 |
-
examples=[[full_ds[i]["image"]] for i in SAMPLE_IDX],
|
| 330 |
-
inputs=img_input,
|
| 331 |
-
label="β¨ Or try these sample images:",
|
| 332 |
-
)
|
| 333 |
img_btn.click(recommend_from_image, inputs=img_input, outputs=[img_gallery, img_summary])
|
| 334 |
|
| 335 |
with gr.Tab("π¬ Search by Description"):
|
|
@@ -345,19 +380,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(
|
|
| 345 |
with gr.Column(scale=2):
|
| 346 |
text_gallery = gr.Gallery(label="Top 3 Matches", columns=3, rows=1, height=320, object_fit="cover")
|
| 347 |
text_summary = gr.Textbox(label="π Match Details", lines=6, show_copy_button=True)
|
| 348 |
-
gr.Examples(
|
| 349 |
-
examples=[
|
| 350 |
-
["ancient stone pyramid in the desert"],
|
| 351 |
-
["tall modern skyscraper at night"],
|
| 352 |
-
["waterfall in the tropical jungle"],
|
| 353 |
-
["ancient Roman amphitheater"],
|
| 354 |
-
["statue of a religious figure with outstretched arms"],
|
| 355 |
-
["a misty stone monument at sunrise"],
|
| 356 |
-
["white marble palace with a dome"],
|
| 357 |
-
],
|
| 358 |
-
inputs=text_input,
|
| 359 |
-
label="β¨ Or try these example queries:",
|
| 360 |
-
)
|
| 361 |
text_btn.click(recommend_from_text, inputs=text_input, outputs=[text_gallery, text_summary])
|
| 362 |
|
| 363 |
gr.HTML("""
|
|
@@ -369,6 +391,10 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(
|
|
| 369 |
</div>
|
| 370 |
""")
|
| 371 |
|
| 372 |
-
|
| 373 |
if __name__ == "__main__":
|
| 374 |
-
demo.launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Wonder Finder β Visual recommender for the 12 Wonders of the World.
|
| 3 |
+
HF Spaces deployment.
|
| 4 |
+
|
| 5 |
+
Notes on defensive patches:
|
| 6 |
+
- gradio 4.44.x has a known bug in gradio_client/utils.py where api_info
|
| 7 |
+
schema generation crashes on `additionalProperties: True` (boolean schema).
|
| 8 |
+
- The bug raises gradio_client.utils.APIInfoParseError, which is NOT a
|
| 9 |
+
subclass of TypeError/KeyError/AttributeError β so naive try/except misses it.
|
| 10 |
+
- We patch at THREE layers: get_type, _json_schema_to_python_type, and
|
| 11 |
+
Blocks.get_api_info. Each catches Exception (the broadest possible).
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
# Belt-and-suspenders env vars
|
| 17 |
+
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
|
| 18 |
+
os.environ["GRADIO_SERVER_NAME"] = "0.0.0.0"
|
| 19 |
+
|
| 20 |
+
# ============================================================
|
| 21 |
+
# DEFENSIVE PATCHES β must run before any Gradio component init
|
| 22 |
+
# ============================================================
|
| 23 |
+
|
| 24 |
+
import gradio_client.utils as _gcu
|
| 25 |
+
|
| 26 |
+
# Patch 1: _json_schema_to_python_type β the inner recursive function
|
| 27 |
+
_original_json_schema = _gcu._json_schema_to_python_type
|
| 28 |
+
def _safe_json_schema(schema, defs=None):
|
| 29 |
+
# Handle boolean schemas (the actual bug trigger)
|
| 30 |
+
if isinstance(schema, bool):
|
| 31 |
+
return "Any"
|
| 32 |
+
if not isinstance(schema, dict):
|
| 33 |
+
return "Any"
|
| 34 |
+
try:
|
| 35 |
+
return _original_json_schema(schema, defs)
|
| 36 |
+
except Exception:
|
| 37 |
+
return "Any"
|
| 38 |
+
_gcu._json_schema_to_python_type = _safe_json_schema
|
| 39 |
+
|
| 40 |
+
# Patch 2: get_type β wraps the entry-point type checker
|
| 41 |
+
_original_get_type = _gcu.get_type
|
| 42 |
+
def _safe_get_type(schema):
|
| 43 |
+
if not isinstance(schema, dict):
|
| 44 |
+
return "Any"
|
| 45 |
+
try:
|
| 46 |
+
return _original_get_type(schema)
|
| 47 |
+
except Exception:
|
| 48 |
+
return "Any"
|
| 49 |
+
_gcu.get_type = _safe_get_type
|
| 50 |
+
|
| 51 |
+
# Patch 3: top-level api_info generator β safety net for anything we missed
|
| 52 |
import gradio as gr
|
| 53 |
+
import gradio.blocks as _gradio_blocks
|
| 54 |
+
_original_get_api_info = _gradio_blocks.Blocks.get_api_info
|
| 55 |
+
def _safe_get_api_info(self):
|
| 56 |
+
try:
|
| 57 |
+
return _original_get_api_info(self)
|
| 58 |
+
except Exception:
|
| 59 |
+
return {"named_endpoints": {}, "unnamed_endpoints": {}}
|
| 60 |
+
_gradio_blocks.Blocks.get_api_info = _safe_get_api_info
|
| 61 |
+
|
| 62 |
+
# ============================================================
|
| 63 |
+
# REGULAR IMPORTS
|
| 64 |
+
# ============================================================
|
| 65 |
+
|
| 66 |
import torch
|
| 67 |
import numpy as np
|
| 68 |
import pandas as pd
|
| 69 |
+
from PIL import Image
|
| 70 |
from transformers import CLIPProcessor, CLIPModel
|
| 71 |
from datasets import load_dataset, concatenate_datasets
|
| 72 |
|
| 73 |
# ============================================================
|
| 74 |
+
# LOAD EVERYTHING ON STARTUP
|
| 75 |
# ============================================================
|
| 76 |
|
| 77 |
print("Loading CLIP model...")
|
|
|
|
| 83 |
|
| 84 |
print("Loading dataset...")
|
| 85 |
ds = load_dataset("chavajaz/wonders_dataset")
|
| 86 |
+
full_ds = concatenate_datasets([ds["train"], ds["validation"], ds["test"]])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
class_names = full_ds.features["label"].names
|
| 88 |
|
| 89 |
print("Loading precomputed embeddings...")
|
| 90 |
embeddings_df = pd.read_parquet("wonders_embeddings.parquet")
|
| 91 |
image_embeddings = np.array(embeddings_df["embedding"].tolist(), dtype=np.float32)
|
| 92 |
EMBEDDINGS_TENSOR = torch.tensor(image_embeddings, device=device, dtype=torch.float32)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
print(f"Ready. {len(full_ds)} images, embeddings {image_embeddings.shape}, on {device}")
|
| 95 |
|
|
|
|
| 156 |
|
| 157 |
def recommend_from_image(input_image):
|
| 158 |
if input_image is None:
|
| 159 |
+
return [], "Please upload an image to find matching wonders."
|
| 160 |
query_emb = embed_image(input_image)
|
| 161 |
results = recommend(query_emb, top_k=3)
|
| 162 |
gallery_items = [
|
|
|
|
| 165 |
]
|
| 166 |
medals = ["π₯", "π₯", "π₯"]
|
| 167 |
summary = "Your top 3 wonder matches:\n\n" + "\n".join(
|
| 168 |
+
f"{medals[i]} {r['label_name'].replace('_', ' ').title()} β similarity {r['score']:.3f}"
|
| 169 |
for i, r in enumerate(results)
|
| 170 |
)
|
| 171 |
return gallery_items, summary
|
| 172 |
|
| 173 |
def recommend_from_text(text_query):
|
| 174 |
if not text_query or not text_query.strip():
|
| 175 |
+
return [], "Please describe what you're looking for."
|
| 176 |
query_emb = embed_text(text_query)
|
| 177 |
results = recommend(query_emb, top_k=3)
|
| 178 |
gallery_items = [
|
|
|
|
| 181 |
]
|
| 182 |
medals = ["π₯", "π₯", "π₯"]
|
| 183 |
summary = f'Best matches for "{text_query}":\n\n' + "\n".join(
|
| 184 |
+
f"{medals[i]} {r['label_name'].replace('_', ' ').title()} β similarity {r['score']:.3f}"
|
| 185 |
for i, r in enumerate(results)
|
| 186 |
)
|
| 187 |
return gallery_items, summary
|
|
|
|
| 198 |
font-family: 'Nunito', 'Quicksand', -apple-system, sans-serif !important;
|
| 199 |
}
|
| 200 |
|
|
|
|
| 201 |
h1, h2, h3, h4 {
|
| 202 |
font-family: 'Quicksand', sans-serif !important;
|
| 203 |
letter-spacing: 0.3px !important;
|
| 204 |
}
|
| 205 |
|
|
|
|
| 206 |
#header-block {
|
| 207 |
background: linear-gradient(135deg, #8B4513 0%, #A0522D 50%, #CD853F 100%);
|
| 208 |
padding: 36px 28px;
|
|
|
|
| 230 |
opacity: 0.95;
|
| 231 |
}
|
| 232 |
|
|
|
|
| 233 |
.tab-nav {
|
| 234 |
background: transparent !important;
|
| 235 |
border-bottom: none !important;
|
|
|
|
| 268 |
transform: translateY(-2px);
|
| 269 |
}
|
| 270 |
|
|
|
|
| 271 |
button.primary, .gr-button-primary {
|
| 272 |
background: linear-gradient(135deg, #8B4513 0%, #A0522D 100%) !important;
|
| 273 |
border: none !important;
|
|
|
|
| 285 |
box-shadow: 0 6px 16px rgba(139, 69, 19, 0.45) !important;
|
| 286 |
}
|
| 287 |
|
|
|
|
| 288 |
.gr-box, .gr-form, .gr-panel {
|
| 289 |
background: #FFF8E7 !important;
|
| 290 |
border: 2px solid #D2B48C !important;
|
|
|
|
| 321 |
padding: 10px !important;
|
| 322 |
}
|
| 323 |
|
|
|
|
| 324 |
#footer-block {
|
| 325 |
margin-top: 28px;
|
| 326 |
padding: 22px 24px;
|
|
|
|
| 342 |
}
|
| 343 |
"""
|
| 344 |
|
|
|
|
|
|
|
|
|
|
| 345 |
with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(
|
| 346 |
primary_hue="orange", secondary_hue="amber", neutral_hue="stone",
|
| 347 |
), title="Wonder Finder") as demo:
|
|
|
|
| 365 |
with gr.Column(scale=2):
|
| 366 |
img_gallery = gr.Gallery(label="Top 3 Matches", columns=3, rows=1, height=320, object_fit="cover")
|
| 367 |
img_summary = gr.Textbox(label="π Match Details", lines=6, show_copy_button=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
img_btn.click(recommend_from_image, inputs=img_input, outputs=[img_gallery, img_summary])
|
| 369 |
|
| 370 |
with gr.Tab("π¬ Search by Description"):
|
|
|
|
| 380 |
with gr.Column(scale=2):
|
| 381 |
text_gallery = gr.Gallery(label="Top 3 Matches", columns=3, rows=1, height=320, object_fit="cover")
|
| 382 |
text_summary = gr.Textbox(label="π Match Details", lines=6, show_copy_button=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
text_btn.click(recommend_from_text, inputs=text_input, outputs=[text_gallery, text_summary])
|
| 384 |
|
| 385 |
gr.HTML("""
|
|
|
|
| 391 |
</div>
|
| 392 |
""")
|
| 393 |
|
|
|
|
| 394 |
if __name__ == "__main__":
|
| 395 |
+
demo.launch(
|
| 396 |
+
server_name="0.0.0.0",
|
| 397 |
+
server_port=7860,
|
| 398 |
+
show_api=False,
|
| 399 |
+
share=False,
|
| 400 |
+
)
|
requirements.txt
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
-
gradio==4.44.
|
| 2 |
-
|
| 3 |
transformers==4.45.2
|
| 4 |
torch==2.4.1
|
| 5 |
datasets==3.0.0
|
| 6 |
pillow==10.4.0
|
| 7 |
numpy==1.26.4
|
| 8 |
pandas==2.2.2
|
| 9 |
-
|
| 10 |
-
huggingface-hub==0.25.0
|
|
|
|
| 1 |
+
gradio==4.44.1
|
| 2 |
+
gradio-client==1.3.0
|
| 3 |
transformers==4.45.2
|
| 4 |
torch==2.4.1
|
| 5 |
datasets==3.0.0
|
| 6 |
pillow==10.4.0
|
| 7 |
numpy==1.26.4
|
| 8 |
pandas==2.2.2
|
| 9 |
+
huggingface-hub==0.25.2
|
|
|