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"
Error: {str(e)}
"
else:
product_names = [line.strip() for line in product_input.split("\n") if line.strip()]
if not product_names:
return "No product names provided.
"
# 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 = ""
# Add debug info to verify data
output_html += f"
Processing {len(product_names)} products.
"
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 += "
"
output_html += "
"
if not all_similarities:
output_html = "No results found. Please check your input or try different products.
"
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;
}
""") as demo:
gr.Markdown("# Product Categorization Tool\nAnalyze products and find the most similar ingredients using AI embeddings.")
gr.Markdown("Use **Cmd+Enter** (Mac) or **Ctrl+Enter** (Windows/Linux) to quickly categorize products.")
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
)
process_btn.click(
fn=categorize_products,
inputs=[file_input, gr.State(True), file_top_n, file_confidence],
outputs=file_output
)
# Add keyboard shortcut handler for both tabs
text_input.change(None, None, None, js="""
function() {
// Set up the keyboard event listener
document.removeEventListener('keydown', cmdEnterHandler);
document.addEventListener('keydown', cmdEnterHandler);
function cmdEnterHandler(e) {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
// Find the currently visible tab content
const textTab = document.querySelector('.tabitem:nth-child(1)');
const fileTab = document.querySelector('.tabitem:nth-child(2)');
if (textTab && window.getComputedStyle(textTab).display !== 'none') {
// Text tab is active
const button = textTab.querySelector('button');
if (button) button.click();
} else if (fileTab && window.getComputedStyle(fileTab).display !== 'none') {
// File tab is active
const button = fileTab.querySelector('button');
if (button) button.click();
}
e.preventDefault();
}
}
return [];
}
""")
gr.Markdown("Powered by Voyage AI embeddings • Built with Gradio")
return demo