"""
Reusable UI components for the Gradio interface.
"""
import gradio as gr
import json
import time
from typing import Any, Dict, List, Optional, Tuple, Callable
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from .themes import create_info_card, create_status_indicator, format_search_result, create_progress_bar
def create_header() -> gr.HTML:
"""Create the header section."""
header_html = """
"""
return gr.HTML(header_html)
def create_file_upload_section() -> Tuple[gr.File, gr.HTML, gr.Button]:
"""Create file upload section."""
file_upload = gr.File(
label="Upload Documents",
file_types=[".pdf", ".docx", ".txt"],
file_count="multiple",
interactive=True,
height=150
)
upload_status = gr.HTML(
create_status_indicator("ready", "Ready to upload documents"),
visible=True
)
# Make the button more prominent by putting it in a separate row
with gr.Row():
with gr.Column(scale=1):
gr.HTML("") # Empty space
with gr.Column(scale=2):
upload_button = gr.Button(
"🚀 Process Documents",
variant="primary",
size="lg",
interactive=False,
elem_classes=["process-button"]
)
with gr.Column(scale=1):
gr.HTML("") # Empty space
return file_upload, upload_status, upload_button
def create_search_interface() -> Tuple[gr.Textbox, gr.Row, gr.Button]:
"""Create search interface components."""
search_query = gr.Textbox(
label="Search Query",
placeholder="Ask a question about your documents...",
lines=2,
max_lines=4,
interactive=True,
scale=4
)
with gr.Row() as search_controls:
with gr.Column(scale=1):
search_mode = gr.Dropdown(
choices=["hybrid", "vector", "bm25"],
value="hybrid",
label="Search Mode",
interactive=True
)
with gr.Column(scale=1):
num_results = gr.Slider(
minimum=1,
maximum=20,
value=10,
step=1,
label="Number of Results",
interactive=True
)
with gr.Column(scale=1):
enable_reranking = gr.Checkbox(
label="Enable Re-ranking",
value=True,
interactive=True
)
search_button = gr.Button(
"Search",
variant="primary",
size="lg",
interactive=False
)
return search_query, search_controls, search_button, search_mode, num_results, enable_reranking
def create_results_display() -> Tuple[gr.HTML, gr.JSON, gr.HTML]:
"""Create results display components."""
results_html = gr.HTML(
"No search results yet. Upload documents and try searching!
",
visible=True
)
results_json = gr.JSON(
label="Detailed Results (JSON)",
visible=False
)
search_stats = gr.HTML(visible=False)
return results_html, results_json, search_stats
def create_document_management() -> Tuple[gr.HTML, gr.Button, gr.Button]:
"""Create document management interface."""
document_list = gr.HTML(
"No documents uploaded yet.
"
)
with gr.Row():
refresh_docs_btn = gr.Button("Refresh List", variant="secondary")
clear_docs_btn = gr.Button("Clear All Documents", variant="stop")
return document_list, refresh_docs_btn, clear_docs_btn
def create_system_status() -> gr.HTML:
"""Create system status display."""
return gr.HTML(
create_status_indicator("loading", "Initializing system..."),
visible=True
)
def create_analytics_dashboard() -> Tuple[gr.HTML, gr.Plot, gr.Plot, gr.Dataframe]:
"""Create analytics dashboard components."""
# System overview cards
system_overview = gr.HTML(
""
)
# Query analytics chart
query_chart = gr.Plot(
label="Queries Over Time",
visible=False
)
# Search modes chart
search_modes_chart = gr.Plot(
label="Search Modes Distribution",
visible=False
)
# Recent activity table
activity_table = gr.Dataframe(
headers=["Timestamp", "Activity", "Details", "Status"],
label="Recent Activity",
visible=False
)
return system_overview, query_chart, search_modes_chart, activity_table
def format_document_list(documents: List[Dict[str, Any]]) -> str:
"""Format document list as HTML."""
if not documents:
return "No documents uploaded yet.
"
html_parts = [""]
for doc in documents:
filename = doc.get("filename", "Unknown")
chunk_count = doc.get("chunk_count", 0)
file_type = doc.get("file_type", "unknown").upper()
file_size = doc.get("file_size", 0)
# Format file size
if file_size > 1024 * 1024:
size_str = f"{file_size / (1024 * 1024):.1f} MB"
elif file_size > 1024:
size_str = f"{file_size / 1024:.1f} KB"
else:
size_str = f"{file_size} bytes"
doc_html = f"""
📄 {filename}
Type: {file_type}
Size: {size_str}
Chunks: {chunk_count}
"""
html_parts.append(doc_html)
html_parts.append("
")
return "".join(html_parts)
def format_search_results(results: List[Dict[str, Any]], search_time: float, query: str) -> Tuple[str, str]:
"""Format search results as HTML and create search statistics."""
if not results:
results_html = """
🔍
No results found for your query.
Try different keywords or check your search settings.
"""
stats_html = f"""
Search completed in {search_time:.2f}s - No results found
"""
return results_html, stats_html
# Format results
results_parts = [f"Search Results for: \"{query}\"
"]
for i, result in enumerate(results, 1):
result_html = format_search_result(result, i)
results_parts.append(result_html)
results_html = "".join(results_parts)
# Create search statistics
avg_score = sum(r.get("scores", {}).get("final_score", 0) for r in results) / len(results)
stats_html = f"""
{len(results)}
Results Found
{search_time:.2f}s
Search Time
{avg_score:.3f}
Avg Score
"""
return results_html, stats_html
def create_analytics_charts(analytics_data: Dict[str, Any]) -> Tuple[go.Figure, go.Figure]:
"""Create analytics charts."""
system_data = analytics_data.get("system", {})
queries_data = analytics_data.get("queries_24h", {})
# Queries over time chart
queries_per_hour = queries_data.get("queries_per_hour", [])
hours = list(range(len(queries_per_hour)))
query_fig = go.Figure()
query_fig.add_trace(go.Scatter(
x=hours,
y=queries_per_hour,
mode='lines+markers',
name='Queries per Hour',
line=dict(color='#667eea', width=3),
marker=dict(size=8, color='#667eea')
))
query_fig.update_layout(
title="Queries Over Time (24 Hours)",
xaxis_title="Hours Ago",
yaxis_title="Number of Queries",
template="plotly_white",
height=300
)
# Search modes distribution
search_modes = queries_data.get("search_modes", {})
if search_modes:
modes = list(search_modes.keys())
counts = list(search_modes.values())
modes_fig = go.Figure(data=[
go.Pie(
labels=modes,
values=counts,
hole=0.3,
marker=dict(colors=['#667eea', '#8b5cf6', '#06b6d4'])
)
])
modes_fig.update_layout(
title="Search Modes Distribution",
template="plotly_white",
height=300
)
else:
modes_fig = go.Figure()
modes_fig.update_layout(
title="Search Modes Distribution",
template="plotly_white",
height=300,
annotations=[dict(text="No data available", showarrow=False)]
)
return query_fig, modes_fig
def format_system_overview(analytics_data: Dict[str, Any]) -> str:
"""Format system overview cards."""
system_data = analytics_data.get("system", {})
cards_html = """
"""
# Total queries
total_queries = system_data.get("total_queries", 0)
cards_html += create_info_card("Total Queries", str(total_queries), "All-time search queries")
# Documents processed
total_docs = system_data.get("total_documents_processed", 0)
cards_html += create_info_card("Documents", str(total_docs), "Successfully processed")
# Uptime
uptime_hours = system_data.get("uptime_hours", 0)
uptime_str = f"{uptime_hours:.1f}h" if uptime_hours < 24 else f"{uptime_hours/24:.1f}d"
cards_html += create_info_card("Uptime", uptime_str, "System running time")
# Active sessions
active_sessions = system_data.get("active_sessions", 0)
cards_html += create_info_card("Active Users", str(active_sessions), "Current sessions")
cards_html += "
"
return cards_html
def create_progress_callback() -> Callable:
"""Create a progress callback function for document processing."""
def progress_callback(message: str, progress: float) -> str:
return create_progress_bar(progress, message)
return progress_callback
def create_error_display(error_message: str) -> str:
"""Create error display HTML."""
return f"""
"""
def create_success_display(message: str) -> str:
"""Create success display HTML."""
return f"""
"""
def create_loading_display(message: str = "Processing...") -> str:
"""Create loading display HTML."""
return f"""
"""