-
Notifications
You must be signed in to change notification settings - Fork 215
Add batch report generation for image outputs #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Reviewer's GuideThis PR implements end-to-end batch report generation by introducing a BatchReportGenerator utility (handling CSV metadata, config JSON, image copying, README, and ZIP bundling), exposing a new backend endpoint to trigger report creation, integrating a dedicated React component into the UI, adding frontend service methods to POST and download reports, and validating the feature with a comprehensive suite of unit and integration tests. Sequence diagram for batch report generation request (frontend to backend)sequenceDiagram
actor User
participant Frontend as React UI (BatchReportGenerator)
participant ReportService as reportService.ts
participant Backend as Flask /api/batch-report/generate
participant Generator as BatchReportGenerator (Python)
User->>Frontend: Click 'Generate Batch Report'
Frontend->>ReportService: generateBatchReport(images, config, reportName)
ReportService->>Backend: POST /api/batch-report/generate (JSON)
Backend->>Generator: generate_report(images_data, config, report_name)
Generator-->>Backend: ZIP file path
Backend-->>ReportService: ZIP file (as response)
ReportService-->>Frontend: ZIP file (Blob)
Frontend-->>User: Download ZIP file
Class diagram for BatchReportGenerator utility (backend)classDiagram
class BatchReportGenerator {
+__init__(output_dir: Optional[str])
+generate_report(images_data: List[Dict], config: Dict, report_name: Optional[str]) str
+validate_csv_schema(csv_path: str) bool
+validate_zip_contents(zip_path: str) bool
-_create_csv(csv_path: str, images_data: List[Dict])
-_create_config_json(config_path: str, config: Dict)
-_copy_images(images_data: List[Dict], grids_dir: str) int
-_create_readme(readme_path: str, images_data: List[Dict], config: Dict)
-_create_zip(source_dir: str, zip_path: str)
}
Class diagram for frontend batch report types and serviceclassDiagram
class ImageReportData {
+id: string
+filename: string
+url: string
+prompt: string
+negativePrompt?: string
+timestamp: number
+settings: GenerationSettings
}
class GenerationSettings {
+model: string
+sampler: string
+steps: number
+cfg_scale: number
+seed: number
+width: number
+height: number
+[key: string]: any
}
class GenerationConfig {
+session_id?: string
+generation_date?: string
+total_images?: number
+[key: string]: any
}
class BatchReportRequest {
+images: ImageReportData[]
+config: GenerationConfig
+report_name?: string
}
class BatchReportResponse {
+status: string
+message?: string
+download_url?: string
}
ImageReportData --> GenerationSettings
BatchReportRequest --> ImageReportData
BatchReportRequest --> GenerationConfig
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
WalkthroughA batch report generation feature was implemented across the backend and frontend. Backend changes introduce a Changes
Sequence Diagram(s)Batch Report Generation Flow (Frontend to Backend)sequenceDiagram
participant User
participant BatchReportGenerator (React)
participant reportService.ts
participant Flask API (/api/batch-report/generate)
participant BatchReportGenerator (Python)
participant ZIP File
User->>BatchReportGenerator (React): Select images, configure, click "Generate"
BatchReportGenerator (React)->>reportService.ts: generateBatchReport(images, config, name)
reportService.ts->>Flask API: POST /api/batch-report/generate (JSON)
Flask API->>BatchReportGenerator (Python): generate_report(images, config, name)
BatchReportGenerator (Python)->>ZIP File: Create ZIP with CSV, JSON, images, README
Flask API->>reportService.ts: Return ZIP file (Blob)
reportService.ts->>BatchReportGenerator (React): Resolve with Blob
BatchReportGenerator (React)->>User: Trigger file download
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| for i in range(num_images): | ||
| images_data.append({ | ||
| 'id': f'img_{i:04d}', | ||
| 'filename': f'test_image_{i:04d}.png', | ||
| 'url': f'http://localhost:5001/api/images/test_image_{i:04d}.png', | ||
| 'prompt': f'Test prompt {i}', | ||
| 'negativePrompt': 'negative', | ||
| 'timestamp': 1704067200000 + i * 1000, | ||
| 'settings': { | ||
| 'model': 'test_model.safetensors', | ||
| 'sampler': 'Euler', | ||
| 'steps': 20, | ||
| 'cfg_scale': 7.0, | ||
| 'seed': 12345 + i, | ||
| 'width': 512, | ||
| 'height': 512 | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Avoid loops in tests. (no-loop-in-tests)
Explanation
Avoid complex code, like loops, in test functions.Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
- loops
- conditionals
Some ways to fix this:
- Use parametrized tests to get rid of the loop.
- Move the complex logic into helpers.
- Move the complex part into pytest fixtures.
Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.
Software Engineering at Google / Don't Put Logic in Tests
|
|
||
| try: | ||
| # Create results.csv | ||
| csv_path = os.path.join(temp_dir, 'results.csv') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Extract code out into method (extract-method)
| headers = reader.fieldnames or [] | ||
|
|
||
| # Check if all required columns are present | ||
| missing_columns = set(REQUIRED_CSV_COLUMNS) - set(headers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Use named expression to simplify assignment and conditional (use-named-expression)
| try: | ||
| with zipfile.ZipFile(zip_path, 'r') as zipf: | ||
| # Get list of files in ZIP | ||
| zip_files = set(zipf.namelist()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Extract code out into method (extract-method)
|
|
||
| try: | ||
| data = request.json | ||
| print(f"📊 Batch report generation request received") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Replace f-string with no interpolated values with string (remove-redundant-fstring)
|
|
||
| # Validate ZIP contents | ||
| with zipfile.ZipFile(zip_path, 'r') as zipf: | ||
| namelist = zipf.namelist() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): We've found these issues:
- Extract code out into method (
extract-method) - Extract duplicate code into method (
extract-duplicate-method)
| zip_path = os.path.join(temp_output_dir, 'test_validation.zip') | ||
| with zipfile.ZipFile(zip_path, 'w') as zipf: | ||
| # Create CSV content | ||
| csv_content = "filename,prompt,negative_prompt,model,sampler,steps,cfg_scale,seed,width,height,timestamp,grid_path\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Replace assignment and augmented assignment with single assignment [×2] (merge-assign-and-aug-assign)
| def test_scalability(self, temp_output_dir, sample_config, num_images): | ||
| """Test report generation with varying numbers of images""" | ||
| # Generate many images | ||
| images_data = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Convert for loop into list comprehension (list-comprehension)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (4)
dream_layer_backend/extras.py (1)
8-8: Remove unused import.The
tempfilemodule is imported but never used in this file.-import tempfiledream_layer_backend/tests/test_batch_report_generator.py (2)
1-19: LGTM! Comprehensive test module structure.The test module is well-organized with proper imports and fixtures. The docstring clearly describes the test scope.
Remove unused import:
-from datetime import datetime
21-84: LGTM! Excellent fixture setup with minor cleanup needed.The fixtures are well-designed with proper setup and teardown. The mocking approach for the served images directory is clever.
Clean up unused variables:
# Also patch the _copy_images method to use our temp dir - original_copy_images = BatchReportGenerator._copy_images def mock_copy_images(self, images_data, grids_dir): # Use the stored temp dir instead of the default served_images_dir = self._served_images_dir copied_count = 0 for idx, image_data in enumerate(images_data): try: original_filename = image_data.get('filename') if not original_filename: continue src_path = os.path.join(served_images_dir, original_filename) grid_filename = f"grid_{idx:04d}_{Path(original_filename).stem}.png" dest_path = os.path.join(grids_dir, grid_filename) if os.path.exists(src_path): shutil.copy2(src_path, dest_path) copied_count += 1 - except Exception as e: + except Exception: passdream_layer_backend/dream_layer_backend_utils/batch_report_generator.py (1)
194-236: Consider validating image format before copyingThe method handles file copying well, but could benefit from validating that files are actually images before copying them.
Add image format validation:
# Copy file if it exists if os.path.exists(src_path): + # Validate it's an image file + valid_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.webp'} + if Path(src_path).suffix.lower() not in valid_extensions: + logger.warning(f"File {src_path} is not a valid image format") + continue shutil.copy2(src_path, dest_path) copied_count += 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
dream_layer_backend/dream_layer_backend_utils/__init__.py(2 hunks)dream_layer_backend/dream_layer_backend_utils/batch_report_generator.py(1 hunks)dream_layer_backend/extras.py(2 hunks)dream_layer_backend/tests/test_batch_report_generator.py(1 hunks)dream_layer_frontend/src/components/batch-report/BatchReportGenerator.tsx(1 hunks)dream_layer_frontend/src/features/Extras/ExtrasPage.tsx(6 hunks)dream_layer_frontend/src/services/reportService.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
dream_layer_backend/dream_layer_backend_utils/__init__.py (1)
dream_layer_backend/dream_layer_backend_utils/batch_report_generator.py (1)
BatchReportGenerator(35-364)
dream_layer_backend/tests/test_batch_report_generator.py (1)
dream_layer_backend/dream_layer_backend_utils/batch_report_generator.py (6)
BatchReportGenerator(35-364)_copy_images(194-236)validate_csv_schema(299-324)_create_csv(126-166)generate_report(60-124)validate_zip_contents(326-364)
🪛 Ruff (0.12.2)
dream_layer_backend/tests/test_batch_report_generator.py
15-15: datetime.datetime imported but unused
Remove unused import: datetime.datetime
(F401)
55-55: Local variable original_copy_images is assigned to but never used
Remove assignment to unused variable original_copy_images
(F841)
75-75: Local variable e is assigned to but never used
Remove assignment to unused variable e
(F841)
dream_layer_backend/extras.py
8-8: tempfile imported but unused
Remove unused import: tempfile
(F401)
330-330: f-string without any placeholders
Remove extraneous f prefix
(F541)
dream_layer_backend/dream_layer_backend_utils/batch_report_generator.py
288-288: Loop control variable dirs not used within loop body
Rename unused dirs to _dirs
(B007)
🪛 Biome (2.1.2)
dream_layer_frontend/src/features/Extras/ExtrasPage.tsx
[error] 390-419: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 421-421: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (20)
dream_layer_backend/dream_layer_backend_utils/__init__.py (2)
11-11: LGTM! Clean import addition.The import follows the established pattern and properly exposes the new BatchReportGenerator utility class.
20-22: LGTM! Proper package API exposure.The BatchReportGenerator is correctly added to the all list, maintaining consistency with the existing public API structure.
dream_layer_frontend/src/features/Extras/ExtrasPage.tsx (3)
6-6: LGTM! Clean imports for batch report functionality.The imports are well-organized and follow the established patterns for this component.
Also applies to: 24-26
54-55: LGTM! Proper subtab configuration.The new subtabs are correctly added to the existing navigation structure.
472-478: LGTM! Dynamic accordion title logic.The conditional title logic properly reflects the active subtab content.
dream_layer_backend/extras.py (1)
6-6: LGTM! Appropriate imports for batch report functionality.The import changes correctly bring in
send_filefor ZIP file delivery andBatchReportGeneratorfor report creation.Also applies to: 11-11
dream_layer_backend/tests/test_batch_report_generator.py (3)
87-141: LGTM! Well-structured test fixtures.The sample data fixtures provide realistic test data with proper structure for comprehensive testing.
142-310: LGTM! Comprehensive unit test coverage.Excellent test coverage including:
- Initialization validation
- CSV schema validation with positive/negative cases
- Deterministic file naming verification
- ZIP creation and content validation
- Edge cases like empty image lists
- Report name generation logic
The test methods are well-structured and cover all critical functionality.
312-382: LGTM! Excellent integration and scalability tests.The integration tests properly validate the complete workflow, and the parametrized scalability tests ensure the system works with varying data sizes. This demonstrates thorough testing practices.
dream_layer_frontend/src/components/batch-report/BatchReportGenerator.tsx (4)
1-15: LGTM! Clean imports and interface definition.The imports are well-organized and the component interface is properly typed with clear prop definitions.
16-35: LGTM! Proper state management and effects.The component state is well-structured, and the useEffect properly handles auto-selection when images change. The select all/deselect all logic is intuitive.
37-83: LGTM! Robust report generation logic.Excellent implementation with:
- Proper validation before processing
- Clean data preparation and configuration
- Good error handling with user-friendly messages
- Proper loading state management
- Toast notifications for feedback
The async/await pattern and error handling follow best practices.
85-184: LGTM! Clean and intuitive UI implementation.The component UI is well-structured with:
- Clear visual hierarchy and descriptive elements
- Proper form controls with appropriate disabled states
- Good use of icons and loading indicators
- Accessible labels and descriptions
- Consistent styling with the design system
The conditional rendering and state-dependent UI updates provide excellent user experience.
dream_layer_frontend/src/services/reportService.ts (1)
41-41: LGTM!The environment variable handling for the API base URL is correctly implemented using Vite's
import.meta.env.dream_layer_backend/dream_layer_backend_utils/batch_report_generator.py (6)
1-33: LGTM!The module setup, imports, and constants are well-organized and appropriate.
43-58: LGTM!The initialization properly handles default and custom output directories with appropriate logging.
126-166: LGTM!The CSV creation correctly handles the data structure with proper error handling. The method appropriately accesses
negativePromptin camelCase to match the frontend data format.
168-192: LGTM!The config JSON creation properly includes metadata and handles Unicode characters correctly.
238-276: LGTM!The README creation provides comprehensive documentation with clear usage instructions.
299-364: LGTM!The validation methods provide thorough checks for CSV schema and ZIP contents with appropriate error handling.
| temp_dir = os.path.join(self.output_dir, f"{report_name}_temp") | ||
| os.makedirs(temp_dir, exist_ok=True) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use tempfile module for safer temporary directory creation
The current implementation could have race conditions if multiple reports are generated simultaneously. Consider using Python's tempfile module for safer temporary directory management.
+import tempfile
+
# Create temporary directory for report contents
-temp_dir = os.path.join(self.output_dir, f"{report_name}_temp")
-os.makedirs(temp_dir, exist_ok=True)
+temp_dir = tempfile.mkdtemp(prefix=f"{report_name}_", dir=self.output_dir)This ensures a unique temporary directory and avoids potential race conditions.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| temp_dir = os.path.join(self.output_dir, f"{report_name}_temp") | |
| os.makedirs(temp_dir, exist_ok=True) | |
| # at the top of the file, alongside the other imports | |
| import tempfile | |
| # Create temporary directory for report contents | |
| temp_dir = tempfile.mkdtemp(prefix=f"{report_name}_", dir=self.output_dir) |
🤖 Prompt for AI Agents
In dream_layer_backend/dream_layer_backend_utils/batch_report_generator.py
around lines 84 to 86, replace the manual creation of the temporary directory
using os.path.join and os.makedirs with the tempfile module's functions like
tempfile.mkdtemp or tempfile.TemporaryDirectory. This change will ensure the
temporary directory is uniquely created and managed safely, preventing race
conditions when multiple reports are generated simultaneously.
| """ | ||
| try: | ||
| with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: | ||
| for root, dirs, files in os.walk(source_dir): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename unused loop variable
The loop variable dirs is not used within the loop body.
- for root, dirs, files in os.walk(source_dir):
+ for root, _dirs, files in os.walk(source_dir):📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for root, dirs, files in os.walk(source_dir): | |
| for root, _dirs, files in os.walk(source_dir): |
🧰 Tools
🪛 Ruff (0.12.2)
288-288: Loop control variable dirs not used within loop body
Rename unused dirs to _dirs
(B007)
🤖 Prompt for AI Agents
In dream_layer_backend/dream_layer_backend_utils/batch_report_generator.py at
line 288, the loop variable 'dirs' in the os.walk loop is unused. Rename 'dirs'
to '_' to indicate it is intentionally unused and improve code clarity.
| @app.route('/api/batch-report/generate', methods=['POST', 'OPTIONS']) | ||
| def generate_batch_report(): | ||
| """Generate a batch report ZIP file containing CSV, JSON, images, and README""" | ||
| if request.method == 'OPTIONS': | ||
| # Handle preflight request | ||
| response = jsonify({'status': 'ok'}) | ||
| response.headers.add('Access-Control-Allow-Origin', 'http://localhost:8080') | ||
| response.headers.add('Access-Control-Allow-Headers', 'Content-Type') | ||
| response.headers.add('Access-Control-Allow-Methods', 'POST, OPTIONS') | ||
| return response | ||
|
|
||
| try: | ||
| data = request.json | ||
| print(f"📊 Batch report generation request received") | ||
|
|
||
| # Validate required fields | ||
| if not data: | ||
| return jsonify({ | ||
| 'status': 'error', | ||
| 'message': 'No data provided' | ||
| }), 400 | ||
|
|
||
| images_data = data.get('images', []) | ||
| config = data.get('config', {}) | ||
| report_name = data.get('report_name', None) | ||
|
|
||
| if not images_data: | ||
| return jsonify({ | ||
| 'status': 'error', | ||
| 'message': 'No images data provided' | ||
| }), 400 | ||
|
|
||
| print(f"📸 Processing {len(images_data)} images for batch report") | ||
|
|
||
| # Initialize the batch report generator | ||
| generator = BatchReportGenerator() | ||
|
|
||
| # Generate the report | ||
| zip_path = generator.generate_report( | ||
| images_data=images_data, | ||
| config=config, | ||
| report_name=report_name | ||
| ) | ||
|
|
||
| # Validate the generated ZIP | ||
| if not os.path.exists(zip_path): | ||
| return jsonify({ | ||
| 'status': 'error', | ||
| 'message': 'Failed to generate report ZIP file' | ||
| }), 500 | ||
|
|
||
| # Validate CSV schema | ||
| if not generator.validate_csv_schema(os.path.join(os.path.dirname(zip_path), 'results.csv')): | ||
| print("⚠️ Warning: CSV schema validation failed, but continuing...") | ||
|
|
||
| # Validate ZIP contents | ||
| if not generator.validate_zip_contents(zip_path): | ||
| print("⚠️ Warning: ZIP contents validation failed, but continuing...") | ||
|
|
||
| # Get file info | ||
| file_size = os.path.getsize(zip_path) | ||
| file_name = os.path.basename(zip_path) | ||
|
|
||
| print(f"✅ Batch report generated successfully: {file_name} ({file_size} bytes)") | ||
|
|
||
| # Return the file for download | ||
| return send_file( | ||
| zip_path, | ||
| mimetype='application/zip', | ||
| as_attachment=True, | ||
| download_name=file_name | ||
| ) | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Error generating batch report: {str(e)}") | ||
| import traceback | ||
| traceback.print_exc() | ||
| return jsonify({ | ||
| 'status': 'error', | ||
| 'message': str(e) | ||
| }), 500 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix CSV validation path and f-string issues.
The endpoint implementation is well-structured with proper error handling, but has a few issues:
- Line 330: Unnecessary f-string without placeholders
- Line 369: CSV validation uses wrong path - the CSV file doesn't exist at that location
Apply these fixes:
- print(f"📊 Batch report generation request received")
+ print("📊 Batch report generation request received")
# Validate CSV schema
- if not generator.validate_csv_schema(os.path.join(os.path.dirname(zip_path), 'results.csv')):
+ # Note: CSV validation should be done on extracted content from ZIP
+ # For now, skip this validation as the CSV is inside the ZIP
print("⚠️ Warning: CSV schema validation failed, but continuing...")The rest of the endpoint logic, including CORS handling, input validation, and file delivery, is well-implemented.
🧰 Tools
🪛 Ruff (0.12.2)
330-330: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
In dream_layer_backend/extras.py around lines 317 to 398, fix two issues: first,
remove the unnecessary f-string on line 330 by replacing it with a regular
string since it has no placeholders; second, correct the CSV validation path on
line 369 by providing the accurate location of the CSV file, ensuring it points
to the actual path where the CSV is generated rather than the incorrect
directory currently used.
| case "report": | ||
| // Batch Report tab - generate reports from gallery images | ||
| const galleryImages: ImageReportData[] = [...txt2imgImages, ...img2imgImages].map((img, index) => { | ||
| // Generate proper filename | ||
| let filename: string; | ||
| if (img.url.startsWith('data:')) { | ||
| // For data URLs, create a filename based on the ID | ||
| filename = `${img.id}.png`; | ||
| } else { | ||
| // For regular URLs, extract the filename | ||
| filename = img.url.split('/').pop() || `image_${index}.png`; | ||
| } | ||
|
|
||
| return { | ||
| id: img.id, | ||
| filename: filename, | ||
| url: img.url, | ||
| prompt: img.prompt, | ||
| negativePrompt: img.negativePrompt, | ||
| timestamp: img.timestamp, | ||
| settings: { | ||
| model: img.settings?.model || 'unknown', | ||
| sampler: img.settings?.sampler || 'unknown', | ||
| steps: img.settings?.steps || 20, | ||
| cfg_scale: img.settings?.cfg_scale || 7.0, | ||
| seed: img.settings?.seed || -1, | ||
| width: img.settings?.width || 512, | ||
| height: img.settings?.height || 512, | ||
| ...img.settings | ||
| } | ||
| }; | ||
| }); | ||
|
|
||
| const allImages = galleryImages; | ||
|
|
||
| return ( | ||
| <div className="space-y-4 mb-4"> | ||
| <BatchReportGenerator | ||
| images={allImages} | ||
| config={{ | ||
| session_id: new Date().getTime().toString(), | ||
| total_images: allImages.length | ||
| }} | ||
| /> | ||
|
|
||
| {/* Data source indicator */} | ||
| {allImages.length > 0 && ( | ||
| <div className="text-xs text-muted-foreground p-2 bg-muted/50 rounded"> | ||
| Images available: {txt2imgImages.length} from txt2img, {img2imgImages.length} from img2img | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix variable scope issue in switch case.
The variable declarations within the switch case can be accessed by other cases, which could lead to unexpected behavior.
Wrap the case content in a block to restrict variable scope:
case "report":
+ {
// Batch Report tab - generate reports from gallery images
const galleryImages: ImageReportData[] = [...txt2imgImages, ...img2imgImages].map((img, index) => {
// ... rest of the mapping logic
});
const allImages = galleryImages;
return (
<div className="space-y-4 mb-4">
<BatchReportGenerator
images={allImages}
config={{
session_id: new Date().getTime().toString(),
total_images: allImages.length
}}
/>
{/* Data source indicator */}
{allImages.length > 0 && (
<div className="text-xs text-muted-foreground p-2 bg-muted/50 rounded">
Images available: {txt2imgImages.length} from txt2img, {img2imgImages.length} from img2img
</div>
)}
</div>
);
+ }The image aggregation and normalization logic looks correct otherwise.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case "report": | |
| // Batch Report tab - generate reports from gallery images | |
| const galleryImages: ImageReportData[] = [...txt2imgImages, ...img2imgImages].map((img, index) => { | |
| // Generate proper filename | |
| let filename: string; | |
| if (img.url.startsWith('data:')) { | |
| // For data URLs, create a filename based on the ID | |
| filename = `${img.id}.png`; | |
| } else { | |
| // For regular URLs, extract the filename | |
| filename = img.url.split('/').pop() || `image_${index}.png`; | |
| } | |
| return { | |
| id: img.id, | |
| filename: filename, | |
| url: img.url, | |
| prompt: img.prompt, | |
| negativePrompt: img.negativePrompt, | |
| timestamp: img.timestamp, | |
| settings: { | |
| model: img.settings?.model || 'unknown', | |
| sampler: img.settings?.sampler || 'unknown', | |
| steps: img.settings?.steps || 20, | |
| cfg_scale: img.settings?.cfg_scale || 7.0, | |
| seed: img.settings?.seed || -1, | |
| width: img.settings?.width || 512, | |
| height: img.settings?.height || 512, | |
| ...img.settings | |
| } | |
| }; | |
| }); | |
| const allImages = galleryImages; | |
| return ( | |
| <div className="space-y-4 mb-4"> | |
| <BatchReportGenerator | |
| images={allImages} | |
| config={{ | |
| session_id: new Date().getTime().toString(), | |
| total_images: allImages.length | |
| }} | |
| /> | |
| {/* Data source indicator */} | |
| {allImages.length > 0 && ( | |
| <div className="text-xs text-muted-foreground p-2 bg-muted/50 rounded"> | |
| Images available: {txt2imgImages.length} from txt2img, {img2imgImages.length} from img2img | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| case "report": | |
| { | |
| // Batch Report tab - generate reports from gallery images | |
| const galleryImages: ImageReportData[] = [...txt2imgImages, ...img2imgImages].map((img, index) => { | |
| // Generate proper filename | |
| let filename: string; | |
| if (img.url.startsWith('data:')) { | |
| // For data URLs, create a filename based on the ID | |
| filename = `${img.id}.png`; | |
| } else { | |
| // For regular URLs, extract the filename | |
| filename = img.url.split('/').pop() || `image_${index}.png`; | |
| } | |
| return { | |
| id: img.id, | |
| filename, | |
| url: img.url, | |
| prompt: img.prompt, | |
| negativePrompt: img.negativePrompt, | |
| timestamp: img.timestamp, | |
| settings: { | |
| model: img.settings?.model || 'unknown', | |
| sampler: img.settings?.sampler || 'unknown', | |
| steps: img.settings?.steps || 20, | |
| cfg_scale: img.settings?.cfg_scale || 7.0, | |
| seed: img.settings?.seed || -1, | |
| width: img.settings?.width || 512, | |
| height: img.settings?.height || 512, | |
| ...img.settings | |
| } | |
| }; | |
| }); | |
| const allImages = galleryImages; | |
| return ( | |
| <div className="space-y-4 mb-4"> | |
| <BatchReportGenerator | |
| images={allImages} | |
| config={{ | |
| session_id: new Date().getTime().toString(), | |
| total_images: allImages.length | |
| }} | |
| /> | |
| {/* Data source indicator */} | |
| {allImages.length > 0 && ( | |
| <div className="text-xs text-muted-foreground p-2 bg-muted/50 rounded"> | |
| Images available: {txt2imgImages.length} from txt2img, {img2imgImages.length} from img2img | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 390-419: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 421-421: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
In dream_layer_frontend/src/features/Extras/ExtrasPage.tsx around lines 388 to
440, the variables declared inside the "report" case of the switch statement
have function-level scope, which can cause conflicts with other cases. To fix
this, wrap the entire content of the "report" case in curly braces {} to create
a block scope, ensuring variables like galleryImages and allImages are scoped
only within this case.
| export interface BatchReportRequest { | ||
| images: ImageReportData[]; | ||
| config: GenerationConfig; | ||
| report_name?: string; | ||
| } | ||
|
|
||
| export interface ImageReportData { | ||
| id: string; | ||
| filename: string; | ||
| url: string; | ||
| prompt: string; | ||
| negativePrompt?: string; | ||
| timestamp: number; | ||
| settings: GenerationSettings; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Inconsistent naming conventions between interfaces
The interfaces mix snake_case (report_name) and camelCase (negativePrompt) for property names. This inconsistency could lead to confusion and maintenance issues. Consider using a consistent naming convention throughout the TypeScript code and handle the transformation to snake_case when sending requests to the Python backend.
Apply this diff to use consistent camelCase in TypeScript:
export interface BatchReportRequest {
images: ImageReportData[];
config: GenerationConfig;
- report_name?: string;
+ reportName?: string;
}
export interface ImageReportData {
id: string;
filename: string;
url: string;
prompt: string;
negativePrompt?: string;
timestamp: number;
settings: GenerationSettings;
}Then update line 54 to transform the property name when creating the request:
- report_name: reportName
+ report_name: reportName // Transform to snake_case for backendCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In dream_layer_frontend/src/services/reportService.ts lines 1 to 15, the
interface properties use inconsistent naming conventions, mixing snake_case and
camelCase. Change all property names to camelCase, specifically rename
'report_name' to 'reportName' in the BatchReportRequest interface. Then, in the
code that sends requests to the Python backend (around line 54), transform
'reportName' back to 'report_name' to maintain compatibility with the backend
API.
| export interface GenerationSettings { | ||
| model: string; | ||
| sampler: string; | ||
| steps: number; | ||
| cfg_scale: number; | ||
| seed: number; | ||
| width: number; | ||
| height: number; | ||
| [key: string]: any; | ||
| } | ||
|
|
||
| export interface GenerationConfig { | ||
| session_id?: string; | ||
| generation_date?: string; | ||
| total_images?: number; | ||
| [key: string]: any; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Continue using camelCase for TypeScript interface properties
The GenerationSettings and GenerationConfig interfaces also use snake_case for properties like cfg_scale, session_id, generation_date, and total_images.
Apply this diff for consistency:
export interface GenerationSettings {
model: string;
sampler: string;
steps: number;
- cfg_scale: number;
+ cfgScale: number;
seed: number;
width: number;
height: number;
[key: string]: any;
}
export interface GenerationConfig {
- session_id?: string;
- generation_date?: string;
- total_images?: number;
+ sessionId?: string;
+ generationDate?: string;
+ totalImages?: number;
[key: string]: any;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export interface GenerationSettings { | |
| model: string; | |
| sampler: string; | |
| steps: number; | |
| cfg_scale: number; | |
| seed: number; | |
| width: number; | |
| height: number; | |
| [key: string]: any; | |
| } | |
| export interface GenerationConfig { | |
| session_id?: string; | |
| generation_date?: string; | |
| total_images?: number; | |
| [key: string]: any; | |
| } | |
| export interface GenerationSettings { | |
| model: string; | |
| sampler: string; | |
| steps: number; | |
| cfgScale: number; | |
| seed: number; | |
| width: number; | |
| height: number; | |
| [key: string]: any; | |
| } | |
| export interface GenerationConfig { | |
| sessionId?: string; | |
| generationDate?: string; | |
| totalImages?: number; | |
| [key: string]: any; | |
| } |
🤖 Prompt for AI Agents
In dream_layer_frontend/src/services/reportService.ts between lines 17 and 33,
the interfaces GenerationSettings and GenerationConfig use snake_case property
names like cfg_scale, session_id, generation_date, and total_images. Rename
these properties to camelCase (cfgScale, sessionId, generationDate, totalImages)
to maintain consistent camelCase naming conventions throughout the TypeScript
codebase.
| export const generateBatchReport = async ( | ||
| images: ImageReportData[], | ||
| config: GenerationConfig, | ||
| reportName?: string | ||
| ): Promise<Blob> => { | ||
| try { | ||
| console.log('🔄 Generating batch report with', images.length, 'images'); | ||
|
|
||
| const requestData: BatchReportRequest = { | ||
| images, | ||
| config, | ||
| report_name: reportName | ||
| }; | ||
|
|
||
| const response = await fetch(`${EXTRAS_API_BASE_URL}/api/batch-report/generate`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify(requestData), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = await response.json().catch(() => ({ message: 'Unknown error' })); | ||
| throw new Error(errorData.message || `Failed to generate report: ${response.statusText}`); | ||
| } | ||
|
|
||
| // The response should be a blob (ZIP file) | ||
| const blob = await response.blob(); | ||
| console.log('✅ Batch report generated successfully, size:', blob.size, 'bytes'); | ||
|
|
||
| return blob; | ||
| } catch (error) { | ||
| console.error('❌ Error generating batch report:', error); | ||
| throw error; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add input validation and make console logging conditional
The function should validate inputs before making the API call. Also, consider making console logs conditional based on a debug flag to avoid cluttering production logs.
Add input validation at the beginning of the function:
export const generateBatchReport = async (
images: ImageReportData[],
config: GenerationConfig,
reportName?: string
): Promise<Blob> => {
+ // Validate inputs
+ if (!images || images.length === 0) {
+ throw new Error('At least one image is required for batch report generation');
+ }
+
+ if (!config) {
+ throw new Error('Configuration is required for batch report generation');
+ }
+
try {
- console.log('🔄 Generating batch report with', images.length, 'images');
+ if (import.meta.env.DEV) {
+ console.log('🔄 Generating batch report with', images.length, 'images');
+ }Also update line 72:
- console.log('✅ Batch report generated successfully, size:', blob.size, 'bytes');
+ if (import.meta.env.DEV) {
+ console.log('✅ Batch report generated successfully, size:', blob.size, 'bytes');
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const generateBatchReport = async ( | |
| images: ImageReportData[], | |
| config: GenerationConfig, | |
| reportName?: string | |
| ): Promise<Blob> => { | |
| try { | |
| console.log('🔄 Generating batch report with', images.length, 'images'); | |
| const requestData: BatchReportRequest = { | |
| images, | |
| config, | |
| report_name: reportName | |
| }; | |
| const response = await fetch(`${EXTRAS_API_BASE_URL}/api/batch-report/generate`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(requestData), | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json().catch(() => ({ message: 'Unknown error' })); | |
| throw new Error(errorData.message || `Failed to generate report: ${response.statusText}`); | |
| } | |
| // The response should be a blob (ZIP file) | |
| const blob = await response.blob(); | |
| console.log('✅ Batch report generated successfully, size:', blob.size, 'bytes'); | |
| return blob; | |
| } catch (error) { | |
| console.error('❌ Error generating batch report:', error); | |
| throw error; | |
| } | |
| }; | |
| export const generateBatchReport = async ( | |
| images: ImageReportData[], | |
| config: GenerationConfig, | |
| reportName?: string | |
| ): Promise<Blob> => { | |
| // Validate inputs | |
| if (!images || images.length === 0) { | |
| throw new Error('At least one image is required for batch report generation'); | |
| } | |
| if (!config) { | |
| throw new Error('Configuration is required for batch report generation'); | |
| } | |
| try { | |
| if (import.meta.env.DEV) { | |
| console.log('🔄 Generating batch report with', images.length, 'images'); | |
| } | |
| const requestData: BatchReportRequest = { | |
| images, | |
| config, | |
| report_name: reportName | |
| }; | |
| const response = await fetch(`${EXTRAS_API_BASE_URL}/api/batch-report/generate`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(requestData), | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json().catch(() => ({ message: 'Unknown error' })); | |
| throw new Error(errorData.message || `Failed to generate report: ${response.statusText}`); | |
| } | |
| // The response should be a blob (ZIP file) | |
| const blob = await response.blob(); | |
| if (import.meta.env.DEV) { | |
| console.log('✅ Batch report generated successfully, size:', blob.size, 'bytes'); | |
| } | |
| return blob; | |
| } catch (error) { | |
| console.error('❌ Error generating batch report:', error); | |
| throw error; | |
| } | |
| }; |
🤖 Prompt for AI Agents
In dream_layer_frontend/src/services/reportService.ts between lines 43 and 79,
add input validation at the start of the generateBatchReport function to check
that the images array is not empty and config is defined, throwing an error if
validation fails. Additionally, introduce a debug flag parameter or use an
existing one to conditionally enable console.log statements, wrapping all
console logs in checks for this flag to prevent cluttering production logs.
| export const downloadBatchReport = (blob: Blob, filename?: string) => { | ||
| const url = window.URL.createObjectURL(blob); | ||
| const a = document.createElement('a'); | ||
| a.href = url; | ||
| a.download = filename || `report_${new Date().toISOString().split('T')[0]}.zip`; | ||
| document.body.appendChild(a); | ||
| a.click(); | ||
| document.body.removeChild(a); | ||
| window.URL.revokeObjectURL(url); | ||
| console.log('📥 Batch report downloaded'); | ||
| }; No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add blob validation and make logging conditional
The function should validate the blob parameter and use conditional logging.
export const downloadBatchReport = (blob: Blob, filename?: string) => {
+ if (!blob || blob.size === 0) {
+ throw new Error('Invalid or empty blob provided for download');
+ }
+
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || `report_${new Date().toISOString().split('T')[0]}.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
- console.log('📥 Batch report downloaded');
+ if (import.meta.env.DEV) {
+ console.log('📥 Batch report downloaded');
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const downloadBatchReport = (blob: Blob, filename?: string) => { | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename || `report_${new Date().toISOString().split('T')[0]}.zip`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| console.log('📥 Batch report downloaded'); | |
| }; | |
| export const downloadBatchReport = (blob: Blob, filename?: string) => { | |
| if (!blob || blob.size === 0) { | |
| throw new Error('Invalid or empty blob provided for download'); | |
| } | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename || `report_${new Date().toISOString().split('T')[0]}.zip`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| if (import.meta.env.DEV) { | |
| console.log('📥 Batch report downloaded'); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In dream_layer_frontend/src/services/reportService.ts around lines 81 to 91, the
downloadBatchReport function lacks validation for the blob parameter and always
logs a message. Add a check to ensure the blob is valid before proceeding with
the download logic. Also, modify the logging to be conditional, for example,
only logging if a debug flag is enabled or if the download succeeds, to avoid
unnecessary console output.
Description
Adds batch report generation functionality to create organized ZIP archives containing generated images with metadata, CSV reports, and configuration details for batch analysis and archival.
Changes Made
reports
Evidence Required ✅
UI Screenshot
Generated Image
N/A - This feature creates reports from existing generated images, not new image generation.
Logs
🔄 Generating batch report with 3 images
BatchReportGenerator initialized with output directory: /tmp/tmpmjy_7bhy
✅ Batch report generated successfully: batch_report_20250805_164138.zip
📊 Report contains: 3 images, CSV metadata, configuration JSON
Tests (Optional)
============================= test session starts ==============================
platform linux -- Python 3.11.3, pytest-7.4.3, pluggy-1.0.0 --
cachedir: .pytest_cache
rootdir: omitted
plugins: anyio-3.7.1
collecting ... collected 11 items
test_batch_report_generator.py::TestBatchReportGenerator::test_initialization PASSED [ 9%]
test_batch_report_generator.py::TestBatchReportGenerator::test_csv_schema_validation PASSED [ 18%]
test_batch_report_generator.py::TestBatchReportGenerator::test_deterministic_file_naming PASSED [ 27%]
test_batch_report_generator.py::TestBatchReportGenerator::test_generate_report_creates_zip PASSED [ 36%]
test_batch_report_generator.py::TestBatchReportGenerator::test_validate_zip_contents PASSED [ 45%]
test_batch_report_generator.py::TestBatchReportGenerator::test_report_with_empty_images PASSED [ 54%]
test_batch_report_generator.py::TestBatchReportGenerator::test_report_name_generation PASSED [ 63%]
test_batch_report_generator.py::TestBatchReportIntegration::test_full_report_generation_workflow PASSED [ 72%]
test_batch_report_generator.py::TestBatchReportIntegration::test_scalability[1] PASSED [ 81%]
test_batch_report_generator.py::TestBatchReportIntegration::test_scalability[10] PASSED [ 90%]
test_batch_report_generator.py::TestBatchReportIntegration::test_scalability[100] PASSED [100%]
============================== 11 passed in 0.33s ==============================
Checklist
Summary by Sourcery
Add functionality to generate downloadable ZIP reports of image outputs, including metadata CSV, configuration JSON, image grids, and README documentation via both backend and frontend changes.
New Features:
Enhancements:
Tests:
Summary by CodeRabbit
New Features
Tests