Spaces:
Running
Running
Optimize Gallery UI and enable Ctrl+V image pasting
Browse filesRemove custom upload/delete buttons in favor of native gr.Gallery features.
Add support for pasting images (Ctrl+V) directly into the gallery.
Refactor code to remove unused functions and simplify the layout.
app.py
CHANGED
|
@@ -740,42 +740,6 @@ def get_selection_from_gallery(gallery: list, tag_results: dict, selected_state:
|
|
| 740 |
|
| 741 |
return (selected_state.value["image"]["path"], selected_state.value["caption"]), tag_result["strings"], display_classified, tag_result["rating"], tag_result["character_res"], tag_result["general_res"], tag_result["unclassified_tags"]
|
| 742 |
|
| 743 |
-
def append_gallery(gallery: list, image: str):
|
| 744 |
-
if gallery is None:
|
| 745 |
-
gallery = []
|
| 746 |
-
if not image:
|
| 747 |
-
return gallery, None
|
| 748 |
-
|
| 749 |
-
gallery.append(image)
|
| 750 |
-
|
| 751 |
-
return gallery, None
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
def extend_gallery(gallery: list, images):
|
| 755 |
-
if gallery is None:
|
| 756 |
-
gallery = []
|
| 757 |
-
if not images:
|
| 758 |
-
return gallery
|
| 759 |
-
|
| 760 |
-
# Combine the new images with the existing gallery images
|
| 761 |
-
gallery.extend(images)
|
| 762 |
-
|
| 763 |
-
return gallery
|
| 764 |
-
|
| 765 |
-
def remove_image_from_gallery(gallery: list, selected_image: str):
|
| 766 |
-
if not gallery or not selected_image:
|
| 767 |
-
return gallery
|
| 768 |
-
|
| 769 |
-
try:
|
| 770 |
-
selected_image_tuple = ast.literal_eval(selected_image) #Use ast.literal_eval to parse text into a tuple.
|
| 771 |
-
# Remove the selected image from the gallery
|
| 772 |
-
if selected_image_tuple in gallery:
|
| 773 |
-
gallery.remove(selected_image_tuple)
|
| 774 |
-
except (ValueError, SyntaxError):
|
| 775 |
-
# Handle cases where the string is not a valid literal
|
| 776 |
-
print(f"Warning: Could not parse selected_image string: {selected_image}")
|
| 777 |
-
|
| 778 |
-
return gallery
|
| 779 |
|
| 780 |
|
| 781 |
def main():
|
|
@@ -801,7 +765,71 @@ def main():
|
|
| 801 |
.tag-dropdown span.svelte-1f354aw {
|
| 802 |
font-family: monospace;
|
| 803 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
args = parse_args()
|
| 806 |
|
| 807 |
predictor = Predictor()
|
|
@@ -859,24 +887,28 @@ def main():
|
|
| 859 |
with gr.Column():
|
| 860 |
submit = gr.Button(value="Submit", variant="primary", size="lg")
|
| 861 |
|
| 862 |
-
# Input type selector
|
| 863 |
-
input_type_radio = gr.Radio(
|
| 864 |
-
choices=['Image', 'Text file (.txt)'],
|
| 865 |
-
value='Image',
|
| 866 |
-
label="Input Type"
|
| 867 |
-
)
|
| 868 |
-
|
| 869 |
# Group for image inputs, initially visible
|
| 870 |
with gr.Column(visible=True) as image_inputs_group:
|
| 871 |
with gr.Column(variant="panel"):
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 880 |
with gr.Column(visible=False) as text_inputs_group:
|
| 881 |
text_files_input = gr.Files(
|
| 882 |
label="Upload .txt files",
|
|
@@ -884,6 +916,14 @@ def main():
|
|
| 884 |
file_count="multiple",
|
| 885 |
height=500
|
| 886 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 887 |
|
| 888 |
# Image-specific settings
|
| 889 |
model_repo = gr.Dropdown(
|
|
@@ -1047,18 +1087,12 @@ def main():
|
|
| 1047 |
selected_image = gr.Textbox(label="Selected Image", visible=False)
|
| 1048 |
|
| 1049 |
# Event Listeners
|
| 1050 |
-
# Define the event listener to add the uploaded image to the gallery
|
| 1051 |
-
image_input.change(append_gallery, inputs=[gallery, image_input], outputs=[gallery, image_input])
|
| 1052 |
-
# When the upload button is clicked, add the new images to the gallery
|
| 1053 |
-
upload_button.upload(extend_gallery, inputs=[gallery, upload_button], outputs=gallery)
|
| 1054 |
# Event to update the selected image when an image is clicked in the gallery
|
| 1055 |
gallery.select(
|
| 1056 |
get_selection_from_gallery,
|
| 1057 |
inputs=[gallery, tag_results],
|
| 1058 |
outputs=[selected_image, sorted_general_strings, categorized_state, rating, character_res, general_res, unclassified]
|
| 1059 |
)
|
| 1060 |
-
# Event to remove a selected image from the gallery
|
| 1061 |
-
remove_button.click(remove_image_from_gallery, inputs=[gallery, selected_image], outputs=gallery)
|
| 1062 |
|
| 1063 |
# Logic to show/hide input groups based on radio selection
|
| 1064 |
def change_input_type(input_type):
|
|
@@ -1113,9 +1147,9 @@ def main():
|
|
| 1113 |
)
|
| 1114 |
|
| 1115 |
gr.Examples(
|
| 1116 |
-
[["power.jpg", SWINV2_MODEL_DSV3_REPO, 0.35, False, 0.85, False]],
|
| 1117 |
inputs=[
|
| 1118 |
-
|
| 1119 |
model_repo,
|
| 1120 |
general_thresh,
|
| 1121 |
general_mcut_enabled,
|
|
@@ -1123,6 +1157,10 @@ def main():
|
|
| 1123 |
character_mcut_enabled,
|
| 1124 |
],
|
| 1125 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1126 |
|
| 1127 |
demo.queue(max_size=2)
|
| 1128 |
demo.launch(inbrowser=True)
|
|
|
|
| 740 |
|
| 741 |
return (selected_state.value["image"]["path"], selected_state.value["caption"]), tag_result["strings"], display_classified, tag_result["rating"], tag_result["character_res"], tag_result["general_res"], tag_result["unclassified_tags"]
|
| 742 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 743 |
|
| 744 |
|
| 745 |
def main():
|
|
|
|
| 765 |
.tag-dropdown span.svelte-1f354aw {
|
| 766 |
font-family: monospace;
|
| 767 |
}
|
| 768 |
+
/* Add hover effect to Gallery to indicate it is an interactive area */
|
| 769 |
+
#input_gallery:hover {
|
| 770 |
+
border-color: var(--color-accent) !important;
|
| 771 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
| 772 |
+
}
|
| 773 |
"""
|
| 774 |
+
|
| 775 |
+
# JavaScript to handle Ctrl+V paste for MULTIPLE files ONLY when hovering over the gallery
|
| 776 |
+
paste_js = """
|
| 777 |
+
function initPaste() {
|
| 778 |
+
document.addEventListener('paste', function(e) {
|
| 779 |
+
// 1. First find the Gallery component
|
| 780 |
+
const gallery = document.getElementById('input_gallery');
|
| 781 |
+
if (!gallery) return;
|
| 782 |
+
|
| 783 |
+
// 2. Check if mouse is hovering over the Gallery
|
| 784 |
+
// If mouse is not over the gallery, ignore this paste event
|
| 785 |
+
if (!gallery.matches(':hover')) {
|
| 786 |
+
return;
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
const clipboardData = e.clipboardData || e.originalEvent.clipboardData;
|
| 790 |
+
if (!clipboardData) return;
|
| 791 |
+
|
| 792 |
+
const items = clipboardData.items;
|
| 793 |
+
const files = [];
|
| 794 |
+
|
| 795 |
+
// 3. Check clipboard content
|
| 796 |
+
for (let i = 0; i < items.length; i++) {
|
| 797 |
+
if (items[i].kind === 'file' && items[i].type.startsWith('image/')) {
|
| 798 |
+
files.push(items[i].getAsFile());
|
| 799 |
+
}
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
// 4. Check file list (Copied files from OS)
|
| 803 |
+
if (files.length === 0 && clipboardData.files.length > 0) {
|
| 804 |
+
for (let i = 0; i < clipboardData.files.length; i++) {
|
| 805 |
+
if (clipboardData.files[i].type.startsWith('image/')) {
|
| 806 |
+
files.push(clipboardData.files[i]);
|
| 807 |
+
}
|
| 808 |
+
}
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
if (files.length === 0) return;
|
| 812 |
+
|
| 813 |
+
// 5. Execute upload logic
|
| 814 |
+
// Find input inside the gallery component
|
| 815 |
+
const uploadInput = gallery.querySelector('input[type="file"]');
|
| 816 |
+
|
| 817 |
+
if (uploadInput) {
|
| 818 |
+
e.preventDefault();
|
| 819 |
+
e.stopPropagation();
|
| 820 |
+
|
| 821 |
+
const dataTransfer = new DataTransfer();
|
| 822 |
+
files.forEach(file => dataTransfer.items.add(file));
|
| 823 |
+
|
| 824 |
+
uploadInput.files = dataTransfer.files;
|
| 825 |
+
|
| 826 |
+
// Trigger Gradio update
|
| 827 |
+
uploadInput.dispatchEvent(new Event('change', { bubbles: true }));
|
| 828 |
+
}
|
| 829 |
+
});
|
| 830 |
+
}
|
| 831 |
+
"""
|
| 832 |
+
|
| 833 |
args = parse_args()
|
| 834 |
|
| 835 |
predictor = Predictor()
|
|
|
|
| 887 |
with gr.Column():
|
| 888 |
submit = gr.Button(value="Submit", variant="primary", size="lg")
|
| 889 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 890 |
# Group for image inputs, initially visible
|
| 891 |
with gr.Column(visible=True) as image_inputs_group:
|
| 892 |
with gr.Column(variant="panel"):
|
| 893 |
+
gallery = gr.Gallery(
|
| 894 |
+
columns=5,
|
| 895 |
+
rows=5,
|
| 896 |
+
show_share_button=False,
|
| 897 |
+
interactive=True,
|
| 898 |
+
height=500,
|
| 899 |
+
label="Image Gallery (Drag multiple images here)",
|
| 900 |
+
elem_id="input_gallery",
|
| 901 |
+
)
|
| 902 |
+
gr.Markdown(
|
| 903 |
+
"""
|
| 904 |
+
<div style="text-align: right; font-size: 0.9em; color: gray;">
|
| 905 |
+
💡 Tip: Press <b>Ctrl+V</b> anywhere to paste images directly into the gallery.
|
| 906 |
+
</div>
|
| 907 |
+
"""
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
|
| 911 |
+
# 2. Define text input area (default hidden)
|
| 912 |
with gr.Column(visible=False) as text_inputs_group:
|
| 913 |
text_files_input = gr.Files(
|
| 914 |
label="Upload .txt files",
|
|
|
|
| 916 |
file_count="multiple",
|
| 917 |
height=500
|
| 918 |
)
|
| 919 |
+
|
| 920 |
+
# 3. Define Input Type selector
|
| 921 |
+
input_type_radio = gr.Radio(
|
| 922 |
+
choices=['Image', 'Text file (.txt)'],
|
| 923 |
+
value='Image',
|
| 924 |
+
label="Input Mode",
|
| 925 |
+
info="Select whether to process images or text files"
|
| 926 |
+
)
|
| 927 |
|
| 928 |
# Image-specific settings
|
| 929 |
model_repo = gr.Dropdown(
|
|
|
|
| 1087 |
selected_image = gr.Textbox(label="Selected Image", visible=False)
|
| 1088 |
|
| 1089 |
# Event Listeners
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1090 |
# Event to update the selected image when an image is clicked in the gallery
|
| 1091 |
gallery.select(
|
| 1092 |
get_selection_from_gallery,
|
| 1093 |
inputs=[gallery, tag_results],
|
| 1094 |
outputs=[selected_image, sorted_general_strings, categorized_state, rating, character_res, general_res, unclassified]
|
| 1095 |
)
|
|
|
|
|
|
|
| 1096 |
|
| 1097 |
# Logic to show/hide input groups based on radio selection
|
| 1098 |
def change_input_type(input_type):
|
|
|
|
| 1147 |
)
|
| 1148 |
|
| 1149 |
gr.Examples(
|
| 1150 |
+
[[["power.jpg"], SWINV2_MODEL_DSV3_REPO, 0.35, False, 0.85, False]],
|
| 1151 |
inputs=[
|
| 1152 |
+
gallery,
|
| 1153 |
model_repo,
|
| 1154 |
general_thresh,
|
| 1155 |
general_mcut_enabled,
|
|
|
|
| 1157 |
character_mcut_enabled,
|
| 1158 |
],
|
| 1159 |
)
|
| 1160 |
+
|
| 1161 |
+
# Load the JavaScript
|
| 1162 |
+
demo.load(None, None, None, js=paste_js)
|
| 1163 |
+
|
| 1164 |
|
| 1165 |
demo.queue(max_size=2)
|
| 1166 |
demo.launch(inbrowser=True)
|