mknolan commited on
Commit
0b41934
·
verified ·
1 Parent(s): 04de987

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +292 -260
app.py CHANGED
@@ -1074,268 +1074,155 @@ def save_to_html(content, source_type, filename=None):
1074
  # Function to analyze images with a prompt for folder analysis
1075
  def analyze_folder_images(folder_path, prompt):
1076
  """Analyze all images in a folder."""
1077
- if not os.path.exists(folder_path):
1078
- return f"Folder path does not exist: {folder_path}"
1079
 
1080
- # Get list of image files
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1081
  image_files = []
1082
  for ext in SUPPORTED_EXTENSIONS:
1083
- image_files.extend(glob.glob(os.path.join(folder_path, f"*{ext}")))
 
 
 
 
 
1084
 
1085
  if not image_files:
1086
- return f"No supported images found in folder: {folder_path}"
 
 
 
 
 
1087
 
1088
- # Sort files by name
1089
  image_files.sort()
1090
 
1091
- result = f"Found {len(image_files)} images in folder: {os.path.basename(folder_path)}\n\n"
 
1092
 
1093
- # Process each image file
1094
- for image_file in image_files:
1095
- file_name = os.path.basename(image_file)
1096
- result += f"---\nImage: {file_name}\n"
1097
-
1098
- # For PDF files, handle differently
1099
- if file_name.lower().endswith('.pdf'):
1100
- try:
1101
- logger.info(f"Processing PDF file in folder analysis: {image_file}")
1102
- logger.debug(f"PDF absolute path: {os.path.abspath(image_file)}")
1103
- logger.debug(f"PDF exists: {os.path.exists(image_file)}")
1104
- logger.debug(f"PDF file size: {os.path.getsize(image_file) if os.path.exists(image_file) else 'N/A'}")
1105
-
1106
- # Load model here to ensure it's ready
1107
- model, tokenizer = load_model()
1108
- if model is None or tokenizer is None:
1109
- logger.error("Model failed to load for PDF analysis")
1110
- result += "Error: Model failed to load for PDF analysis.\n"
1111
- continue
1112
-
1113
- # Try a completely different approach for PDFs to avoid tensor issues
1114
- try:
1115
- # Convert PDF to images with detailed logging
1116
- logger.info(f"Starting PDF to image conversion for {file_name}")
1117
- with open(image_file, 'rb') as pdf_file:
1118
- pdf_data = pdf_file.read()
1119
- logger.debug(f"Read {len(pdf_data)} bytes from PDF file")
1120
-
1121
- # Try both methods
1122
- try:
1123
- logger.debug("Attempting convert_from_path...")
1124
- pdf_images = convert_from_path(image_file)
1125
- logger.info(f"convert_from_path successful: {len(pdf_images)} pages")
1126
- except Exception as path_err:
1127
- logger.error(f"convert_from_path failed: {str(path_err)}")
1128
- logger.error(traceback.format_exc())
1129
-
1130
- # Fall back to bytes method
1131
- logger.debug("Falling back to convert_from_bytes...")
1132
- pdf_images = convert_from_bytes(pdf_data)
1133
- logger.info(f"convert_from_bytes successful: {len(pdf_images)} pages")
1134
-
1135
- logger.info(f"PDF converted to {len(pdf_images)} pages")
1136
-
1137
- if not pdf_images or len(pdf_images) == 0:
1138
- logger.error("PDF converted but no pages were extracted")
1139
- result += "PDF converted but no pages were extracted.\n"
1140
- continue
1141
-
1142
- # Process each page separately to avoid batch issues
1143
- for i, img in enumerate(pdf_images):
1144
- try:
1145
- logger.info(f"Processing PDF page {i+1}/{len(pdf_images)}")
1146
- logger.debug(f"Page {i+1} image: size={img.size}, mode={img.mode}")
1147
-
1148
- # Manual preprocessing - don't use the typical image loading pipeline
1149
- logger.debug("Converting image to RGB")
1150
- img = img.convert('RGB')
1151
-
1152
- # Log the image info for debugging
1153
- logger.debug(f"After RGB conversion: size={img.size}, mode={img.mode}")
1154
-
1155
- # Resize and transform manually
1156
- logger.debug(f"Resizing image to {IMAGE_SIZE}x{IMAGE_SIZE}")
1157
- img_resized = img.resize((IMAGE_SIZE, IMAGE_SIZE))
1158
- logger.debug(f"After resize: size={img_resized.size}, mode={img_resized.mode}")
1159
-
1160
- # Build transform and apply it
1161
- logger.debug("Building and applying transform")
1162
- transform = T.Compose([
1163
- T.ToTensor(),
1164
- T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD)
1165
- ])
1166
-
1167
- # Transform to tensor with explicit error handling
1168
- try:
1169
- logger.debug("Converting image to tensor")
1170
- tensor = transform(img_resized)
1171
- logger.debug(f"Transform successful: tensor shape={tensor.shape}, dtype={tensor.dtype}")
1172
-
1173
- # Log tensor stats for debugging numerical issues
1174
- if tensor.numel() > 0:
1175
- logger.debug(f"Tensor stats: min={tensor.min().item():.4f}, max={tensor.max().item():.4f}, "
1176
- f"mean={tensor.mean().item():.4f}, std={tensor.std().item():.4f}")
1177
-
1178
- # Add batch dimension with careful checking
1179
- if isinstance(tensor, torch.Tensor):
1180
- logger.debug("Adding batch dimension")
1181
- tensor = tensor.unsqueeze(0)
1182
- logger.debug(f"After unsqueeze: shape={tensor.shape}")
1183
- else:
1184
- logger.error(f"Expected tensor but got {type(tensor)}")
1185
- raise TypeError(f"Transform returned {type(tensor)} instead of tensor")
1186
- except Exception as tensor_err:
1187
- logger.error(f"Error in tensor creation: {str(tensor_err)}")
1188
- logger.error(traceback.format_exc())
1189
- raise
1190
-
1191
- # Move to device and set data type
1192
- device = "cuda" if torch.cuda.is_available() else "cpu"
1193
- logger.debug(f"Moving tensor to device: {device}")
1194
- tensor = tensor.to(device)
1195
-
1196
- if torch.cuda.is_available():
1197
- logger.debug("Converting tensor to bfloat16")
1198
- tensor = tensor.to(torch.bfloat16)
1199
-
1200
- logger.info(f"Preprocessed tensor: shape={tensor.shape}, device={tensor.device}, dtype={tensor.dtype}")
1201
-
1202
- # Use direct text generation
1203
- page_prompt = f"PDF Page {i+1}: {prompt}"
1204
- logger.debug(f"Preparing tokenization for prompt: {page_prompt}")
1205
- input_tokens = tokenizer(page_prompt, return_tensors="pt").to(device)
1206
- logger.debug(f"Tokenization complete: shape={input_tokens['input_ids'].shape}")
1207
-
1208
- # Generate with proper error handling
1209
- try:
1210
- # Try direct generation first
1211
- logger.info("Attempting direct generation")
1212
- outputs = model.generate(
1213
- input_tokens["input_ids"],
1214
- pixel_values=tensor,
1215
- max_new_tokens=512,
1216
- do_sample=False
1217
- )
1218
- logger.info("Generation successful")
1219
- response = tokenizer.decode(outputs[0], skip_special_tokens=True)
1220
- logger.debug(f"Response length: {len(response)} chars")
1221
- except Exception as gen_err:
1222
- logger.error(f"Error in direct generation: {str(gen_err)}")
1223
- logger.error(traceback.format_exc())
1224
-
1225
- # Fall back to chat method
1226
- try:
1227
- print("Trying chat method fallback")
1228
- question = f"<image>\n{page_prompt}"
1229
- # IMPORTANT: Ensure we're not passing a list here!
1230
- if isinstance(tensor, list):
1231
- print("WARNING: tensor is a list, converting...")
1232
- if len(tensor) > 0:
1233
- # Take the first item if it's a tensor
1234
- if isinstance(tensor[0], torch.Tensor):
1235
- tensor = tensor[0].unsqueeze(0)
1236
- else:
1237
- # Create a new tensor from scratch
1238
- print("Creating new tensor from scratch")
1239
- tensor = torch.zeros((1, 3, IMAGE_SIZE, IMAGE_SIZE),
1240
- dtype=torch.float32).to(device)
1241
- if torch.cuda.is_available():
1242
- tensor = tensor.to(torch.bfloat16)
1243
- else:
1244
- # Create a dummy tensor
1245
- tensor = torch.zeros((1, 3, IMAGE_SIZE, IMAGE_SIZE),
1246
- dtype=torch.float32).to(device)
1247
- if torch.cuda.is_available():
1248
- tensor = tensor.to(torch.bfloat16)
1249
-
1250
- # Verify tensor shape and type before passing to model
1251
- print(f"Final tensor type: {type(tensor)}, shape: {tensor.shape if hasattr(tensor, 'shape') else 'unknown'}")
1252
-
1253
- # Use the chat method with verified tensor
1254
- response, _ = model.chat(
1255
- tokenizer=tokenizer,
1256
- pixel_values=tensor,
1257
- question=question,
1258
- generation_config={"max_new_tokens": 512, "do_sample": False},
1259
- history=None,
1260
- return_history=True
1261
- )
1262
- except Exception as chat_err:
1263
- print(f"Chat fallback failed: {str(chat_err)}")
1264
- import traceback
1265
- print(traceback.format_exc())
1266
-
1267
- # Last attempt - use direct model forward pass
1268
- try:
1269
- print("Attempting direct model forward pass")
1270
- # Create inputs manually
1271
- if hasattr(model, "forward"):
1272
- # Create tensors from scratch if needed
1273
- if not isinstance(tensor, torch.Tensor):
1274
- tensor = torch.zeros((1, 3, IMAGE_SIZE, IMAGE_SIZE),
1275
- dtype=torch.float32).to(device)
1276
- if torch.cuda.is_available():
1277
- tensor = tensor.to(torch.bfloat16)
1278
-
1279
- # Get input tokens in the right format
1280
- input_ids = input_tokens["input_ids"]
1281
- if len(input_ids.shape) == 1:
1282
- input_ids = input_ids.unsqueeze(0)
1283
-
1284
- # Prepare inputs for direct call
1285
- inputs = {
1286
- "input_ids": input_ids,
1287
- "pixel_values": tensor,
1288
- "return_dict": True,
1289
- }
1290
- # Call model directly
1291
- outputs = model(**inputs)
1292
- # Try to get some output
1293
- if hasattr(outputs, "logits") and outputs.logits is not None:
1294
- pred_ids = torch.argmax(outputs.logits, dim=-1)
1295
- response = tokenizer.decode(pred_ids[0], skip_special_tokens=True)
1296
- else:
1297
- response = "Failed to generate analysis - model output didn't contain usable data"
1298
- else:
1299
- response = "Failed to generate analysis - model doesn't support direct calling"
1300
- except Exception as final_err:
1301
- print(f"All attempts failed: {str(final_err)}")
1302
- import traceback
1303
- print(traceback.format_exc())
1304
- response = f"Analysis failed due to model error: {str(final_err)}"
1305
-
1306
- # Add to result
1307
- result += f"\n-- PDF Page {i+1} --\n{response.strip()}\n"
1308
-
1309
- except Exception as page_err:
1310
- print(f"Error processing page {i+1}: {str(page_err)}")
1311
- import traceback
1312
- print(traceback.format_exc())
1313
- result += f"\n-- PDF Page {i+1} --\nError: {str(page_err)}\n"
1314
-
1315
- except Exception as pdf_err:
1316
- print(f"PDF processing error: {str(pdf_err)}")
1317
- import traceback
1318
- print(traceback.format_exc())
1319
- result += f"Failed to process PDF: {str(pdf_err)}\n"
1320
-
1321
- except Exception as e:
1322
- print(f"General exception in PDF processing: {str(e)}")
1323
- import traceback
1324
- print(traceback.format_exc())
1325
- result += f"Failed to process PDF: {str(e)}\n"
1326
- else:
1327
- # Regular image processing
1328
  try:
