Spaces:
Sleeping
Sleeping
update
Browse files- app.py +54 -14
- feedback_viewer.py +121 -48
app.py
CHANGED
|
@@ -52,6 +52,38 @@ if USE_HUGGINGFACE_ZEROGPU:
|
|
| 52 |
logging.warning("Could not import download_models")
|
| 53 |
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
def load_gradio_images_helper(pil_images: Union[List, Image.Image, str]) -> List[Image.Image]:
|
| 56 |
"""
|
| 57 |
Convert various image input formats to a list of PIL Images.
|
|
@@ -120,15 +152,18 @@ def create_vibe_blending_tab():
|
|
| 120 |
with gr.Group():
|
| 121 |
gr.Markdown("**Step 3:** Submit your feedback")
|
| 122 |
rating = gr.Radio(label="How do you like the results?", choices=["1", "2", "3", "4", "5"])
|
| 123 |
-
feedback_form = gr.TextArea(label="Feedback", show_label=
|
|
|
|
| 124 |
feedback_button = gr.Button("Submit Feedback", variant="secondary", size="sm")
|
| 125 |
|
| 126 |
with gr.Column():
|
| 127 |
with gr.Group():
|
| 128 |
gr.Markdown("**Step 2:** Run Vibe Blending")
|
| 129 |
-
with gr.Accordion("Vibe Blending Results", open=False):
|
| 130 |
-
blending_results_grid = gr.Image(label="Grid View", show_label=True, format="png")
|
| 131 |
blending_results = gr.Gallery(label="Gallery View", show_label=False, columns=4, rows=3, interactive=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
blend_button = gr.Button("🔴 Run Vibe Blending", variant="primary")
|
| 133 |
|
| 134 |
def _process_input_images(input1, input2, extra_images, negative_images):
|
|
@@ -156,28 +191,32 @@ def create_vibe_blending_tab():
|
|
| 156 |
alpha_weights = np.linspace(alpha_start, alpha_end, n_steps+2)[1:-1].tolist()
|
| 157 |
blended_images_list = run_vibe_blend_not_safe(input1, input2, extra_images, negative_images, DEFAULT_CONFIG_PATH, alpha_weights)
|
| 158 |
blended_images_grid = create_image_grid(blended_images_list, rows=np.ceil(len(blended_images_list)/4).astype(int), cols=4)
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
-
blend_button.click(blend_button_click, inputs=[input1, input2, extra_images, negative_images, alpha_start, alpha_end, n_steps], outputs=[blending_results_grid, blending_results])
|
| 162 |
|
| 163 |
-
def feedback_button_click(rating, feedback_form, input1, input2, extra_images, negative_images, alpha_start, alpha_end, n_steps, blending_results):
|
| 164 |
"""Handle feedback submission and store to Hugging Face Dataset."""
|
| 165 |
if not rating:
|
| 166 |
gr.Warning("Please select a rating before submitting feedback.")
|
| 167 |
-
return gr.update(value=None), gr.update(value="")
|
| 168 |
|
| 169 |
# Validate that required images exist
|
| 170 |
if input1 is None:
|
| 171 |
gr.Warning("Please upload Input 1 image before submitting feedback.")
|
| 172 |
-
return gr.update(value=rating), gr.update(value=feedback_form)
|
| 173 |
|
| 174 |
if input2 is None:
|
| 175 |
gr.Warning("Please upload Input 2 image before submitting feedback.")
|
| 176 |
-
return gr.update(value=rating), gr.update(value=feedback_form)
|
| 177 |
|
| 178 |
if blending_results is None or len(blending_results) == 0:
|
| 179 |
gr.Warning("Please run vibe blending first to generate results before submitting feedback.")
|
| 180 |
-
return gr.update(value=rating), gr.update(value=feedback_form)
|
| 181 |
|
| 182 |
# Process images to check if they exist
|
| 183 |
input1_img, input2_img, extra_images_processed, negative_images_processed = _process_input_images(
|
|
@@ -205,11 +244,12 @@ def create_vibe_blending_tab():
|
|
| 205 |
extra_images=extra_images_processed if extra_images_processed else None,
|
| 206 |
negative_images=negative_images_processed if negative_images_processed else None,
|
| 207 |
blending_result_images=blending_result_images, # Upload list of images
|
|
|
|
| 208 |
)
|
| 209 |
|
| 210 |
if success:
|
| 211 |
gr.Info("Thank you! Your feedback has been submitted successfully.")
|
| 212 |
-
return gr.update(value=None), gr.update(value="") # Reset rating
|
| 213 |
else:
|
| 214 |
error_msg = "Feedback could not be stored. "
|
| 215 |
if not HF_FEEDBACK_DATASET_REPO:
|
|
@@ -217,12 +257,12 @@ def create_vibe_blending_tab():
|
|
| 217 |
else:
|
| 218 |
error_msg += "Please check the logs for details."
|
| 219 |
gr.Warning(error_msg)
|
| 220 |
-
return gr.update(value=rating), gr.update(value=feedback_form) # Keep rating
|
| 221 |
|
| 222 |
feedback_button.click(
|
| 223 |
feedback_button_click,
|
| 224 |
-
inputs=[rating, feedback_form, input1, input2, extra_images, negative_images, alpha_start, alpha_end, n_steps, blending_results],
|
| 225 |
-
outputs=[rating, feedback_form] # Reset rating
|
| 226 |
)
|
| 227 |
|
| 228 |
example_cases = [
|
|
|
|
| 52 |
logging.warning("Could not import download_models")
|
| 53 |
|
| 54 |
|
| 55 |
+
def create_gif_from_images(images: List[Image.Image], fps: float = 3.0) -> str:
|
| 56 |
+
"""
|
| 57 |
+
Create a GIF from a list of PIL Images.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
images: List of PIL Images to combine into a GIF
|
| 61 |
+
fps: Frames per second for the GIF (default: 3.0)
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
Path to the temporary GIF file
|
| 65 |
+
"""
|
| 66 |
+
if not images:
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
# Calculate duration in milliseconds (1000ms / fps)
|
| 70 |
+
duration_ms = int(1000 / fps)
|
| 71 |
+
|
| 72 |
+
# Create a temporary file for the GIF
|
| 73 |
+
gif_path = os.path.join(tempfile.gettempdir(), f"vibe_blend_{uuid.uuid4().hex}.gif")
|
| 74 |
+
|
| 75 |
+
# Save as GIF with loop
|
| 76 |
+
images[0].save(
|
| 77 |
+
gif_path,
|
| 78 |
+
save_all=True,
|
| 79 |
+
append_images=images[1:],
|
| 80 |
+
duration=duration_ms,
|
| 81 |
+
loop=0 # 0 = infinite loop
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
return gif_path
|
| 85 |
+
|
| 86 |
+
|
| 87 |
def load_gradio_images_helper(pil_images: Union[List, Image.Image, str]) -> List[Image.Image]:
|
| 88 |
"""
|
| 89 |
Convert various image input formats to a list of PIL Images.
|
|
|
|
| 152 |
with gr.Group():
|
| 153 |
gr.Markdown("**Step 3:** Submit your feedback")
|
| 154 |
rating = gr.Radio(label="How do you like the results?", choices=["1", "2", "3", "4", "5"])
|
| 155 |
+
feedback_form = gr.TextArea(label="Feedback (optional)", show_label=True, lines=1, placeholder="Enter your feedback here...")
|
| 156 |
+
make_public = gr.Checkbox(label="Make feedback public", value=True, info="Allow this feedback to be visible in the public feedback viewer")
|
| 157 |
feedback_button = gr.Button("Submit Feedback", variant="secondary", size="sm")
|
| 158 |
|
| 159 |
with gr.Column():
|
| 160 |
with gr.Group():
|
| 161 |
gr.Markdown("**Step 2:** Run Vibe Blending")
|
|
|
|
|
|
|
| 162 |
blending_results = gr.Gallery(label="Gallery View", show_label=False, columns=4, rows=3, interactive=False)
|
| 163 |
+
with gr.Accordion("Vibe Blending Results (grid view)", open=False):
|
| 164 |
+
blending_results_grid = gr.Image(label="Grid View", show_label=True, format="png", interactive=False)
|
| 165 |
+
with gr.Accordion("Vibe Blending Results (GIF view)", open=False):
|
| 166 |
+
blending_results_gif = gr.Image(label="GIF View", show_label=True, format="gif", interactive=False)
|
| 167 |
blend_button = gr.Button("🔴 Run Vibe Blending", variant="primary")
|
| 168 |
|
| 169 |
def _process_input_images(input1, input2, extra_images, negative_images):
|
|
|
|
| 191 |
alpha_weights = np.linspace(alpha_start, alpha_end, n_steps+2)[1:-1].tolist()
|
| 192 |
blended_images_list = run_vibe_blend_not_safe(input1, input2, extra_images, negative_images, DEFAULT_CONFIG_PATH, alpha_weights)
|
| 193 |
blended_images_grid = create_image_grid(blended_images_list, rows=np.ceil(len(blended_images_list)/4).astype(int), cols=4)
|
| 194 |
+
|
| 195 |
+
# Create GIF at 3 frames per second
|
| 196 |
+
gif_path = create_gif_from_images(blended_images_list, fps=3.0)
|
| 197 |
+
|
| 198 |
+
return blended_images_grid, blended_images_list, gif_path # Return grid, list, and GIF for display
|
| 199 |
|
| 200 |
+
blend_button.click(blend_button_click, inputs=[input1, input2, extra_images, negative_images, alpha_start, alpha_end, n_steps], outputs=[blending_results_grid, blending_results, blending_results_gif])
|
| 201 |
|
| 202 |
+
def feedback_button_click(rating, feedback_form, make_public, input1, input2, extra_images, negative_images, alpha_start, alpha_end, n_steps, blending_results):
|
| 203 |
"""Handle feedback submission and store to Hugging Face Dataset."""
|
| 204 |
if not rating:
|
| 205 |
gr.Warning("Please select a rating before submitting feedback.")
|
| 206 |
+
return gr.update(value=None), gr.update(value=""), gr.update(value=True)
|
| 207 |
|
| 208 |
# Validate that required images exist
|
| 209 |
if input1 is None:
|
| 210 |
gr.Warning("Please upload Input 1 image before submitting feedback.")
|
| 211 |
+
return gr.update(value=rating), gr.update(value=feedback_form), gr.update(value=make_public)
|
| 212 |
|
| 213 |
if input2 is None:
|
| 214 |
gr.Warning("Please upload Input 2 image before submitting feedback.")
|
| 215 |
+
return gr.update(value=rating), gr.update(value=feedback_form), gr.update(value=make_public)
|
| 216 |
|
| 217 |
if blending_results is None or len(blending_results) == 0:
|
| 218 |
gr.Warning("Please run vibe blending first to generate results before submitting feedback.")
|
| 219 |
+
return gr.update(value=rating), gr.update(value=feedback_form), gr.update(value=make_public)
|
| 220 |
|
| 221 |
# Process images to check if they exist
|
| 222 |
input1_img, input2_img, extra_images_processed, negative_images_processed = _process_input_images(
|
|
|
|
| 244 |
extra_images=extra_images_processed if extra_images_processed else None,
|
| 245 |
negative_images=negative_images_processed if negative_images_processed else None,
|
| 246 |
blending_result_images=blending_result_images, # Upload list of images
|
| 247 |
+
is_public=make_public, # Pass the public flag
|
| 248 |
)
|
| 249 |
|
| 250 |
if success:
|
| 251 |
gr.Info("Thank you! Your feedback has been submitted successfully.")
|
| 252 |
+
return gr.update(value=None), gr.update(value=""), gr.update(value=True) # Reset rating, feedback form, and checkbox
|
| 253 |
else:
|
| 254 |
error_msg = "Feedback could not be stored. "
|
| 255 |
if not HF_FEEDBACK_DATASET_REPO:
|
|
|
|
| 257 |
else:
|
| 258 |
error_msg += "Please check the logs for details."
|
| 259 |
gr.Warning(error_msg)
|
| 260 |
+
return gr.update(value=rating), gr.update(value=feedback_form), gr.update(value=make_public) # Keep rating, feedback form, and checkbox
|
| 261 |
|
| 262 |
feedback_button.click(
|
| 263 |
feedback_button_click,
|
| 264 |
+
inputs=[rating, feedback_form, make_public, input1, input2, extra_images, negative_images, alpha_start, alpha_end, n_steps, blending_results],
|
| 265 |
+
outputs=[rating, feedback_form, make_public] # Reset rating, feedback form, and checkbox
|
| 266 |
)
|
| 267 |
|
| 268 |
example_cases = [
|
feedback_viewer.py
CHANGED
|
@@ -47,6 +47,7 @@ def store_feedback_to_hf_dataset(
|
|
| 47 |
extra_images: Optional[List[Image.Image]],
|
| 48 |
negative_images: Optional[List[Image.Image]],
|
| 49 |
blending_result_images: Optional[List[Image.Image]],
|
|
|
|
| 50 |
dataset_repo: Optional[str] = None,
|
| 51 |
token: Optional[str] = None
|
| 52 |
) -> bool:
|
|
@@ -67,6 +68,7 @@ def store_feedback_to_hf_dataset(
|
|
| 67 |
extra_images: List of extra images (PIL Images)
|
| 68 |
negative_images: List of negative images (PIL Images)
|
| 69 |
blending_result_images: List of blending result images (PIL Images)
|
|
|
|
| 70 |
dataset_repo: Hugging Face dataset repository (username/dataset-name)
|
| 71 |
token: Hugging Face token (if None, will try to use HF_TOKEN env var)
|
| 72 |
|
|
@@ -127,6 +129,7 @@ def store_feedback_to_hf_dataset(
|
|
| 127 |
"alpha_start": Value("float64"),
|
| 128 |
"alpha_end": Value("float64"),
|
| 129 |
"n_steps": Value("int64"),
|
|
|
|
| 130 |
"input1": ImageFeature(),
|
| 131 |
"input2": ImageFeature(),
|
| 132 |
"extra_images": Sequence(ImageFeature()),
|
|
@@ -156,6 +159,7 @@ def store_feedback_to_hf_dataset(
|
|
| 156 |
"alpha_start": float(alpha_start),
|
| 157 |
"alpha_end": float(alpha_end),
|
| 158 |
"n_steps": int(n_steps),
|
|
|
|
| 159 |
"input1": prepare_image(input1_image),
|
| 160 |
"input2": prepare_image(input2_image),
|
| 161 |
"extra_images": [prepare_image(img) for img in (extra_images or []) if prepare_image(img) is not None],
|
|
@@ -180,15 +184,34 @@ def store_feedback_to_hf_dataset(
|
|
| 180 |
return example
|
| 181 |
existing_dataset = existing_dataset.map(add_uuid)
|
| 182 |
|
| 183 |
-
#
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
try:
|
| 186 |
existing_features = existing_dataset.features
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
if "uuid" not in existing_features:
|
| 188 |
# Create new features dict with UUID added
|
| 189 |
-
from datasets import Features as DatasetFeatures
|
| 190 |
-
updated_features_dict = dict(existing_features)
|
| 191 |
updated_features_dict["uuid"] = Value("string")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
updated_features = DatasetFeatures(updated_features_dict)
|
| 193 |
existing_dataset = existing_dataset.cast(updated_features)
|
| 194 |
|
|
@@ -515,7 +538,8 @@ def load_feedback_from_hf_dataset(
|
|
| 515 |
dataset_repo: Optional[str] = None,
|
| 516 |
token: Optional[str] = None,
|
| 517 |
limit: Optional[int] = None,
|
| 518 |
-
reverse: bool = False
|
|
|
|
| 519 |
) -> List[dict]:
|
| 520 |
"""
|
| 521 |
Load feedback entries from a Hugging Face Dataset.
|
|
@@ -525,6 +549,8 @@ def load_feedback_from_hf_dataset(
|
|
| 525 |
token: Hugging Face token (if None, will try to use HF_TOKEN env var)
|
| 526 |
limit: Maximum number of entries to return (None for all)
|
| 527 |
reverse: If True, reverse the order (newest first). Default False (oldest first).
|
|
|
|
|
|
|
| 528 |
|
| 529 |
Returns:
|
| 530 |
List of feedback entries as dictionaries
|
|
@@ -621,6 +647,14 @@ def load_feedback_from_hf_dataset(
|
|
| 621 |
if "uuid" not in entry_dict or not entry_dict.get("uuid"):
|
| 622 |
entry_dict["uuid"] = str(uuid.uuid4())
|
| 623 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
# Convert Image objects to PIL Images using the raw accessed values
|
| 625 |
# ImageFeature objects need to be converted to PIL Images for display
|
| 626 |
try:
|
|
@@ -814,30 +848,61 @@ def create_feedback_viewer_tab():
|
|
| 814 |
|
| 815 |
selected_details = gr.JSON(label="Full Feedback Details", visible=False)
|
| 816 |
|
| 817 |
-
# Admin
|
| 818 |
with gr.Accordion("⋮", open=False):
|
|
|
|
| 819 |
with gr.Row():
|
| 820 |
-
|
| 821 |
-
label="UUID to Delete",
|
| 822 |
-
placeholder="Enter UUID or prefix (e.g. e7132a33)",
|
| 823 |
-
value="",
|
| 824 |
-
scale=2
|
| 825 |
-
)
|
| 826 |
-
delete_password_input = gr.Textbox(
|
| 827 |
label="Admin Password",
|
| 828 |
placeholder="Enter admin password",
|
| 829 |
type="password",
|
| 830 |
value="",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 831 |
scale=1
|
| 832 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 833 |
delete_button = gr.Button("🗑️ Delete", variant="stop", scale=1)
|
| 834 |
delete_status = gr.Markdown("")
|
| 835 |
|
| 836 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 837 |
"""Delete a feedback entry by UUID after password verification."""
|
| 838 |
# Check password
|
| 839 |
-
if
|
| 840 |
-
return "❌ Invalid admin password."
|
| 841 |
|
| 842 |
if not uuid_to_delete or not uuid_to_delete.strip():
|
| 843 |
return "❌ Please enter a UUID to delete."
|
|
@@ -945,7 +1010,7 @@ def create_feedback_viewer_tab():
|
|
| 945 |
|
| 946 |
delete_button.click(
|
| 947 |
delete_entry_by_uuid,
|
| 948 |
-
inputs=[delete_uuid_input,
|
| 949 |
outputs=[delete_status]
|
| 950 |
)
|
| 951 |
|
|
@@ -981,12 +1046,13 @@ def create_feedback_viewer_tab():
|
|
| 981 |
|
| 982 |
return None
|
| 983 |
|
| 984 |
-
def load_and_display_feedback(items_per_page, page, sort_order, uuid_search="", timestamp_start="", timestamp_end="", rating_filter="All", filter_extra=False, filter_negative=False):
|
| 985 |
"""Load feedback from dataset and format as HTML table with pagination."""
|
| 986 |
# Convert radio selection to reverse boolean
|
| 987 |
sort_reverse = (sort_order == "New to Old")
|
| 988 |
# Load all feedback entries (no limit, we'll paginate ourselves)
|
| 989 |
-
|
|
|
|
| 990 |
|
| 991 |
# Filter by UUID if search term provided
|
| 992 |
if uuid_search and uuid_search.strip():
|
|
@@ -1310,86 +1376,93 @@ def create_feedback_viewer_tab():
|
|
| 1310 |
return html_content, page, f"**Total Pages:** {total_pages}"
|
| 1311 |
|
| 1312 |
# Refresh button - loads first page
|
| 1313 |
-
def refresh_feedback(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative):
|
| 1314 |
-
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1315 |
|
| 1316 |
refresh_button.click(
|
| 1317 |
refresh_feedback,
|
| 1318 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1319 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1320 |
)
|
| 1321 |
|
| 1322 |
# Sort order radio - reset to page 1 when sort order changes
|
| 1323 |
-
def on_sort_order_change(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative):
|
| 1324 |
# Reset to page 1 when sort order changes
|
| 1325 |
-
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1326 |
|
| 1327 |
sort_order_radio.change(
|
| 1328 |
on_sort_order_change,
|
| 1329 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1330 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1331 |
)
|
| 1332 |
|
| 1333 |
# Search button
|
| 1334 |
-
def on_search(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative):
|
| 1335 |
-
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1336 |
|
| 1337 |
search_button.click(
|
| 1338 |
on_search,
|
| 1339 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1340 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1341 |
)
|
| 1342 |
|
| 1343 |
# Rating filter change
|
| 1344 |
rating_filter.change(
|
| 1345 |
on_search,
|
| 1346 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1347 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1348 |
)
|
| 1349 |
|
| 1350 |
# Checkbox changes
|
| 1351 |
filter_extra_images.change(
|
| 1352 |
on_search,
|
| 1353 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1354 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1355 |
)
|
| 1356 |
|
| 1357 |
filter_negative_images.change(
|
| 1358 |
on_search,
|
| 1359 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1360 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1361 |
)
|
| 1362 |
|
| 1363 |
|
| 1364 |
# Pagination controls
|
| 1365 |
-
def on_page_change(items_per_page, page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative):
|
| 1366 |
-
return load_and_display_feedback(items_per_page, page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1367 |
|
| 1368 |
-
def on_items_per_page_change(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative):
|
| 1369 |
-
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1370 |
|
| 1371 |
page_number.change(
|
| 1372 |
on_page_change,
|
| 1373 |
-
inputs=[items_per_page_slider, page_number, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1374 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1375 |
)
|
| 1376 |
|
| 1377 |
items_per_page_slider.change(
|
| 1378 |
on_items_per_page_change,
|
| 1379 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1380 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1381 |
)
|
| 1382 |
|
| 1383 |
# Previous/Next page buttons
|
| 1384 |
-
def go_to_previous_page(items_per_page, current_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative):
|
| 1385 |
new_page = max(1, int(current_page) - 1)
|
| 1386 |
-
return load_and_display_feedback(items_per_page, new_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1387 |
|
| 1388 |
-
def go_to_next_page(items_per_page, current_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative):
|
| 1389 |
# Convert radio selection to reverse boolean
|
| 1390 |
sort_reverse = (sort_order == "New to Old")
|
| 1391 |
# Load all feedbacks to calculate total pages (with filters if applicable)
|
| 1392 |
-
all_feedbacks = load_feedback_from_hf_dataset(reverse=sort_reverse)
|
| 1393 |
|
| 1394 |
# Apply UUID filter if search term provided
|
| 1395 |
if uuid_search and uuid_search.strip():
|
|
@@ -1449,42 +1522,42 @@ def create_feedback_viewer_tab():
|
|
| 1449 |
]
|
| 1450 |
|
| 1451 |
if not all_feedbacks:
|
| 1452 |
-
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1453 |
|
| 1454 |
total_items = len(all_feedbacks)
|
| 1455 |
items_per_page = max(1, int(items_per_page))
|
| 1456 |
total_pages = max(1, (total_items + items_per_page - 1) // items_per_page)
|
| 1457 |
new_page = min(total_pages, int(current_page) + 1)
|
| 1458 |
-
return load_and_display_feedback(items_per_page, new_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative)
|
| 1459 |
|
| 1460 |
prev_page_button.click(
|
| 1461 |
go_to_previous_page,
|
| 1462 |
-
inputs=[items_per_page_slider, page_number, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1463 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1464 |
)
|
| 1465 |
|
| 1466 |
next_page_button.click(
|
| 1467 |
go_to_next_page,
|
| 1468 |
-
inputs=[items_per_page_slider, page_number, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1469 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1470 |
)
|
| 1471 |
|
| 1472 |
# Allow Enter key to trigger search
|
| 1473 |
uuid_search_input.submit(
|
| 1474 |
on_search,
|
| 1475 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1476 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1477 |
)
|
| 1478 |
|
| 1479 |
timestamp_start_input.submit(
|
| 1480 |
on_search,
|
| 1481 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1482 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1483 |
)
|
| 1484 |
|
| 1485 |
timestamp_end_input.submit(
|
| 1486 |
on_search,
|
| 1487 |
-
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images],
|
| 1488 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1489 |
)
|
| 1490 |
|
|
|
|
| 47 |
extra_images: Optional[List[Image.Image]],
|
| 48 |
negative_images: Optional[List[Image.Image]],
|
| 49 |
blending_result_images: Optional[List[Image.Image]],
|
| 50 |
+
is_public: bool = True,
|
| 51 |
dataset_repo: Optional[str] = None,
|
| 52 |
token: Optional[str] = None
|
| 53 |
) -> bool:
|
|
|
|
| 68 |
extra_images: List of extra images (PIL Images)
|
| 69 |
negative_images: List of negative images (PIL Images)
|
| 70 |
blending_result_images: List of blending result images (PIL Images)
|
| 71 |
+
is_public: Whether the feedback should be publicly visible (default True)
|
| 72 |
dataset_repo: Hugging Face dataset repository (username/dataset-name)
|
| 73 |
token: Hugging Face token (if None, will try to use HF_TOKEN env var)
|
| 74 |
|
|
|
|
| 129 |
"alpha_start": Value("float64"),
|
| 130 |
"alpha_end": Value("float64"),
|
| 131 |
"n_steps": Value("int64"),
|
| 132 |
+
"is_public": Value("bool"), # Whether feedback is publicly visible
|
| 133 |
"input1": ImageFeature(),
|
| 134 |
"input2": ImageFeature(),
|
| 135 |
"extra_images": Sequence(ImageFeature()),
|
|
|
|
| 159 |
"alpha_start": float(alpha_start),
|
| 160 |
"alpha_end": float(alpha_end),
|
| 161 |
"n_steps": int(n_steps),
|
| 162 |
+
"is_public": bool(is_public), # Store public visibility flag
|
| 163 |
"input1": prepare_image(input1_image),
|
| 164 |
"input2": prepare_image(input2_image),
|
| 165 |
"extra_images": [prepare_image(img) for img in (extra_images or []) if prepare_image(img) is not None],
|
|
|
|
| 184 |
return example
|
| 185 |
existing_dataset = existing_dataset.map(add_uuid)
|
| 186 |
|
| 187 |
+
# Check if existing dataset has is_public field, if not add it (default to True for old entries)
|
| 188 |
+
if "is_public" not in existing_dataset.column_names:
|
| 189 |
+
logging.info("Existing dataset missing is_public field, adding is_public=True to existing entries")
|
| 190 |
+
def add_is_public(example):
|
| 191 |
+
if "is_public" not in example:
|
| 192 |
+
example["is_public"] = True
|
| 193 |
+
return example
|
| 194 |
+
existing_dataset = existing_dataset.map(add_is_public)
|
| 195 |
+
|
| 196 |
+
# Ensure the existing dataset has the UUID and is_public fields in its schema
|
| 197 |
+
# Add fields to schema if missing, then cast to match new schema
|
| 198 |
try:
|
| 199 |
existing_features = existing_dataset.features
|
| 200 |
+
from datasets import Features as DatasetFeatures
|
| 201 |
+
updated_features_dict = dict(existing_features)
|
| 202 |
+
schema_updated = False
|
| 203 |
+
|
| 204 |
if "uuid" not in existing_features:
|
| 205 |
# Create new features dict with UUID added
|
|
|
|
|
|
|
| 206 |
updated_features_dict["uuid"] = Value("string")
|
| 207 |
+
schema_updated = True
|
| 208 |
+
|
| 209 |
+
if "is_public" not in existing_features:
|
| 210 |
+
# Add is_public field to schema
|
| 211 |
+
updated_features_dict["is_public"] = Value("bool")
|
| 212 |
+
schema_updated = True
|
| 213 |
+
|
| 214 |
+
if schema_updated:
|
| 215 |
updated_features = DatasetFeatures(updated_features_dict)
|
| 216 |
existing_dataset = existing_dataset.cast(updated_features)
|
| 217 |
|
|
|
|
| 538 |
dataset_repo: Optional[str] = None,
|
| 539 |
token: Optional[str] = None,
|
| 540 |
limit: Optional[int] = None,
|
| 541 |
+
reverse: bool = False,
|
| 542 |
+
public_only: bool = True
|
| 543 |
) -> List[dict]:
|
| 544 |
"""
|
| 545 |
Load feedback entries from a Hugging Face Dataset.
|
|
|
|
| 549 |
token: Hugging Face token (if None, will try to use HF_TOKEN env var)
|
| 550 |
limit: Maximum number of entries to return (None for all)
|
| 551 |
reverse: If True, reverse the order (newest first). Default False (oldest first).
|
| 552 |
+
public_only: If True, only return public feedback entries. Default True.
|
| 553 |
+
Old entries without is_public field are treated as public.
|
| 554 |
|
| 555 |
Returns:
|
| 556 |
List of feedback entries as dictionaries
|
|
|
|
| 647 |
if "uuid" not in entry_dict or not entry_dict.get("uuid"):
|
| 648 |
entry_dict["uuid"] = str(uuid.uuid4())
|
| 649 |
|
| 650 |
+
# Add is_public if missing (backward compatibility - old entries are public by default)
|
| 651 |
+
if "is_public" not in entry_dict:
|
| 652 |
+
entry_dict["is_public"] = True
|
| 653 |
+
|
| 654 |
+
# Filter by public_only if requested
|
| 655 |
+
if public_only and not entry_dict.get("is_public", True):
|
| 656 |
+
continue
|
| 657 |
+
|
| 658 |
# Convert Image objects to PIL Images using the raw accessed values
|
| 659 |
# ImageFeature objects need to be converted to PIL Images for display
|
| 660 |
try:
|
|
|
|
| 848 |
|
| 849 |
selected_details = gr.JSON(label="Full Feedback Details", visible=False)
|
| 850 |
|
| 851 |
+
# Admin section - hidden behind accordion
|
| 852 |
with gr.Accordion("⋮", open=False):
|
| 853 |
+
gr.Markdown("### Admin Options")
|
| 854 |
with gr.Row():
|
| 855 |
+
admin_password_input = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 856 |
label="Admin Password",
|
| 857 |
placeholder="Enter admin password",
|
| 858 |
type="password",
|
| 859 |
value="",
|
| 860 |
+
scale=2
|
| 861 |
+
)
|
| 862 |
+
include_private_checkbox = gr.Checkbox(
|
| 863 |
+
label="Include private feedbacks",
|
| 864 |
+
value=False,
|
| 865 |
+
interactive=False,
|
| 866 |
scale=1
|
| 867 |
)
|
| 868 |
+
verify_password_button = gr.Button("🔓 Verify", variant="secondary", scale=1)
|
| 869 |
+
admin_status = gr.Markdown("")
|
| 870 |
+
|
| 871 |
+
gr.Markdown("### Delete Entry")
|
| 872 |
+
with gr.Row():
|
| 873 |
+
delete_uuid_input = gr.Textbox(
|
| 874 |
+
label="UUID to Delete",
|
| 875 |
+
placeholder="Enter UUID or prefix (e.g. e7132a33)",
|
| 876 |
+
value="",
|
| 877 |
+
scale=3
|
| 878 |
+
)
|
| 879 |
delete_button = gr.Button("🗑️ Delete", variant="stop", scale=1)
|
| 880 |
delete_status = gr.Markdown("")
|
| 881 |
|
| 882 |
+
def verify_admin_password(password: str, current_include_private: bool):
|
| 883 |
+
"""Verify admin password and enable/disable private feedback checkbox."""
|
| 884 |
+
if password == "admin":
|
| 885 |
+
return (
|
| 886 |
+
gr.update(value=True, interactive=True), # Enable and check the checkbox
|
| 887 |
+
"✅ Admin access granted. You can now view private feedbacks."
|
| 888 |
+
)
|
| 889 |
+
else:
|
| 890 |
+
return (
|
| 891 |
+
gr.update(value=False, interactive=False), # Disable and uncheck the checkbox
|
| 892 |
+
"❌ Invalid password. Private feedbacks hidden."
|
| 893 |
+
)
|
| 894 |
+
|
| 895 |
+
verify_password_button.click(
|
| 896 |
+
verify_admin_password,
|
| 897 |
+
inputs=[admin_password_input, include_private_checkbox],
|
| 898 |
+
outputs=[include_private_checkbox, admin_status]
|
| 899 |
+
)
|
| 900 |
+
|
| 901 |
+
def delete_entry_by_uuid(uuid_to_delete: str, admin_password: str):
|
| 902 |
"""Delete a feedback entry by UUID after password verification."""
|
| 903 |
# Check password
|
| 904 |
+
if admin_password != "admin":
|
| 905 |
+
return "❌ Invalid admin password. Enter password and click Verify first."
|
| 906 |
|
| 907 |
if not uuid_to_delete or not uuid_to_delete.strip():
|
| 908 |
return "❌ Please enter a UUID to delete."
|
|
|
|
| 1010 |
|
| 1011 |
delete_button.click(
|
| 1012 |
delete_entry_by_uuid,
|
| 1013 |
+
inputs=[delete_uuid_input, admin_password_input],
|
| 1014 |
outputs=[delete_status]
|
| 1015 |
)
|
| 1016 |
|
|
|
|
| 1046 |
|
| 1047 |
return None
|
| 1048 |
|
| 1049 |
+
def load_and_display_feedback(items_per_page, page, sort_order, uuid_search="", timestamp_start="", timestamp_end="", rating_filter="All", filter_extra=False, filter_negative=False, include_private=False):
|
| 1050 |
"""Load feedback from dataset and format as HTML table with pagination."""
|
| 1051 |
# Convert radio selection to reverse boolean
|
| 1052 |
sort_reverse = (sort_order == "New to Old")
|
| 1053 |
# Load all feedback entries (no limit, we'll paginate ourselves)
|
| 1054 |
+
# If include_private is True, set public_only to False
|
| 1055 |
+
all_feedbacks = load_feedback_from_hf_dataset(reverse=sort_reverse, public_only=not include_private)
|
| 1056 |
|
| 1057 |
# Filter by UUID if search term provided
|
| 1058 |
if uuid_search and uuid_search.strip():
|
|
|
|
| 1376 |
return html_content, page, f"**Total Pages:** {total_pages}"
|
| 1377 |
|
| 1378 |
# Refresh button - loads first page
|
| 1379 |
+
def refresh_feedback(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private):
|
| 1380 |
+
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1381 |
|
| 1382 |
refresh_button.click(
|
| 1383 |
refresh_feedback,
|
| 1384 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1385 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1386 |
)
|
| 1387 |
|
| 1388 |
# Sort order radio - reset to page 1 when sort order changes
|
| 1389 |
+
def on_sort_order_change(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private):
|
| 1390 |
# Reset to page 1 when sort order changes
|
| 1391 |
+
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1392 |
|
| 1393 |
sort_order_radio.change(
|
| 1394 |
on_sort_order_change,
|
| 1395 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1396 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1397 |
)
|
| 1398 |
|
| 1399 |
# Search button
|
| 1400 |
+
def on_search(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private):
|
| 1401 |
+
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1402 |
|
| 1403 |
search_button.click(
|
| 1404 |
on_search,
|
| 1405 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1406 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1407 |
)
|
| 1408 |
|
| 1409 |
# Rating filter change
|
| 1410 |
rating_filter.change(
|
| 1411 |
on_search,
|
| 1412 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1413 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1414 |
)
|
| 1415 |
|
| 1416 |
# Checkbox changes
|
| 1417 |
filter_extra_images.change(
|
| 1418 |
on_search,
|
| 1419 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1420 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1421 |
)
|
| 1422 |
|
| 1423 |
filter_negative_images.change(
|
| 1424 |
on_search,
|
| 1425 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1426 |
+
outputs=[feedback_html, page_number, total_pages_display]
|
| 1427 |
+
)
|
| 1428 |
+
|
| 1429 |
+
# Include private checkbox change - refresh when toggled
|
| 1430 |
+
include_private_checkbox.change(
|
| 1431 |
+
on_search,
|
| 1432 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1433 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1434 |
)
|
| 1435 |
|
| 1436 |
|
| 1437 |
# Pagination controls
|
| 1438 |
+
def on_page_change(items_per_page, page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private):
|
| 1439 |
+
return load_and_display_feedback(items_per_page, page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1440 |
|
| 1441 |
+
def on_items_per_page_change(items_per_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private):
|
| 1442 |
+
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1443 |
|
| 1444 |
page_number.change(
|
| 1445 |
on_page_change,
|
| 1446 |
+
inputs=[items_per_page_slider, page_number, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1447 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1448 |
)
|
| 1449 |
|
| 1450 |
items_per_page_slider.change(
|
| 1451 |
on_items_per_page_change,
|
| 1452 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1453 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1454 |
)
|
| 1455 |
|
| 1456 |
# Previous/Next page buttons
|
| 1457 |
+
def go_to_previous_page(items_per_page, current_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private):
|
| 1458 |
new_page = max(1, int(current_page) - 1)
|
| 1459 |
+
return load_and_display_feedback(items_per_page, new_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1460 |
|
| 1461 |
+
def go_to_next_page(items_per_page, current_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private):
|
| 1462 |
# Convert radio selection to reverse boolean
|
| 1463 |
sort_reverse = (sort_order == "New to Old")
|
| 1464 |
# Load all feedbacks to calculate total pages (with filters if applicable)
|
| 1465 |
+
all_feedbacks = load_feedback_from_hf_dataset(reverse=sort_reverse, public_only=not include_private)
|
| 1466 |
|
| 1467 |
# Apply UUID filter if search term provided
|
| 1468 |
if uuid_search and uuid_search.strip():
|
|
|
|
| 1522 |
]
|
| 1523 |
|
| 1524 |
if not all_feedbacks:
|
| 1525 |
+
return load_and_display_feedback(items_per_page, 1, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1526 |
|
| 1527 |
total_items = len(all_feedbacks)
|
| 1528 |
items_per_page = max(1, int(items_per_page))
|
| 1529 |
total_pages = max(1, (total_items + items_per_page - 1) // items_per_page)
|
| 1530 |
new_page = min(total_pages, int(current_page) + 1)
|
| 1531 |
+
return load_and_display_feedback(items_per_page, new_page, sort_order, uuid_search, timestamp_start, timestamp_end, rating_filter, filter_extra, filter_negative, include_private)
|
| 1532 |
|
| 1533 |
prev_page_button.click(
|
| 1534 |
go_to_previous_page,
|
| 1535 |
+
inputs=[items_per_page_slider, page_number, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1536 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1537 |
)
|
| 1538 |
|
| 1539 |
next_page_button.click(
|
| 1540 |
go_to_next_page,
|
| 1541 |
+
inputs=[items_per_page_slider, page_number, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1542 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1543 |
)
|
| 1544 |
|
| 1545 |
# Allow Enter key to trigger search
|
| 1546 |
uuid_search_input.submit(
|
| 1547 |
on_search,
|
| 1548 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1549 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1550 |
)
|
| 1551 |
|
| 1552 |
timestamp_start_input.submit(
|
| 1553 |
on_search,
|
| 1554 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1555 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1556 |
)
|
| 1557 |
|
| 1558 |
timestamp_end_input.submit(
|
| 1559 |
on_search,
|
| 1560 |
+
inputs=[items_per_page_slider, sort_order_radio, uuid_search_input, timestamp_start_input, timestamp_end_input, rating_filter, filter_extra_images, filter_negative_images, include_private_checkbox],
|
| 1561 |
outputs=[feedback_html, page_number, total_pages_display]
|
| 1562 |
)
|
| 1563 |
|