adf-chatbot2 / src /ui /components.py
Yannick Lemin
fixed search
6dfc718
import gradio as gr
from src.search.service import SearchService
from src.utils.constants import Constants
from src.utils.logging import get_logger
class UIComponents:
"""
Class to handle the Gradio UI components for the application.
"""
def __init__(self):
"""
Initialize the UI components.
"""
self.logger = get_logger()
self.logger.info("Initializing UI components")
try:
self.search_service = SearchService()
self.logger.info("UI components initialized successfully")
except Exception as e:
self.logger.error(f"Failed to initialize UI components: {str(e)}")
raise
def create_customer_tab(self):
"""
Create the customer search tab.
Returns:
gradio.Tab: The customer search tab.
"""
with gr.Tab(Constants.UI_CUSTOMER_TAB) as tab:
gr.Markdown(f"## {Constants.UI_CUSTOMER_DESCRIPTION}")
with gr.Row():
text_input = gr.Textbox(
label=Constants.UI_TEXT_INPUT_LABEL,
placeholder="Ex: Une grille décorative pour mon jardin avec des motifs floraux",
lines=3
)
with gr.Row():
image_input = gr.Image(
label=Constants.UI_IMAGE_INPUT_LABEL,
type="pil"
)
with gr.Row():
search_button = gr.Button(Constants.UI_SEARCH_BUTTON, variant="primary")
with gr.Row(visible=False) as loading_indicator:
gr.Markdown(f"### {Constants.UI_LOADING_TEXT}")
results_container = gr.HTML(visible=False)
def search(text, image):
self.logger.info("Customer UI search initiated")
self.logger.debug(f"Search parameters - Text: '{text}', Image provided: {image is not None}")
if not text and image is None:
self.logger.info("No search parameters provided, returning empty results")
return None, gr.update(visible=False), gr.update(visible=False)
try:
self.logger.info("Calling customer search service")
results = self.search_service.customer_search(text, image)
if not results:
self.logger.info("No results found for customer search")
return None, gr.update(visible=False), gr.HTML(
f"<div class='no-results'>{Constants.UI_NO_RESULTS}</div>", visible=True)
# Prepare gallery images
self.logger.debug(f"Preparing gallery with {len(results)} images")
gallery_images = []
for result in results:
gallery_images.append((result["thumbnail"], result["title"]))
# Prepare HTML for results
self.logger.debug("Creating HTML for search results")
html = self._create_results_html(results)
self.logger.info(f"Customer search completed successfully with {len(results)} results")
return gr.HTML(html, visible=True)
except Exception as e:
self.logger.error(f"Error during customer UI search: {str(e)}")
# Return a user-friendly error message
error_html = f"<div class='no-results'>Une erreur s'est produite lors de la recherche. Veuillez réessayer.</div>"
return None, gr.update(visible=False), gr.HTML(error_html, visible=True)
search_button.click(
fn=search,
inputs=[text_input, image_input],
outputs=[results_container],
# js="""
# function(text, image) {
# // Disable search button and show loading indicator
# document.querySelector("button[variant='primary']").disabled = true;
# return [text, image];
# }
# """,
preprocess=True
).then(
# js="""
# function() {
# // Re-enable search button and hide loading indicator
# document.querySelector("button[variant='primary']").disabled = false;
# return [];
# }
# """
)
# Show/hide loading indicator
search_button.click(
fn=lambda: gr.update(visible=True),
inputs=None,
outputs=loading_indicator
).then(
fn=lambda: gr.update(visible=False),
inputs=None,
outputs=loading_indicator
)
return tab
def create_staff_tab(self):
"""
Create the staff search tab.
Returns:
gradio.Tab: The staff search tab.
"""
with gr.Tab(Constants.UI_STAFF_TAB) as tab:
gr.Markdown(f"## {Constants.UI_STAFF_DESCRIPTION}")
with gr.Row():
image_input = gr.Image(
label=Constants.UI_IMAGE_INPUT_LABEL,
type="pil"
)
with gr.Row():
search_button = gr.Button(Constants.UI_SEARCH_BUTTON, variant="primary")
with gr.Row(visible=False) as loading_indicator:
gr.Markdown(f"### {Constants.UI_LOADING_TEXT}")
result_container = gr.HTML(visible=False)
def search(image):
self.logger.info("Staff UI search initiated")
self.logger.debug(f"Search parameters - Image provided: {image is not None}")
if image is None:
self.logger.info("No image provided for staff search, returning empty results")
return gr.update(visible=False)
try:
self.logger.info("Calling staff search service")
result = self.search_service.staff_search(image)
if not result:
self.logger.info("No results found for staff search")
return gr.HTML(f"<div class='no-results'>{Constants.UI_NO_RESULTS}</div>", visible=True)
# Prepare HTML for result
self.logger.debug("Creating HTML for search result")
html = self._create_results_html([result])
self.logger.info("Staff search completed successfully with a match")
return gr.HTML(html, visible=True)
except Exception as e:
self.logger.error(f"Error during staff UI search: {str(e)}")
# Return a user-friendly error message
error_html = f"<div class='no-results'>Une erreur s'est produite lors de la recherche. Veuillez réessayer.</div>"
return gr.HTML(error_html, visible=True)
search_button.click(
fn=search,
inputs=[image_input],
outputs=[result_container],
# js="""
# function(image) {
# // Disable search button and show loading indicator
# document.querySelector("button[variant='primary']").disabled = true;
# return [image];
# }
# """,
preprocess=True
).then(
# js="""
# function() {
# // Re-enable search button and hide loading indicator
# document.querySelector("button[variant='primary']").disabled = false;
# return [];
# }
# """
)
# Show/hide loading indicator
search_button.click(
fn=lambda: gr.update(visible=True),
inputs=None,
outputs=loading_indicator
).then(
fn=lambda: gr.update(visible=False),
inputs=None,
outputs=loading_indicator
)
return tab
def _create_results_html(self, results):
"""
Create HTML for displaying search results.
Args:
results (list): List of search results.
Returns:
str: HTML string for displaying results.
"""
self.logger.debug(f"Creating HTML for {len(results)} results")
try:
html = "<div class='results-container'>"
for i, result in enumerate(results):
self.logger.debug(f"Adding result {i + 1}: {result['title']}")
html += f"""
<div class='result-card'>
<div class='result-image'>
<img src='{result["thumbnail"]}' alt='{result["title"]}'>
</div>
<div class='result-content'>
<h3>{result["title"]}</h3>
<p>{result["description"]}</p>
<p class='price'><strong>{Constants.UI_PRICE_LABEL}</strong> {result["price"]}</p>
<a href='{result["product_page_url"]}' target='_blank' class='product-button'>{Constants.UI_PRODUCT_BUTTON}</a>
</div>
</div>
"""
html += "</div>"
self.logger.debug("HTML for results created successfully")
except Exception as e:
self.logger.error(f"Error creating HTML for results: {str(e)}")
# Create a simple fallback HTML in case of error
html = "<div class='results-container'><p>Résultats disponibles mais impossible de les afficher correctement.</p></div>"
# Add CSS for styling
html += """
<style>
.results-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
}
.result-card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
width: 300px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
background-color: white;
}
.result-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.result-image {
height: 200px;
overflow: hidden;
}
.result-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.result-content {
padding: 15px;
}
.result-content h3 {
margin-top: 0;
color: #333;
}
.price {
font-size: 1.1em;
color: #e63946;
}
.product-button {
display: inline-block;
background-color: #457b9d;
color: white;
padding: 8px 16px;
text-decoration: none;
border-radius: 4px;
margin-top: 10px;
transition: background-color 0.3s ease;
}
.product-button:hover {
background-color: #1d3557;
}
.no-results {
text-align: center;
padding: 20px;
font-size: 1.2em;
color: #666;
}
/* Responsive design */
@media (max-width: 768px) {
.results-container {
flex-direction: column;
align-items: center;
}
.result-card {
width: 100%;
max-width: 350px;
}
}
</style>
"""
return html
def create_interface(self):
"""
Create the complete Gradio interface.
Returns:
gradio.Blocks: The Gradio interface.
"""
with gr.Blocks(title=Constants.UI_TITLE, theme=gr.themes.Soft()) as interface:
gr.Markdown(f"# {Constants.UI_TITLE}")
with gr.Tabs():
self.create_customer_tab()
self.create_staff_tab()
# Add CSS for responsive design
gr.HTML("""
<style>
/* Responsive design */
@media (max-width: 768px) {
.gradio-container {
padding: 10px !important;
}
.gradio-row {
flex-direction: column !important;
}
}
</style>
""")
return interface
def close(self):
"""
Close connections and resources.
"""
self.logger.info("Closing UI components resources")
try:
self.search_service.close()
self.logger.info("UI components resources closed successfully")
except Exception as e:
self.logger.error(f"Error closing UI components resources: {str(e)}")
# We don't re-raise the exception here to ensure cleanup continues even if there's an error