1329
- image = Image.open(image_file).convert('RGB')
1330
- image_result = process_image_with_text(image, prompt)
1331
- result += f"\n{image_result}\n"
1332
- except Exception as e:
1333
- print(f"Error processing image {image_file}: {str(e)}")
1334
- import traceback
1335
- print(traceback.format_exc())
1336
- result += f"Error processing image: {str(e)}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1337
 
1338
- return result
 
 
1339
 
1340
  # Function to process an image with text prompt
1341
  def process_image_with_text(image, prompt):
@@ -1882,10 +1769,36 @@ def main():
1882
 
1883
  # Tab for folder analysis
1884
  with gr.Tab("Folder Analysis"):
1885
- gr.Markdown("Analyze all images and PDFs in a specified folder")
 
 
 
 
 
 
1886
 
1887
  with gr.Row():
1888
- folder_path = gr.Textbox(label="Folder Path", placeholder="Enter the path to the folder")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1889
 
1890
  prompt_folder = gr.Dropdown(
1891
  label="Analysis Prompt",
@@ -1893,23 +1806,142 @@ def main():
1893
  value=prompts[0],
1894
  allow_custom_value=True
1895
  )
1896
- analyze_btn_folder = gr.Button("Analyze Folder")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1897
  output_folder = gr.Textbox(label="Analysis Result", lines=20)
1898
 
1899
- # Save button for folder analysis
1900
- save_btn_folder = gr.Button("Save Results to File")
1901
- save_status_folder = gr.Textbox(label="Save Status", lines=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1902
 
1903
  analyze_btn_folder.click(
1904
- fn=analyze_folder_images,
1905
  inputs=[folder_path, prompt_folder],
1906
- outputs=output_folder
1907
  )
1908
 
 
 
 
 
 
 
 
 
1909
  save_btn_folder.click(
1910
  fn=lambda text, prompt: save_to_file(text, "folder_analysis", prompt=prompt),
1911
  inputs=[output_folder, prompt_folder],
1912
- outputs=save_status_folder
 
 
 
 
 
 
 
 
 
 
 
 
1913
  )
1914
 
1915
  # Add a new tab for logs
 
1074
  # Function to analyze images with a prompt for folder analysis
1075
  def analyze_folder_images(folder_path, prompt):
1076
  """Analyze all images in a folder."""
1077
+ # Add extensive logging to debug panel
1078
+ logger.info(f"analyze_folder_images called with path: '{folder_path}'")
1079
 
1080
+ # Increment operations counter
1081
+ gui_stats['operations_completed'] += 1
1082
+
1083
+ if not folder_path or folder_path.strip() == "":
1084
+ error_msg = "No folder path provided. Please enter a valid folder path."
1085
+ logger.error(error_msg)
1086
+ gui_stats['errors'] += 1
1087
+ gui_stats['last_error'] = error_msg
1088
+ gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
1089
+ return error_msg
1090
+
1091
+ # Clean up the folder path
1092
+ folder_path = folder_path.strip()
1093
+ logger.debug(f"Cleaned folder path: '{folder_path}'")
1094
+
1095
+ # Try multiple path options
1096
+ potential_paths = [
1097
+ folder_path, # As provided
1098
+ os.path.join(os.getcwd(), folder_path), # Relative to cwd
1099
+ os.path.normpath(folder_path), # Normalized path
1100
+ os.path.abspath(folder_path), # Absolute path
1101
+ os.path.expanduser(folder_path) # Expand user directory (~)
1102
+ ]
1103
+
1104
+ # If we're in a Hugging Face Space, try the /data path
1105
+ if os.path.exists("/data"):
1106
+ potential_paths.append(os.path.join("/data", folder_path))
1107
+
1108
+ # Debug information
1109
+ logger.debug(f"Current working directory: {os.getcwd()}")
1110
+ try:
1111
+ logger.debug(f"Directory contents of current directory: {os.listdir('.')}")
1112
+ except Exception as e:
1113
+ logger.warning(f"Could not list current directory: {str(e)}")
1114
+
1115
+ # Try each path
1116
+ valid_path = None
1117
+ for test_path in potential_paths:
1118
+ logger.debug(f"Testing path: '{test_path}'")
1119
+ if os.path.exists(test_path):
1120
+ logger.debug(f"Path exists: '{test_path}'")
1121
+ if os.path.isdir(test_path):
1122
+ valid_path = test_path
1123
+ logger.info(f"Found valid directory path: '{valid_path}'")
1124
+ break
1125
+ else:
1126
+ logger.debug(f"Path exists but is not a directory: '{test_path}'")
1127
+
1128
+ if not valid_path:
1129
+ error_msg = f"Could not find a valid directory at '{folder_path}'. Please provide a complete and valid folder path."
1130
+ logger.error(error_msg)
1131
+ gui_stats['errors'] += 1
1132
+ gui_stats['last_error'] = error_msg
1133
+ gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
1134
+
1135
+ # Try to provide helpful information about available directories
1136
+ try:
1137
+ available_dirs = [d for d in os.listdir('.') if os.path.isdir(d)]
1138
+ if available_dirs:
1139
+ return f"Error: {error_msg}\n\nAvailable directories in current location: {', '.join(available_dirs)}"
1140
+ else:
1141
+ return f"Error: {error_msg}\n\nNo directories found in the current location."
1142
+ except:
1143
+ return f"Error: {error_msg}"
1144
+
1145
+ # Convert to Path object for easier handling
1146
+ folder_path = Path(valid_path)
1147
+ logger.debug(f"Using folder path: {folder_path}")
1148
+
1149
+ # Find all image files in the directory
1150
  image_files = []
1151
  for ext in SUPPORTED_EXTENSIONS:
1152
+ logger.debug(f"Searching for files with extension: {ext}")
1153
+ # Use glob patterns that are case-insensitive
1154
+ image_files.extend(list(folder_path.glob(f"*{ext.lower()}")))
1155
+ image_files.extend(list(folder_path.glob(f"*{ext.upper()}")))
1156
+
1157
+ logger.info(f"Found {len(image_files)} images in {folder_path}")
1158
 
1159
  if not image_files:
1160
+ error_msg = f"No supported image files found in '{folder_path}'. Supported formats: {', '.join(SUPPORTED_EXTENSIONS)}"
1161
+ logger.warning(error_msg)
1162
+ gui_stats['warnings'] += 1
1163
+ gui_stats['last_warning'] = error_msg
1164
+ gui_stats['last_warning_time'] = datetime.datetime.now().strftime("%H:%M:%S")
1165
+ return error_msg
1166
 
1167
+ # Sort the files for consistent output
1168
  image_files.sort()
1169
 
1170
+ results = []
1171
+ results.append(f"Found {len(image_files)} images in {folder_path}\n")
1172
 
1173
+ # Load model once for all images
1174
+ logger.info("Loading model for folder analysis")
1175
+ model, tokenizer = load_model()
1176
+ if model is None or tokenizer is None:
1177
+ error_msg = "Error: Could not load model for image analysis"
1178
+ logger.error(error_msg)
1179
+ gui_stats['errors'] += 1
1180
+ gui_stats['last_error'] = error_msg
1181
+ gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
1182
+ return error_msg
1183
+
1184
+ # Process each image
1185
+ for i, img_path in enumerate(image_files, 1):
1186
+ try:
1187
+ logger.info(f"Processing image {i}/{len(image_files)}: {img_path.name}")
1188
+
1189
+ # Open and process the image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1190
  try:
1191
+ image = Image.open(img_path).convert('RGB')
1192
+ logger.debug(f"Image loaded: size={image.size}, mode={image.mode}")
1193
+ except Exception as img_err:
1194
+ error_msg = f"Error opening image {img_path.name}: {str(img_err)}"
1195
+ logger.error(error_msg)
1196
+ logger.error(traceback.format_exc())
1197
+ gui_stats['errors'] += 1
1198
+ gui_stats['last_error'] = error_msg
1199
+ gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
1200
+ results.append(f"---\nImage {i}/{len(image_files)}: {img_path.name}\nError opening image: {str(img_err)}\n")
1201
+ continue
1202
+
1203
+ # Process image
1204
+ logger.debug(f"Processing image with prompt: {prompt}")
1205
+ # Use the more reliable process_image_with_text function
1206
+ image_result = process_image_with_text(image, f"Image {i}/{len(image_files)} - {img_path.name}: {prompt}")
1207
+
1208
+ # Add result with separator
1209
+ results.append(f"---\nImage {i}/{len(image_files)}: {img_path.name}\n{image_result}\n")
1210
+
1211
+ # Log success
1212
+ logger.info(f"Successfully processed image {i}/{len(image_files)}: {img_path.name}")
1213
+
1214
+ except Exception as e:
1215
+ error_msg = f"Error processing image {img_path.name}: {str(e)}"
1216
+ logger.error(error_msg)
1217
+ logger.error(traceback.format_exc())
1218
+ gui_stats['errors'] += 1
1219
+ gui_stats['last_error'] = error_msg
1220
+ gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
1221
+ results.append(f"---\nImage {i}/{len(image_files)}: {img_path.name}\nError: {str(e)}\n")
1222
 
1223
+ combined_result = "\n".join(results)
1224
+ logger.info(f"Folder analysis complete, processed {len(image_files)} images")
1225
+ return combined_result
1226
 
1227
  # Function to process an image with text prompt
1228
  def process_image_with_text(image, prompt):
 
1769
 
1770
  # Tab for folder analysis
1771
  with gr.Tab("Folder Analysis"):
1772
+ gr.Markdown("## Analyze all images and PDFs in a folder")
1773
+ gr.Markdown("""
1774
+ Please enter a complete folder path. You can try these options:
1775
+ - Absolute path (e.g., `/home/user/images`)
1776
+ - Relative path from current directory (e.g., `example_images`)
1777
+ - Path with ~ for home directory (e.g., `~/images`)
1778
+ """)
1779
 
1780
  with gr.Row():
1781
+ with gr.Column(scale=4):
1782
+ folder_path = gr.Textbox(
1783
+ label="Folder Path",
1784
+ placeholder="Enter the complete path to the folder containing images",
1785
+ value="example_images" # Default to example folder
1786
+ )
1787
+ with gr.Column(scale=1):
1788
+ example_folders = gr.Dropdown(
1789
+ choices=["example_images", "example_images_2", "/data/images", "images"],
1790
+ label="Example Folders",
1791
+ value="example_images"
1792
+ )
1793
+
1794
+ def set_folder_path(folder):
1795
+ return folder
1796
+
1797
+ example_folders.change(
1798
+ fn=set_folder_path,
1799
+ inputs=[example_folders],
1800
+ outputs=[folder_path]
1801
+ )
1802
 
1803
  prompt_folder = gr.Dropdown(
1804
  label="Analysis Prompt",
 
1806
  value=prompts[0],
1807
  allow_custom_value=True
1808
  )
1809
+
1810
+ # Show folder contents without analyzing
1811
+ view_folder_btn = gr.Button("View Folder Contents")
1812
+ folder_contents = gr.Markdown("Select a folder and click 'View Folder Contents' to see available images")
1813
+
1814
+ def view_folder_contents(folder_path):
1815
+ """List all image files in the folder without analyzing them."""
1816
+ logger.info(f"Viewing contents of folder: '{folder_path}'")
1817
+
1818
+ if not folder_path or folder_path.strip() == "":
1819
+ return "Please enter a folder path."
1820
+
1821
+ # Clean up the folder path
1822
+ folder_path = folder_path.strip()
1823
+
1824
+ # Try multiple path options
1825
+ potential_paths = [
1826
+ folder_path,
1827
+ os.path.join(os.getcwd(), folder_path),
1828
+ os.path.normpath(folder_path),
1829
+ os.path.abspath(folder_path),
1830
+ os.path.expanduser(folder_path)
1831
+ ]
1832
+
1833
+ # If we're in a Hugging Face Space, try the /data path
1834
+ if os.path.exists("/data"):
1835
+ potential_paths.append(os.path.join("/data", folder_path))
1836
+
1837
+ # Try each path
1838
+ valid_path = None
1839
+ for test_path in potential_paths:
1840
+ if os.path.exists(test_path) and os.path.isdir(test_path):
1841
+ valid_path = test_path
1842
+ break
1843
+
1844
+ if not valid_path:
1845
+ return f"Could not find a valid directory at '{folder_path}'.\n\nTried the following paths:\n" + "\n".join(f"- {p}" for p in potential_paths)
1846
+
1847
+ # List image files
1848
+ image_files = []
1849
+ for ext in SUPPORTED_EXTENSIONS:
1850
+ files = glob.glob(os.path.join(valid_path, f"*{ext}"))
1851
+ files.extend(glob.glob(os.path.join(valid_path, f"*{ext.upper()}")))
1852
+ image_files.extend(files)
1853
+
1854
+ # Sort
1855
+ image_files.sort()
1856
+
1857
+ if not image_files:
1858
+ return f"No supported image files found in '{valid_path}'.\n\nSupported formats: {', '.join(SUPPORTED_EXTENSIONS)}"
1859
+
1860
+ # Format as markdown
1861
+ output = f"### Found {len(image_files)} images in '{valid_path}'\n\n"
1862
+ for i, file in enumerate(image_files, 1):
1863
+ file_name = os.path.basename(file)
1864
+ file_size = os.path.getsize(file) / 1024 # KB
1865
+ output += f"{i}. **{file_name}** ({file_size:.1f} KB)\n"
1866
+
1867
+ output += f"\nPath used: `{valid_path}`"
1868
+ return output
1869
+
1870
+ view_folder_btn.click(
1871
+ fn=view_folder_contents,
1872
+ inputs=[folder_path],
1873
+ outputs=[folder_contents]
1874
+ )
1875
+
1876
+ gr.Markdown("---")
1877
+ analyze_btn_folder = gr.Button("Analyze All Images in Folder", variant="primary")
1878
  output_folder = gr.Textbox(label="Analysis Result", lines=20)
1879
 
1880
+ # Status indicator
1881
+ with gr.Row():
1882
+ folder_status = gr.Markdown("Ready to analyze folder images")
1883
+
1884
+ # Define a function to update status while processing
1885
+ def analyze_with_status(folder_path, prompt):
1886
+ folder_status_msg = "Starting folder analysis..."
1887
+ yield folder_status_msg, ""
1888
+
1889
+ try:
1890
+ # Get number of potential images
1891
+ try:
1892
+ folder_path = folder_path.strip()
1893
+ folder_obj = Path(folder_path)
1894
+ if folder_obj.exists() and folder_obj.is_dir():
1895
+ image_count = sum(1 for _ in folder_obj.glob("*.*") if _.suffix.lower() in SUPPORTED_EXTENSIONS)
1896
+ folder_status_msg = f"Found {image_count} images to process. Starting analysis..."
1897
+ yield folder_status_msg, ""
1898
+ except:
1899
+ pass
1900
+
1901
+ # Run analysis
1902
+ folder_status_msg = "Processing images... (this may take several minutes)"
1903
+ yield folder_status_msg, ""
1904
+
1905
+ # Run the actual analysis
1906
+ result = analyze_folder_images(folder_path, prompt)
1907
+
1908
+ folder_status_msg = "Folder analysis complete!"
1909
+ yield folder_status_msg, result
1910
+ except Exception as e:
1911
+ error_msg = f"Error analyzing folder: {str(e)}"
1912
+ folder_status_msg = "Analysis failed! See error message in results."
1913
+ yield folder_status_msg, error_msg
1914
 
1915
  analyze_btn_folder.click(
1916
+ fn=analyze_with_status,
1917
  inputs=[folder_path, prompt_folder],
1918
+ outputs=[folder_status, output_folder]
1919
  )
1920
 
1921
+ # Save button for folder analysis
1922
+ with gr.Row():
1923
+ save_btn_folder = gr.Button("Save Results to Text File")
1924
+ save_json_folder = gr.Button("Save Results as JSON")
1925
+ save_html_folder = gr.Button("Save Results as HTML")
1926
+
1927
+ save_status_folder = gr.Textbox(label="Save Status", lines=1)
1928
+
1929
  save_btn_folder.click(
1930
  fn=lambda text, prompt: save_to_file(text, "folder_analysis", prompt=prompt),
1931
  inputs=[output_folder, prompt_folder],
1932
+ outputs=[save_status_folder]
1933
+ )
1934
+
1935
+ save_json_folder.click(
1936
+ fn=lambda content: save_to_json(content, "folder", "Folder analysis", None)[0],
1937
+ inputs=[output_folder],
1938
+ outputs=[save_status_folder]
1939
+ )
1940
+
1941
+ save_html_folder.click(
1942
+ fn=lambda content: save_to_html(content, "folder")[0],
1943
+ inputs=[output_folder],
1944
+ outputs=[save_status_folder]
1945
  )
1946
 
1947
  # Add a new tab for logs