Spaces:
Runtime error
Runtime error
| import os | |
| import gradio as gr | |
| # Import robust business logic operations | |
| from logic import ( | |
| run_ingest, run_search, load_caption_browser, load_collections_view, | |
| update_collections_dropdown, update_search_dropdown, zip_selected_files | |
| ) | |
| from caption_store import entry_count, get_all_collections | |
| # Import modular UI Page builders | |
| from ui.sidebar import render_sidebar | |
| # from ui.home import render_home | |
| from ui.ingest import render_ingest | |
| from ui.search import render_search | |
| from ui.collections import render_collections | |
| from ui.captions import render_captions | |
| from ui.about import render_about | |
| # Load layout parameters from global stylesheet | |
| with open("style.css", "r") as f: | |
| custom_css = f.read() | |
| with gr.Blocks(css=custom_css, title="ShutterSearch — Photo Archive", fill_height=False) as demo: | |
| # 1. Render Navigation Sidebar | |
| nav_btns = render_sidebar() | |
| # 2. Render Main Content Container Pages | |
| with gr.Column(elem_classes="main-content"): | |
| # home_page, start_btn = render_home() | |
| ingest_page, upload, coll_dropdown, use_new_coll, new_coll_name, ingest_btn, ingest_status = render_ingest() | |
| # Search layout with selection returns | |
| ( | |
| search_page, search_query, search_col_filter, search_btn, search_status_msg, search_gallery, | |
| selected_search_paths, loaded_search_paths, search_selection_status, search_download_btn, | |
| search_download_file, search_clear_selection_btn, search_select_all_btn | |
| ) = render_search() | |
| # Collections layout with selection returns | |
| ( | |
| colls_page, view_coll_selector, refresh_coll_btn, coll_status, coll_gallery, | |
| selected_paths, loaded_original_paths, selection_status, download_btn, | |
| download_file, clear_selection_btn, select_all_btn | |
| ) = render_collections() | |
| caps_page, refresh_cap_btn, cap_status, caption_table = render_captions() | |
| about_page = render_about() | |
| # Define page indexes for visibility toggles | |
| # pages = [home_page, ingest_page, search_page, colls_page, caps_page, about_page] | |
| # page_names = ["home", "ingest", "search", "collections", "captions", "about"] | |
| pages = [ingest_page, search_page, colls_page, caps_page, about_page] | |
| page_names = ["ingest", "search", "collections", "captions", "about"] | |
| def switch_page(page_name): | |
| """Generates dynamic updates to manage current visible pages.""" | |
| return [gr.update(visible=(name == page_name)) for name in page_names] | |
| # --- Sidebar Navigation Interactions --- | |
| # nav_btns["home"].click(fn=lambda: switch_page("home"), outputs=pages) | |
| # nav_btns["ingest"].click(fn=lambda: switch_page("ingest"), outputs=pages) | |
| # Safe helpers are invoked here to avoid component value schema mismatch issues | |
| nav_btns["search"].click( | |
| fn=lambda: switch_page("search") + [update_search_dropdown()], | |
| outputs=pages + [search_col_filter] | |
| ) | |
| nav_btns["collections"].click( | |
| fn=lambda: switch_page("collections") + [update_collections_dropdown()], | |
| outputs=pages + [view_coll_selector] | |
| ) | |
| nav_btns["captions"].click(fn=lambda: switch_page("captions"), outputs=pages) | |
| nav_btns["about"].click(fn=lambda: switch_page("about"), outputs=pages) | |
| # start_btn.click(fn=lambda: switch_page("ingest"), outputs=pages) | |
| # --- Ingestion View Handlers --- | |
| def toggle_collection_inputs(is_new): | |
| """Toggles rendering states of manual and standard collections selectors.""" | |
| return ( | |
| gr.update(visible=not is_new), # Hide selector if creating new | |
| gr.update(visible=is_new) # Show text box if creating new | |
| ) | |
| use_new_coll.change( | |
| fn=toggle_collection_inputs, | |
| inputs=use_new_coll, | |
| outputs=[coll_dropdown, new_coll_name] | |
| ) | |
| ingest_btn.click( | |
| fn=run_ingest, | |
| inputs=[upload, coll_dropdown, use_new_coll, new_coll_name], | |
| outputs=[ingest_status, caption_table, coll_dropdown] | |
| ) | |
| # --- Shared Selection Utility Functions --- | |
| def format_selection_status(count, filenames): | |
| """Generates double-linebreak formatted selection statuses to divide output lines.""" | |
| if count == 0: | |
| return "**0** image(s) selected for download packaging.\n\n*No files currently selected.*" | |
| name_string = ", ".join(filenames) | |
| if len(name_string) > 150: | |
| name_string = name_string[:150] + "..." | |
| # Double linebreak forces a paragraph divide onto a separate line | |
| return f"**{count}** image(s) selected for download packaging.\n\n📋 **Selected Files:** `{name_string}`" | |
| def handle_gallery_selection(evt: gr.SelectData, current_selections, raw_paths, gallery_data): | |
| """Toggles visual labels dynamically and outputs double-linebreak reports.""" | |
| clicked_file = raw_paths[evt.index] | |
| if clicked_file in current_selections: | |
| current_selections.remove(clicked_file) | |
| else: | |
| current_selections.append(clicked_file) | |
| updated_gallery = [] | |
| for idx, path in enumerate(raw_paths): | |
| thumb_path = gallery_data[idx][0] | |
| basename = os.path.basename(path) | |
| label = f"✅ {basename}" if path in current_selections else basename | |
| updated_gallery.append((thumb_path, label)) | |
| filenames = [os.path.basename(f) for f in current_selections] | |
| summary = format_selection_status(len(current_selections), filenames) | |
| return current_selections, summary, updated_gallery | |
| def handle_select_all(raw_paths, gallery_data): | |
| """Packages all source paths inside current array list and displays checkmarks.""" | |
| if not raw_paths: | |
| return [], format_selection_status(0, []), [] | |
| current_selections = list(raw_paths) | |
| updated_gallery = [] | |
| for idx, path in enumerate(raw_paths): | |
| thumb_path = gallery_data[idx][0] | |
| basename = os.path.basename(path) | |
| updated_gallery.append((thumb_path, f"✅ {basename}")) | |
| filenames = [os.path.basename(f) for f in current_selections] | |
| summary = format_selection_status(len(current_selections), filenames) | |
| return current_selections, summary, updated_gallery | |
| def clear_selection(raw_paths, gallery_data): | |
| """Flushes storage state lists and clears visual checkmarks from labels.""" | |
| updated_gallery = [] | |
| for idx, path in enumerate(raw_paths): | |
| thumb_path = gallery_data[idx][0] | |
| basename = os.path.basename(path) | |
| updated_gallery.append((thumb_path, basename)) | |
| return [], format_selection_status(0, []), updated_gallery, gr.update(visible=False) | |
| def handle_selected_download(selected_list): | |
| """Compiles specified absolute paths into a downloadable archive container.""" | |
| if not selected_list: | |
| return gr.update(visible=False), "⚠️ Download failure: No selections marked." | |
| file_path, status_msg = zip_selected_files(selected_list) | |
| if file_path: | |
| return gr.update(value=file_path, visible=True), status_msg | |
| return gr.update(visible=False), status_msg | |
| # --- Search View Bindings --- | |
| def trigger_search_load(query, col_filter): | |
| images, original_paths, status = run_search(query, col_filter) | |
| return images, original_paths, [], format_selection_status(0, []), status, gr.update(visible=False) | |
| search_btn.click( | |
| fn=trigger_search_load, | |
| inputs=[search_query, search_col_filter], | |
| outputs=[search_gallery, loaded_search_paths, selected_search_paths, search_selection_status, search_status_msg, search_download_file] | |
| ) | |
| search_query.submit( | |
| fn=trigger_search_load, | |
| inputs=[search_query, search_col_filter], | |
| outputs=[search_gallery, loaded_search_paths, selected_search_paths, search_selection_status, search_status_msg, search_download_file] | |
| ) | |
| # Search Interactive Select Hooks | |
| search_gallery.select( | |
| fn=handle_gallery_selection, | |
| inputs=[selected_search_paths, loaded_search_paths, search_gallery], | |
| outputs=[selected_search_paths, search_selection_status, search_gallery] | |
| ) | |
| search_select_all_btn.click( | |
| fn=handle_select_all, | |
| inputs=[loaded_search_paths, search_gallery], | |
| outputs=[selected_search_paths, search_selection_status, search_gallery] | |
| ) | |
| search_clear_selection_btn.click( | |
| fn=clear_selection, | |
| inputs=[loaded_search_paths, search_gallery], | |
| outputs=[selected_search_paths, search_selection_status, search_gallery, search_download_file] | |
| ) | |
| search_download_btn.click( | |
| fn=handle_selected_download, | |
| inputs=selected_search_paths, | |
| outputs=[search_download_file, search_selection_status] | |
| ) | |
| # --- Collections View Bindings --- | |
| def trigger_collection_load(selected_collection): | |
| images, original_paths, status = load_collections_view(selected_collection) | |
| return images, original_paths, [], format_selection_status(0, []), gr.update(visible=False) | |
| view_coll_selector.change( | |
| fn=trigger_collection_load, | |
| inputs=view_coll_selector, | |
| outputs=[coll_gallery, loaded_original_paths, selected_paths, selection_status, download_file] | |
| ) | |
| refresh_coll_btn.click( | |
| fn=trigger_collection_load, | |
| inputs=view_coll_selector, | |
| outputs=[coll_gallery, loaded_original_paths, selected_paths, selection_status, download_file] | |
| ) | |
| # Collections Interactive Select Hooks | |
| coll_gallery.select( | |
| fn=handle_gallery_selection, | |
| inputs=[selected_paths, loaded_original_paths, coll_gallery], | |
| outputs=[selected_paths, selection_status, coll_gallery] | |
| ) | |
| select_all_btn.click( | |
| fn=handle_select_all, | |
| inputs=[loaded_original_paths, coll_gallery], | |
| outputs=[selected_paths, selection_status, coll_gallery] | |
| ) | |
| clear_selection_btn.click( | |
| fn=clear_selection, | |
| inputs=[loaded_original_paths, coll_gallery], | |
| outputs=[selected_paths, selection_status, coll_gallery, download_file] | |
| ) | |
| download_btn.click( | |
| fn=handle_selected_download, | |
| inputs=selected_paths, | |
| outputs=[download_file, selection_status] | |
| ) | |
| # --- Caption Browser Refresh Bindings --- | |
| refresh_cap_btn.click(fn=load_caption_browser, outputs=[caption_table, cap_status]) | |
| # Initial startup payload loaders | |
| demo.load(fn=load_caption_browser, outputs=[caption_table, cap_status]) | |
| demo.load( | |
| fn=trigger_collection_load, | |
| inputs=view_coll_selector, | |
| outputs=[coll_gallery, loaded_original_paths, selected_paths, selection_status, download_file] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |