Fix Image Processing
This commit is contained in:
220
ASYNC_IMAGE_PROCESSING.md
Normal file
220
ASYNC_IMAGE_PROCESSING.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Async Image Processing Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
The image processing system has been updated to handle external images asynchronously, preventing timeouts when processing stories with many images. This provides real-time progress updates to users showing which images are being processed.
|
||||
|
||||
## Backend Components
|
||||
|
||||
### 1. `ImageProcessingProgressService`
|
||||
- Tracks progress for individual story image processing sessions
|
||||
- Thread-safe with `ConcurrentHashMap` for multi-user support
|
||||
- Provides progress information: total images, processed count, current image, status, errors
|
||||
|
||||
### 2. `AsyncImageProcessingService`
|
||||
- Handles asynchronous image processing using Spring's `@Async` annotation
|
||||
- Counts external images before processing
|
||||
- Provides progress callbacks during processing
|
||||
- Updates story content when processing completes
|
||||
- Automatic cleanup of progress data after completion
|
||||
|
||||
### 3. Enhanced `ImageService`
|
||||
- Added `processContentImagesWithProgress()` method with callback support
|
||||
- Progress callbacks provide real-time updates during image download/processing
|
||||
- Maintains compatibility with existing synchronous processing
|
||||
|
||||
### 4. Updated `StoryController`
|
||||
- `POST /api/stories` and `PUT /api/stories/{id}` now trigger async image processing
|
||||
- `GET /api/stories/{id}/image-processing-progress` endpoint for progress polling
|
||||
- Processing starts immediately after story save and returns control to user
|
||||
|
||||
## Frontend Components
|
||||
|
||||
### 1. `ImageProcessingProgressTracker` (Utility Class)
|
||||
```typescript
|
||||
const tracker = new ImageProcessingProgressTracker(storyId);
|
||||
tracker.onProgress((progress) => {
|
||||
console.log(`Processing ${progress.processedImages}/${progress.totalImages}`);
|
||||
});
|
||||
tracker.onComplete(() => console.log('Done!'));
|
||||
tracker.start();
|
||||
```
|
||||
|
||||
### 2. `ImageProcessingProgressComponent` (React Component)
|
||||
```tsx
|
||||
<ImageProcessingProgressComponent
|
||||
storyId={storyId}
|
||||
autoStart={true}
|
||||
onComplete={() => refreshStory()}
|
||||
/>
|
||||
```
|
||||
|
||||
## User Experience
|
||||
|
||||
### Before (Synchronous)
|
||||
1. User saves story with external images
|
||||
2. Request hangs for 30+ seconds processing images
|
||||
3. Browser may timeout
|
||||
4. No feedback about progress
|
||||
5. User doesn't know if it's working
|
||||
|
||||
### After (Asynchronous)
|
||||
1. User saves story with external images
|
||||
2. Save completes immediately
|
||||
3. Progress indicator appears: "Processing 5 images. Currently image 2 of 5..."
|
||||
4. User can continue using the application
|
||||
5. Progress updates every second
|
||||
6. Story automatically refreshes when processing completes
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Progress Endpoint
|
||||
```
|
||||
GET /api/stories/{id}/image-processing-progress
|
||||
```
|
||||
|
||||
**Response when processing:**
|
||||
```json
|
||||
{
|
||||
"isProcessing": true,
|
||||
"totalImages": 5,
|
||||
"processedImages": 2,
|
||||
"currentImageUrl": "https://example.com/image.jpg",
|
||||
"status": "Processing image 3 of 5",
|
||||
"progressPercentage": 40.0,
|
||||
"completed": false,
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
**Response when completed:**
|
||||
```json
|
||||
{
|
||||
"isProcessing": false,
|
||||
"totalImages": 5,
|
||||
"processedImages": 5,
|
||||
"currentImageUrl": "",
|
||||
"status": "Completed: 5 images processed",
|
||||
"progressPercentage": 100.0,
|
||||
"completed": true,
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
**Response when no processing:**
|
||||
```json
|
||||
{
|
||||
"isProcessing": false,
|
||||
"message": "No active image processing"
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### React Hook Usage
|
||||
```tsx
|
||||
import { useImageProcessingProgress } from '../utils/imageProcessingProgress';
|
||||
|
||||
function StoryEditor({ storyId }) {
|
||||
const { progress, isTracking, startTracking } = useImageProcessingProgress(storyId);
|
||||
|
||||
const handleSave = async () => {
|
||||
await saveStory();
|
||||
startTracking(); // Start monitoring progress
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isTracking && progress && (
|
||||
<div className="progress-indicator">
|
||||
Processing {progress.processedImages}/{progress.totalImages} images...
|
||||
</div>
|
||||
)}
|
||||
<button onClick={handleSave}>Save Story</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Progress Tracking
|
||||
```typescript
|
||||
// After saving a story with external images
|
||||
const tracker = new ImageProcessingProgressTracker(storyId);
|
||||
|
||||
tracker.onProgress((progress) => {
|
||||
updateProgressBar(progress.progressPercentage);
|
||||
showStatus(progress.status);
|
||||
if (progress.currentImageUrl) {
|
||||
showCurrentImage(progress.currentImageUrl);
|
||||
}
|
||||
});
|
||||
|
||||
tracker.onComplete((finalProgress) => {
|
||||
hideProgressBar();
|
||||
showNotification('Image processing completed!');
|
||||
refreshStoryContent(); // Reload story with processed images
|
||||
});
|
||||
|
||||
tracker.onError((error) => {
|
||||
hideProgressBar();
|
||||
showError(`Image processing failed: ${error}`);
|
||||
});
|
||||
|
||||
tracker.start();
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Polling Interval
|
||||
Default: 1 second (1000ms)
|
||||
```typescript
|
||||
const tracker = new ImageProcessingProgressTracker(storyId, 500); // Poll every 500ms
|
||||
```
|
||||
|
||||
### Timeout
|
||||
Default: 5 minutes (300000ms)
|
||||
```typescript
|
||||
const tracker = new ImageProcessingProgressTracker(storyId, 1000, 600000); // 10 minute timeout
|
||||
```
|
||||
|
||||
### Spring Async Configuration
|
||||
The backend uses Spring's default async executor. For production, consider configuring a custom thread pool in your application properties:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
task:
|
||||
execution:
|
||||
pool:
|
||||
core-size: 4
|
||||
max-size: 8
|
||||
queue-capacity: 100
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Backend Errors
|
||||
- Network timeouts downloading images
|
||||
- Invalid image formats
|
||||
- Disk space issues
|
||||
- All errors are logged and returned in progress status
|
||||
|
||||
### Frontend Errors
|
||||
- Network failures during progress polling
|
||||
- Timeout if processing takes too long
|
||||
- Graceful degradation - user can continue working
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **No More Timeouts**: Large image processing operations won't timeout HTTP requests
|
||||
2. **Better UX**: Users get real-time feedback about processing progress
|
||||
3. **Improved Performance**: Users can continue using the app while images process
|
||||
4. **Error Visibility**: Clear error messages when image processing fails
|
||||
5. **Scalability**: Multiple users can process images simultaneously without blocking
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **WebSocket Support**: Replace polling with WebSocket for real-time push updates
|
||||
2. **Batch Processing**: Queue multiple stories for batch image processing
|
||||
3. **Retry Logic**: Automatic retry for failed image downloads
|
||||
4. **Progress Persistence**: Save progress to database for recovery after server restart
|
||||
5. **Image Optimization**: Automatic resize/compress images during processing
|
||||
Reference in New Issue
Block a user