ShutterSearch / app.py
SwikarG's picture
Hide home (#6)
fc7ded2
Raw
History Blame Contribute Delete
11.2 kB
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()