Spaces:
Sleeping
Sleeping
import gradio as gr | |
from utils import SafeProgress, format_categories_html | |
from embeddings import create_product_embeddings | |
from similarity import compute_similarities | |
from chicory_api import call_chicory_parser | |
# Global variable for embeddings | |
embeddings = {} | |
# Sample product names for the example button | |
EXAMPLE_PRODUCTS = """Nature's Promise Spring Water Multipack | |
Red's Burritos | |
Nature's Promise Spring Water Multipack | |
Schweppes Seltzer 12 Pack | |
Hunt's Pasta Sauce | |
Buitoni Filled Pasta | |
Buitoni Filled Pasta | |
Samuel Adams or Blue Moon 12 Pack | |
Mrs. T's Pierogies | |
Buitoni Filled Pasta | |
Pillsbury Dough | |
Nature's Promise Organic Celery Hearts | |
MorningStar Farms Meatless Nuggets, Patties or Crumbles | |
Nature's Promise Organic Celery Hearts | |
Boar's Head Mild Provolone Cheese | |
Athenos Feta Crumbles""" | |
def categorize_products(product_input, is_file=False, top_n=5, confidence_threshold=0.5, progress=None): | |
"""Categorize products from text input or file""" | |
progress_tracker = SafeProgress(progress) | |
progress_tracker(0, desc="Starting...") | |
# Parse input | |
if is_file: | |
from utils import parse_product_file | |
try: | |
product_names = parse_product_file(product_input.name) | |
except Exception as e: | |
return f"<div style='color: #d32f2f; font-weight: bold;'>Error: {str(e)}</div>" | |
else: | |
product_names = [line.strip() for line in product_input.split("\n") if line.strip()] | |
if not product_names: | |
return "<div style='color: #d32f2f;'>No product names provided.</div>" | |
# Create embeddings | |
progress_tracker(0.2, desc="Generating product embeddings...") | |
products_embeddings = create_product_embeddings(product_names) | |
# Call Chicory Parser API | |
progress_tracker(0.5, desc="Calling Chicory Parser API...") | |
chicory_results = call_chicory_parser(product_names) | |
# Compute similarities | |
progress_tracker(0.7, desc="Computing similarities...") | |
all_similarities = compute_similarities(embeddings, products_embeddings) | |
# Format results | |
progress_tracker(0.9, desc="Formatting results...") | |
output_html = "<div style='font-family: Arial, sans-serif; max-width: 100%; overflow-x: auto;'>" | |
# Add debug info to verify data | |
output_html += f"<p style='color: #555;'>Processing {len(product_names)} products.</p>" | |
for product, similarities in all_similarities.items(): | |
filtered_similarities = [(ingredient, score) for ingredient, score in similarities if score >= confidence_threshold] | |
top_similarities = filtered_similarities[:top_n] | |
# Debug info for Chicory results | |
chicory_data = chicory_results.get(product, []) | |
output_html += format_categories_html(product, top_similarities, chicory_result=chicory_data) | |
output_html += "<hr style='margin: 15px 0; border: 0; border-top: 1px solid #eee;'>" | |
output_html += "</div>" | |
if not all_similarities: | |
output_html = "<div style='color: #d32f2f; font-weight: bold; padding: 20px;'>No results found. Please check your input or try different products.</div>" | |
progress_tracker(1.0, desc="Done!") | |
return output_html | |
def load_examples(): | |
"""Load example product names into the text input""" | |
return EXAMPLE_PRODUCTS | |
def create_demo(): | |
"""Create the Gradio interface""" | |
with gr.Blocks(css=""" | |
.results-container { | |
height: 600px !important; | |
max-height: 600px !important; | |
overflow-y: auto !important; | |
overflow-x: hidden !important; | |
padding: 15px !important; | |
border: 1px solid #333 !important; | |
background-color: #121212 !important; | |
color: #fff !important; | |
display: block !important; | |
} | |
.product-result { | |
background-color: #1e1e1e !important; | |
border-radius: 8px !important; | |
padding: 15px !important; | |
margin-bottom: 20px !important; | |
} | |
.product-result h3 { | |
margin-top: 5px !important; | |
border-bottom: 2px solid #333 !important; | |
padding-bottom: 8px !important; | |
color: #e0e0e0 !important; | |
} | |
.result-section { | |
margin: 15px 0 !important; | |
border-radius: 8px !important; | |
} | |
.result-section h4 { | |
color: #e0e0e0 !important; | |
font-size: 16px !important; | |
font-weight: 500 !important; | |
} | |
hr { | |
border-color: #333 !important; | |
} | |
""", js=""" | |
document.addEventListener('keydown', function(e) { | |
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { | |
// Find the active tab | |
const tabs = document.querySelector('.tabs'); | |
if (!tabs) return; | |
const activeTabIndex = Array.from(tabs.querySelectorAll('button')) | |
.findIndex(tab => tab.getAttribute('aria-selected') === 'true'); | |
// Find the submit button in the active tab content | |
const tabContents = document.querySelectorAll('.tabitem'); | |
if (activeTabIndex < 0 || activeTabIndex >= tabContents.length) return; | |
const activeTab = tabContents[activeTabIndex]; | |
const button = activeTabIndex === 0 | |
? activeTab.querySelector('button[variant="primary"]') // Text Input tab | |
: activeTab.querySelector('button[variant="primary"]'); // File Upload tab | |
if (button) { | |
button.click(); | |
e.preventDefault(); | |
} | |
} | |
}); | |
""") as demo: | |
gr.Markdown("# Product Categorization Tool\nAnalyze products and find the most similar ingredients using AI embeddings.") | |
gr.Markdown("Use **Cmd+Enter** (or Ctrl+Enter) to quickly categorize products (shortcut works in both tabs).") | |
with gr.Tabs() as tabs: | |
with gr.TabItem("Text Input"): | |
with gr.Row(): | |
with gr.Column(scale=1): | |
# Input section | |
text_input = gr.Textbox( | |
lines=10, | |
placeholder="Enter product names, one per line", | |
label="Product Names" | |
) | |
input_controls = gr.Row() | |
with input_controls: | |
top_n = gr.Slider(1, 25, 5, label="Top N Results") | |
confidence = gr.Slider(0.1, 0.9, 0.5, label="Confidence Threshold") | |
with gr.Row(): | |
examples_btn = gr.Button("Load Examples", variant="secondary") | |
categorize_btn = gr.Button("Categorize", variant="primary") | |
with gr.Column(scale=1): | |
# Results section | |
text_output = gr.HTML(label="Categorization Results", elem_classes="results-container") | |
with gr.TabItem("File Upload"): | |
with gr.Row(): | |
with gr.Column(scale=1): | |
# Input section | |
file_input = gr.File(label="Upload JSON or text file with products", file_types=[".json", ".txt"]) | |
file_controls = gr.Row() | |
with file_controls: | |
file_top_n = gr.Slider(1, 25, 5, label="Top N Results") | |
file_confidence = gr.Slider(0.1, 0.9, 0.5, label="Confidence Threshold") | |
process_btn = gr.Button("Process File", variant="primary") | |
with gr.Column(scale=1): | |
# Results section | |
file_output = gr.HTML(label="Categorization Results", elem_classes="results-container") | |
# Connect button click to the categorize function - more explicit binding | |
categorize_btn.click( | |
fn=categorize_products, | |
inputs=[text_input, gr.State(False), top_n, confidence], | |
outputs=text_output | |
) | |
# Add examples button functionality | |
examples_btn.click( | |
fn=load_examples, | |
inputs=[], | |
outputs=text_input | |
) | |
# Remove the Enter key trigger as we're now using Cmd+Enter/Ctrl+Enter instead | |
# text_input.submit( | |
# fn=categorize_products, | |
# inputs=[text_input, gr.State(False), top_n, confidence], | |
# outputs=text_output | |
# ) | |
process_btn.click( | |
fn=categorize_products, | |
inputs=[file_input, gr.State(True), file_top_n, file_confidence], | |
outputs=file_output | |
) | |
# Remove the custom JavaScript keyboard handler since we're using Gradio's built-in submit event | |
gr.Markdown("Powered by Voyage AI embeddings • Built with Gradio") | |
return demo | |