avans06 commited on
Commit
a55bf41
·
1 Parent(s): 32c8def

Optimize Gallery UI and enable Ctrl+V image pasting

Browse files

Remove 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.

Files changed (1) hide show
  1. app.py +97 -59
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
- # Create an Image component for uploading images
873
- image_input = gr.Image(label="Upload an Image or clicking paste from clipboard button", type="filepath", sources=["upload", "clipboard"], height=150)
874
- with gr.Row():
875
- upload_button = gr.UploadButton("Upload multiple images", file_types=["image"], file_count="multiple", size="sm")
876
- remove_button = gr.Button("Remove Selected Image", size="sm")
877
- gallery = gr.Gallery(columns=5, rows=5, show_share_button=False, interactive=True, height=500, label="Gallery that displaying a grid of images")
878
-
879
- # Group for text file inputs, initially hidden
 
 
 
 
 
 
 
 
 
 
 
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
- image_input,
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)