"""History gallery tab component.""" import logging from pathlib import Path from typing import Optional import gradio as gr from src.storage.history import history_manager, HistoryItem logger = logging.getLogger(__name__) def get_history_gallery(type_filter: str = "All") -> list[tuple[str, str]]: """Get history items for gallery display.""" items = history_manager.get_recent(limit=100) gallery_items = [] for item in items: # Apply type filter if type_filter == "Images" and item.type != "image": continue if type_filter == "Videos" and item.type != "video": continue output_path = Path(item.output_path) if not output_path.exists(): continue # For images, show the image directly if item.type == "image": caption = f"{item.output_filename}\n{item.output_width}x{item.output_height}" gallery_items.append((str(output_path), caption)) # For videos, show output path (Gradio Gallery will handle video thumbnails) elif item.type == "video": caption = f"🎬 {item.output_filename}\n{item.output_width}x{item.output_height}" gallery_items.append((str(output_path), caption)) return gallery_items def get_history_stats() -> str: """Get history statistics.""" stats = history_manager.get_statistics() return ( f"**History Statistics**\n\n" f"- Total Items: {stats['total_items']}\n" f"- Images: {stats['images']}\n" f"- Videos: {stats['videos']}\n" f"- Total Processing Time: {stats['total_processing_time']/60:.1f} min\n" f"- Total Input Size: {stats['total_input_size_mb']:.1f} MB\n" f"- Total Output Size: {stats['total_output_size_mb']:.1f} MB" ) def search_history(query: str, type_filter: str = "All") -> list[tuple[str, str]]: """Search history by filename.""" if not query: return get_history_gallery(type_filter) items = history_manager.search(query) gallery_items = [] for item in items: # Apply type filter if type_filter == "Images" and item.type != "image": continue if type_filter == "Videos" and item.type != "video": continue output_path = Path(item.output_path) if not output_path.exists(): continue if item.type == "image": caption = f"{item.output_filename}\n{item.output_width}x{item.output_height}" gallery_items.append((str(output_path), caption)) elif item.type == "video": caption = f"🎬 {item.output_filename}\n{item.output_width}x{item.output_height}" gallery_items.append((str(output_path), caption)) return gallery_items def get_item_details(evt: gr.SelectData, type_filter: str = "All") -> tuple[Optional[tuple], str]: """Get details for selected history item.""" if evt.index is None: return None, "Select an item to view details" items = history_manager.get_recent(limit=100) # Filter items same as gallery filtered_items = [] for item in items: if type_filter == "Images" and item.type != "image": continue if type_filter == "Videos" and item.type != "video": continue if Path(item.output_path).exists(): filtered_items.append(item) if evt.index >= len(filtered_items): return None, "Item not found" item = filtered_items[evt.index] # Load images for slider (only for images) slider_images = None if item.type == "image": input_path = Path(item.input_path) output_path = Path(item.output_path) if input_path.exists() and output_path.exists(): slider_images = (str(input_path), str(output_path)) # Format details details = f"**{item.output_filename}**\n\n" details += f"- **Type:** {item.type.title()}\n" details += f"- **Model:** {item.model}\n" details += f"- **Scale:** {item.scale}x\n" details += f"- **Face Enhancement:** {'Yes' if item.face_enhance else 'No'}\n" if item.type == "video" and item.video_codec: details += f"- **Codec:** {item.video_codec}\n" if item.frames_count: details += f"- **Frames:** {item.frames_count}\n" details += f"- **Input:** {item.input_width}x{item.input_height}\n" details += f"- **Output:** {item.output_width}x{item.output_height}\n" details += f"- **Processing Time:** {item.processing_time_seconds:.1f}s\n" details += f"- **Input Size:** {item.input_size_bytes/1024:.1f} KB\n" details += f"- **Output Size:** {item.output_size_bytes/1024/1024:.1f} MB\n" if item.output_size_bytes > 1024*1024 else f"- **Output Size:** {item.output_size_bytes/1024:.1f} KB\n" details += f"- **Created:** {item.created_at.strftime('%Y-%m-%d %H:%M')}" return slider_images, details def delete_selected(evt: gr.SelectData, type_filter: str = "All") -> tuple[list, str]: """Delete selected history item.""" if evt.index is None: return get_history_gallery(type_filter), "No item selected" items = history_manager.get_recent(limit=100) # Filter items same as gallery filtered_items = [] for item in items: if type_filter == "Images" and item.type != "image": continue if type_filter == "Videos" and item.type != "video": continue if Path(item.output_path).exists(): filtered_items.append(item) if evt.index >= len(filtered_items): return get_history_gallery(type_filter), "Item not found" item = filtered_items[evt.index] history_manager.delete_item(item.id) return get_history_gallery(type_filter), f"Deleted: {item.output_filename}" def create_history_tab(): """Create the history gallery tab component.""" with gr.Tab("History", id="history-tab"): gr.Markdown("## Processing History") with gr.Row(): # Search search_box = gr.Textbox( label="Search", placeholder="Search by filename...", scale=3, ) filter_dropdown = gr.Dropdown( choices=["All", "Images", "Videos"], value="All", label="Filter", scale=1, ) refresh_btn = gr.Button("Refresh", variant="secondary", scale=1) with gr.Row(): # Gallery with gr.Column(scale=2): gallery = gr.Gallery( value=get_history_gallery(), label="History", columns=4, object_fit="cover", height=400, allow_preview=True, ) # Details panel with gr.Column(scale=1): stats_display = gr.Markdown( get_history_stats(), elem_classes=["info-card"], ) # Selected item details with gr.Row(): with gr.Column(scale=2): try: from gradio_imageslider import ImageSlider comparison_slider = ImageSlider( label="Before / After", type="filepath", ) except ImportError: comparison_slider = gr.Image( label="Selected Image", type="filepath", ) with gr.Column(scale=1): item_details = gr.Markdown( "Select an item to view details", elem_classes=["info-card"], ) with gr.Row(): download_btn = gr.Button("Download", variant="secondary") delete_btn = gr.Button("Delete", variant="stop") status_text = gr.Markdown("") # Event handlers search_box.change( fn=search_history, inputs=[search_box, filter_dropdown], outputs=[gallery], ) filter_dropdown.change( fn=get_history_gallery, inputs=[filter_dropdown], outputs=[gallery], ) gallery.select( fn=get_item_details, inputs=[filter_dropdown], outputs=[comparison_slider, item_details], ) delete_btn.click( fn=lambda f: (get_history_gallery(f), get_history_stats()), inputs=[filter_dropdown], outputs=[gallery, stats_display], ) refresh_btn.click( fn=lambda f: (get_history_gallery(f), get_history_stats()), inputs=[filter_dropdown], outputs=[gallery, stats_display], ) return { "gallery": gallery, "comparison_slider": comparison_slider, "item_details": item_details, "stats_display": stats_display, "filter_dropdown": filter_dropdown, "refresh_btn": refresh_btn, }